ddf0ddf71e
Сквозная миграция HY2XS на современную Hysteria (2.12.2) и переход на v1. Build: - версия Hysteria резолвится на этапе сборки из HyNetworks/hysteria и замораживается в metadata пакета (version + immutable url + sha256); - compatibility gate: реальный бинарник должен принять канонический конфиг HY2XS для gecko и salamander до создания пакета; - сборка прогоняет тесты оркестратора и админки. Конфигурационный контракт: - HY2XS_CONFIG_SCHEMA_VERSION=2, чужая схема отклоняется fail-fast; - obfs стал настоящим union gecko|salamander, gecko — default; - obfs-блок рендерится оркестратором целиком, два подтипа одновременно структурно невозможны; - современный baseline: congestion bbr/standard, disableLossCompensation=false, disableStatelessReset=false, полный quic-блок. Исправления: - share URI для gecko: генератор был завязан на Obfs.Salamander.Password и выдавал нерабочую ссылку при любой другой обфускации; - SNI брался только из ACME-блока и уходил пустым при HY2XS_TLS_MODE=file; - экспорт конфига выносил trafficStats.secret, access_token и obfs-пароль; - экспорт терял неизвестные upstream-поля при round-trip через типизированную модель; - renderRuntimeEnv печатал тип обфускации литералом, расходясь с конфигом; - namedotcom удалён из ACME-реестра (нет в Hysteria с 2.11.0). Тесты: - 95 тестов оркестратора: env, рендер, семантика профиля, резолвер, rollover; - тесты URI и экспорта в Go; - tools/test/e2e-hysteria.sh с реальным клиентом Hysteria. UX: - подсказки и примеры в форме создания пира. Прочее: CHANGELOG.md, .gitattributes (LF для target-side файлов), документация на русском.
319 lines
19 KiB
Go
319 lines
19 KiB
Go
package bo
|
||
|
||
// ObfsShareConfig — та часть обфускации, которую способна описать официальная
|
||
// схема hysteria2:// URI: только type и password.
|
||
//
|
||
// Gecko minPacketSize/maxPacketSize в ссылку не помещаются. Именно поэтому
|
||
// HY2XS фиксирует их на upstream defaults 512/1200: иначе сгенерированная
|
||
// ссылка не описывала бы подключение полностью.
|
||
type ObfsShareConfig struct {
|
||
Type string
|
||
Password string
|
||
}
|
||
|
||
// ObfsShare приводит любой поддерживаемый тип обфускации к единому виду для
|
||
// генерации клиентской ссылки. Знание о подтипах живёт рядом с моделью, чтобы
|
||
// добавление нового типа не требовало правок в слое share URI.
|
||
func (c *Hysteria2ServerConfig) ObfsShare() ObfsShareConfig {
|
||
if c == nil || c.Obfs == nil || c.Obfs.Type == nil {
|
||
return ObfsShareConfig{}
|
||
}
|
||
|
||
switch *c.Obfs.Type {
|
||
case "salamander":
|
||
if c.Obfs.Salamander != nil && c.Obfs.Salamander.Password != nil {
|
||
return ObfsShareConfig{Type: "salamander", Password: *c.Obfs.Salamander.Password}
|
||
}
|
||
case "gecko":
|
||
if c.Obfs.Gecko != nil && c.Obfs.Gecko.Password != nil {
|
||
return ObfsShareConfig{Type: "gecko", Password: *c.Obfs.Gecko.Password}
|
||
}
|
||
}
|
||
|
||
return ObfsShareConfig{}
|
||
}
|
||
|
||
// AcmeDomain возвращает первый ACME-домен, если он есть.
|
||
func (c *Hysteria2ServerConfig) AcmeDomain() string {
|
||
if c == nil || c.ACME == nil || len(c.ACME.Domains) == 0 {
|
||
return ""
|
||
}
|
||
return c.ACME.Domains[0]
|
||
}
|
||
|
||
type Hysteria2ServerConfig struct {
|
||
Listen *string `yaml:"listen,omitempty" json:"listen" validate:"required"`
|
||
Obfs *serverConfigObfs `yaml:"obfs,omitempty" json:"obfs" validate:"omitempty"`
|
||
TLS *serverConfigTLS `yaml:"tls,omitempty" json:"tls" validate:"omitempty"`
|
||
ACME *serverConfigACME `yaml:"acme,omitempty" json:"acme" validate:"omitempty"`
|
||
ECH *serverConfigECH `yaml:"ech,omitempty" json:"ech" validate:"omitempty"`
|
||
QUIC *serverConfigQUIC `yaml:"quic,omitempty" json:"quic" validate:"omitempty"`
|
||
Bandwidth *serverConfigBandwidth `yaml:"bandwidth,omitempty" json:"bandwidth" validate:"omitempty"`
|
||
Congestion *ServerConfigCongestion `yaml:"congestion,omitempty" json:"congestion" validate:"omitempty"`
|
||
IgnoreClientBandwidth *bool `yaml:"ignoreClientBandwidth,omitempty" json:"ignoreClientBandwidth" validate:"omitempty"`
|
||
SpeedTest *bool `yaml:"speedTest,omitempty" json:"speedTest" validate:"omitempty"`
|
||
DisableUDP *bool `yaml:"disableUDP,omitempty" json:"disableUDP" validate:"omitempty"`
|
||
UDPIdleTimeout *string `yaml:"udpIdleTimeout,omitempty" json:"udpIdleTimeout" validate:"omitempty"`
|
||
Auth *ServerConfigAuth `yaml:"auth,omitempty" json:"-" validate:"omitempty"`
|
||
Resolver *serverConfigResolver `yaml:"resolver,omitempty" json:"resolver" validate:"omitempty"`
|
||
Sniff *serverConfigSniff `yaml:"sniff,omitempty" json:"sniff" validate:"omitempty"`
|
||
ACL *serverConfigACL `yaml:"acl,omitempty" json:"acl" validate:"omitempty"`
|
||
Outbounds []serverConfigOutboundEntry `yaml:"outbounds,omitempty" json:"outbounds" validate:"omitempty"`
|
||
TrafficStats *ServerConfigTrafficStats `yaml:"trafficStats,omitempty" json:"trafficStats" validate:"required"`
|
||
Masquerade *serverConfigMasquerade `yaml:"masquerade,omitempty" json:"masquerade" validate:"omitempty"`
|
||
Mimic *serverConfigMimic `yaml:"mimic,omitempty" json:"mimic" validate:"omitempty"`
|
||
Realm *serverConfigRealm `yaml:"realm,omitempty" json:"realm" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigObfsSalamander struct {
|
||
Password *string `yaml:"password,omitempty" json:"password" validate:"required"`
|
||
}
|
||
|
||
// serverConfigObfsGecko описывает Gecko-обфускацию (Hysteria 2.9.2+).
|
||
// Gecko достраивается поверх Salamander и дополнительно фрагментирует
|
||
// QUIC handshake на пакеты случайного размера в диапазоне min..max.
|
||
type serverConfigObfsGecko struct {
|
||
Password *string `yaml:"password,omitempty" json:"password" validate:"required"`
|
||
MinPacketSize *int `yaml:"minPacketSize,omitempty" json:"minPacketSize" validate:"omitempty"`
|
||
MaxPacketSize *int `yaml:"maxPacketSize,omitempty" json:"maxPacketSize" validate:"omitempty"`
|
||
}
|
||
|
||
// Обе ветки опциональны: в конфиге присутствует ровно одна из них,
|
||
// соответствующая Type.
|
||
type serverConfigObfs struct {
|
||
Type *string `yaml:"type,omitempty" json:"type" validate:"required"`
|
||
Salamander *serverConfigObfsSalamander `yaml:"salamander,omitempty" json:"salamander" validate:"omitempty"`
|
||
Gecko *serverConfigObfsGecko `yaml:"gecko,omitempty" json:"gecko" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigTLS struct {
|
||
Cert *string `yaml:"cert,omitempty" json:"cert" validate:"required"`
|
||
Key *string `yaml:"key,omitempty" json:"key" validate:"required"`
|
||
SNIGuard *string `yaml:"sniGuard,omitempty" json:"sniGuard" validate:"omitempty"`
|
||
ClientCA *string `yaml:"clientCA,omitempty" json:"clientCA" validate:"omitempty"`
|
||
}
|
||
|
||
// serverConfigECH — Encrypted Client Hello (Hysteria 2.10.0+).
|
||
// HY2XS не генерирует ECH keypair и не включает ECH в default-профиле,
|
||
// но обязан корректно читать и сохранять этот блок.
|
||
type serverConfigECH struct {
|
||
KeyPath *string `yaml:"keyPath,omitempty" json:"keyPath" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigACME struct {
|
||
// Common fields
|
||
Domains []string `yaml:"domains,omitempty" json:"domains" validate:"required"`
|
||
Email *string `yaml:"email,omitempty" json:"email" validate:"required"`
|
||
CA *string `yaml:"ca,omitempty" json:"ca" validate:"required"`
|
||
ListenHost *string `yaml:"listenHost,omitempty" json:"listenHost" validate:"required"`
|
||
Dir *string `yaml:"dir,omitempty" json:"dir" validate:"required"`
|
||
|
||
// Type selection
|
||
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
|
||
HTTP *serverConfigACMEHTTP `yaml:"http,omitempty" json:"http" validate:"omitempty"`
|
||
TLS *serverConfigACMETLS `yaml:"tls,omitempty" json:"tls" validate:"omitempty"`
|
||
DNS *serverConfigACMEDNS `yaml:"dns,omitempty" json:"dns" validate:"omitempty"`
|
||
|
||
// Legacy fields for backwards compatibility
|
||
// Only applicable when Type is empty
|
||
DisableHTTP *bool `yaml:"disableHTTP,omitempty" json:"disableHTTP" validate:"required"`
|
||
DisableTLSALPN *bool `yaml:"disableTLSALPN,omitempty" json:"disableTLSALPN" validate:"required"`
|
||
AltHTTPPort *int `yaml:"altHTTPPort,omitempty" json:"altHTTPPort" validate:"required"`
|
||
AltTLSALPNPort *int `yaml:"altTLSALPNPort,omitempty" json:"altTLSALPNPort" validate:"required"`
|
||
}
|
||
|
||
type serverConfigACMEHTTP struct {
|
||
AltPort *int `yaml:"altPort,omitempty" json:"altPort" validate:"required"`
|
||
}
|
||
|
||
type serverConfigACMETLS struct {
|
||
AltPort *int `yaml:"altPort,omitempty" json:"altPort" validate:"required"`
|
||
}
|
||
|
||
type serverConfigACMEDNS struct {
|
||
Name *string `yaml:"name,omitempty" json:"name" validate:"required"`
|
||
Config map[string]string `yaml:"config,omitempty" json:"config" validate:"required"`
|
||
}
|
||
|
||
type serverConfigQUIC struct {
|
||
InitStreamReceiveWindow *uint64 `yaml:"initStreamReceiveWindow,omitempty" json:"initStreamReceiveWindow" validate:"omitempty"`
|
||
MaxStreamReceiveWindow *uint64 `yaml:"maxStreamReceiveWindow,omitempty" json:"maxStreamReceiveWindow" validate:"omitempty"`
|
||
InitConnectionReceiveWindow *uint64 `yaml:"initConnReceiveWindow,omitempty" json:"initConnReceiveWindow" validate:"omitempty"`
|
||
MaxConnectionReceiveWindow *uint64 `yaml:"maxConnReceiveWindow,omitempty" json:"maxConnReceiveWindow" validate:"omitempty"`
|
||
MaxIdleTimeout *string `yaml:"maxIdleTimeout,omitempty" json:"maxIdleTimeout" validate:"omitempty"`
|
||
MaxIncomingStreams *int64 `yaml:"maxIncomingStreams,omitempty" json:"maxIncomingStreams" validate:"omitempty"`
|
||
DisablePathMTUDiscovery *bool `yaml:"disablePathMTUDiscovery,omitempty" json:"disablePathMTUDiscovery" validate:"omitempty"`
|
||
// DisableStatelessReset появился в Hysteria 2.12.2. HY2XS оставляет
|
||
// stateless reset включённым: клиент после перезапуска сервера или сна
|
||
// устройства переподключается сразу, а не по таймауту.
|
||
DisableStatelessReset *bool `yaml:"disableStatelessReset,omitempty" json:"disableStatelessReset" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigBandwidth struct {
|
||
Up *string `yaml:"up,omitempty" json:"up" validate:"required"`
|
||
Down *string `yaml:"down,omitempty" json:"down" validate:"required"`
|
||
// DisableLossCompensation появился в Hysteria 2.10.0.
|
||
DisableLossCompensation *bool `yaml:"disableLossCompensation,omitempty" json:"disableLossCompensation" validate:"omitempty"`
|
||
}
|
||
|
||
// ServerConfigCongestion — fallback congestion controller. Используется, когда
|
||
// Brutal bandwidth не согласован сторонами. Это не то же самое, что host-level
|
||
// BBR ядра Linux: у Hysteria собственный congestion-control контур.
|
||
type ServerConfigCongestion struct {
|
||
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
|
||
BBRProfile *string `yaml:"bbrProfile,omitempty" json:"bbrProfile" validate:"omitempty"`
|
||
}
|
||
|
||
type ServerConfigAuthHTTP struct {
|
||
URL *string `yaml:"url,omitempty" json:"url" validate:"required"`
|
||
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"required"`
|
||
}
|
||
|
||
type ServerConfigAuth struct {
|
||
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
|
||
Password *string `yaml:"password,omitempty" json:"password" validate:"omitempty"`
|
||
UserPass map[string]string `yaml:"userpass,omitempty" json:"userpass" validate:"omitempty"`
|
||
HTTP *ServerConfigAuthHTTP `yaml:"http,omitempty" json:"http" validate:"omitempty"`
|
||
Command *string `yaml:"command,omitempty" json:"command" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigResolverTCP struct {
|
||
Addr *string `yaml:"addr,omitempty" json:"addr" validate:"required"`
|
||
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"required"`
|
||
}
|
||
|
||
type serverConfigResolverUDP struct {
|
||
Addr *string `yaml:"addr,omitempty" json:"addr" validate:"required"`
|
||
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"required"`
|
||
}
|
||
|
||
type serverConfigResolverTLS struct {
|
||
Addr *string `yaml:"addr,omitempty" json:"addr" validate:"required"`
|
||
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"required"`
|
||
SNI *string `yaml:"sni,omitempty" json:"sni" validate:"required"`
|
||
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"required"`
|
||
}
|
||
|
||
type serverConfigResolverHTTPS struct {
|
||
Addr *string `yaml:"addr,omitempty" json:"addr" validate:"required"`
|
||
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"required"`
|
||
SNI *string `yaml:"sni,omitempty" json:"sni" validate:"required"`
|
||
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"required"`
|
||
}
|
||
|
||
type serverConfigResolver struct {
|
||
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
|
||
TCP *serverConfigResolverTCP `yaml:"tcp,omitempty" json:"tcp" validate:"omitempty"`
|
||
UDP *serverConfigResolverUDP `yaml:"udp,omitempty" json:"udp" validate:"omitempty"`
|
||
TLS *serverConfigResolverTLS `yaml:"tls,omitempty" json:"tls" validate:"omitempty"`
|
||
HTTPS *serverConfigResolverHTTPS `yaml:"https,omitempty" json:"https" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigSniff struct {
|
||
Enable *bool `yaml:"enable,omitempty" json:"enable" validate:"required"`
|
||
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"required"`
|
||
RewriteDomain *bool `yaml:"rewriteDomain,omitempty" json:"rewriteDomain" validate:"required"`
|
||
TCPPorts *string `yaml:"tcpPorts,omitempty" json:"tcpPorts" validate:"omitempty"`
|
||
UDPPorts *string `yaml:"udpPorts,omitempty" json:"udpPorts" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigACL struct {
|
||
File *string `yaml:"file,omitempty" json:"file" validate:"omitempty"`
|
||
Inline []string `yaml:"inline,omitempty" json:"inline" validate:"omitempty"`
|
||
GeoIP *string `yaml:"geoip,omitempty" json:"geoip" validate:"omitempty"`
|
||
GeoSite *string `yaml:"geosite,omitempty" json:"geosite" validate:"omitempty"`
|
||
GeoUpdateInterval *string `yaml:"geoUpdateInterval,omitempty" json:"geoUpdateInterval" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigOutboundDirect struct {
|
||
Mode *string `yaml:"mode,omitempty" json:"mode" validate:"required"`
|
||
BindIPv4 *string `yaml:"bindIPv4,omitempty" json:"bindIPv4" validate:"required"`
|
||
BindIPv6 *string `yaml:"bindIPv6,omitempty" json:"bindIPv6" validate:"required"`
|
||
BindDevice *string `yaml:"bindDevice,omitempty" json:"bindDevice" validate:"required"`
|
||
FastOpen *bool `yaml:"fastOpen,omitempty" json:"fastOpen" validate:"required"`
|
||
}
|
||
|
||
type serverConfigOutboundSOCKS5 struct {
|
||
Addr *string `yaml:"addr,omitempty" json:"addr" validate:"required"`
|
||
Username *string `yaml:"username,omitempty" json:"username" validate:"omitempty"`
|
||
Password *string `yaml:"password,omitempty" json:"password" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigOutboundHTTP struct {
|
||
URL *string `yaml:"url,omitempty" json:"url" validate:"required"`
|
||
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"required"`
|
||
}
|
||
|
||
type serverConfigOutboundEntry struct {
|
||
Name *string `yaml:"name,omitempty" json:"name" validate:"required"`
|
||
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
|
||
Direct *serverConfigOutboundDirect `yaml:"direct,omitempty" json:"direct" validate:"omitempty"`
|
||
SOCKS5 *serverConfigOutboundSOCKS5 `yaml:"socks5,omitempty" json:"socks5" validate:"omitempty"`
|
||
HTTP *serverConfigOutboundHTTP `yaml:"http,omitempty" json:"http" validate:"omitempty"`
|
||
}
|
||
|
||
type ServerConfigTrafficStats struct {
|
||
Listen *string `yaml:"listen,omitempty" json:"listen" validate:"required"`
|
||
Secret *string `yaml:"secret,omitempty" json:"-" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigMasqueradeFile struct {
|
||
Dir *string `yaml:"dir,omitempty" json:"dir" validate:"required"`
|
||
}
|
||
|
||
type serverConfigMasqueradeProxy struct {
|
||
// URL с Hysteria 2.12.2 может указывать и на unix socket.
|
||
URL *string `yaml:"url,omitempty" json:"url" validate:"required"`
|
||
RewriteHost *bool `yaml:"rewriteHost,omitempty" json:"rewriteHost" validate:"required"`
|
||
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"required"`
|
||
XForwarded *bool `yaml:"xForwarded,omitempty" json:"xForwarded" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigMasqueradeString struct {
|
||
Content *string `yaml:"content,omitempty" json:"content" validate:"required"`
|
||
Headers map[string]string `yaml:"headers,omitempty" json:"headers" validate:"omitempty"`
|
||
StatusCode *int `yaml:"statusCode,omitempty" json:"statusCode" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigMasquerade struct {
|
||
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
|
||
File *serverConfigMasqueradeFile `yaml:"file,omitempty" json:"file" validate:"omitempty"`
|
||
Proxy *serverConfigMasqueradeProxy `yaml:"proxy,omitempty" json:"proxy" validate:"omitempty"`
|
||
String *serverConfigMasqueradeString `yaml:"string,omitempty" json:"string" validate:"omitempty"`
|
||
ListenHTTP *string `yaml:"listenHTTP,omitempty" json:"listenHTTP" validate:"omitempty"`
|
||
ListenHTTPS *string `yaml:"listenHTTPS,omitempty" json:"listenHTTPS" validate:"omitempty"`
|
||
ForceHTTPS *bool `yaml:"forceHTTPS,omitempty" json:"forceHTTPS" validate:"omitempty"`
|
||
}
|
||
|
||
// serverConfigMimic — Mimic / fake TCP (Hysteria 2.12.0+).
|
||
// Требует отдельный сторонний бинарник, root и eBPF/XDP, поэтому в
|
||
// production-профиле HY2XS выключен: текущий systemd-контракт запускает
|
||
// Hysteria под непривилегированным пользователем.
|
||
type serverConfigMimic struct {
|
||
Enabled *bool `yaml:"enabled,omitempty" json:"enabled" validate:"omitempty"`
|
||
Interface *string `yaml:"interface,omitempty" json:"interface" validate:"omitempty"`
|
||
XDPMode *string `yaml:"xdpMode,omitempty" json:"xdpMode" validate:"omitempty"`
|
||
Path *string `yaml:"path,omitempty" json:"path" validate:"omitempty"`
|
||
ExtraArgs []string `yaml:"extraArgs,omitempty" json:"extraArgs" validate:"omitempty"`
|
||
}
|
||
|
||
type serverConfigRealmPortMapping struct {
|
||
Enabled *bool `yaml:"enabled,omitempty" json:"enabled" validate:"omitempty"`
|
||
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"omitempty"`
|
||
Lifetime *string `yaml:"lifetime,omitempty" json:"lifetime" validate:"omitempty"`
|
||
}
|
||
|
||
// serverConfigRealm — Hysteria Realms (2.9.0+), запуск сервера за NAT через
|
||
// STUN/hole punching. Меняет фундаментальный сетевой контракт HY2XS
|
||
// (выделенный сервер, публичный IPv4, UDP/443, own nftables), поэтому в
|
||
// default-профиле не используется, но должен корректно читаться.
|
||
type serverConfigRealm struct {
|
||
StunServers []string `yaml:"stunServers,omitempty" json:"stunServers" validate:"omitempty"`
|
||
StunTimeout *string `yaml:"stunTimeout,omitempty" json:"stunTimeout" validate:"omitempty"`
|
||
PunchTimeout *string `yaml:"punchTimeout,omitempty" json:"punchTimeout" validate:"omitempty"`
|
||
HeartbeatInterval *string `yaml:"heartbeatInterval,omitempty" json:"heartbeatInterval" validate:"omitempty"`
|
||
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"omitempty"`
|
||
IPMode *string `yaml:"ipMode,omitempty" json:"ipMode" validate:"omitempty"`
|
||
PortMapping *serverConfigRealmPortMapping `yaml:"portMapping,omitempty" json:"portMapping" validate:"omitempty"`
|
||
}
|