feat(v1): Gecko-обфускация, latest-stable Hysteria на сборке и forward-compatible admin
Сквозная миграция 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 файлов), документация на русском.
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"hy2xs-admin/dao"
|
||||
"hy2xs-admin/model/bo"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/proxy"
|
||||
"net"
|
||||
@@ -14,6 +15,28 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// resolveShareSni выбирает SNI для клиентской ссылки.
|
||||
//
|
||||
// ACME-домен — не единственный источник истины: в режиме tls (файловые
|
||||
// сертификаты) блока acme в конфиге нет, но домен продукта известен из
|
||||
// runtime-конфига. Публичный IPv4 в качестве SNI не используется.
|
||||
func resolveShareSni(acmeDomain string, publicHost string) string {
|
||||
if domain := strings.TrimSpace(acmeDomain); domain != "" {
|
||||
return domain
|
||||
}
|
||||
if domain := strings.TrimSpace(os.Getenv("HY2XS_DOMAIN")); domain != "" && !isIPAddress(domain) {
|
||||
return domain
|
||||
}
|
||||
if host := strings.TrimSpace(publicHost); host != "" && !isIPAddress(host) {
|
||||
return host
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isIPAddress(value string) bool {
|
||||
return net.ParseIP(strings.TrimSpace(value)) != nil
|
||||
}
|
||||
|
||||
func resolvePublicEndpoint() (string, int, error) {
|
||||
host := strings.TrimSpace(os.Getenv("HY2XS_PUBLIC_HOST"))
|
||||
if host == "" || host == "0.0.0.0" {
|
||||
@@ -135,20 +158,8 @@ func Hysteria2Url(accountId int64) (string, error) {
|
||||
remark = *hysteria2ConfigRemark.Value
|
||||
}
|
||||
|
||||
obfsType := ""
|
||||
obfsPassword := ""
|
||||
if hysteria2Config.Obfs != nil &&
|
||||
hysteria2Config.Obfs.Type != nil &&
|
||||
hysteria2Config.Obfs.Salamander != nil &&
|
||||
hysteria2Config.Obfs.Salamander.Password != nil {
|
||||
obfsType = *hysteria2Config.Obfs.Type
|
||||
obfsPassword = *hysteria2Config.Obfs.Salamander.Password
|
||||
}
|
||||
|
||||
sni := ""
|
||||
if hysteria2Config.ACME != nil && len(hysteria2Config.ACME.Domains) > 0 {
|
||||
sni = hysteria2Config.ACME.Domains[0]
|
||||
}
|
||||
obfs := hysteria2Config.ObfsShare()
|
||||
sni := resolveShareSni(hysteria2Config.AcmeDomain(), hostname)
|
||||
|
||||
secret := ""
|
||||
if peer.SecretEncrypted != nil {
|
||||
@@ -158,14 +169,20 @@ func Hysteria2Url(accountId int64) (string, error) {
|
||||
}
|
||||
secret = decrypted
|
||||
}
|
||||
return buildHysteria2Url(secret, hostname, port, obfsType, obfsPassword, sni, remark), nil
|
||||
return buildHysteria2Url(secret, hostname, port, obfs, sni, remark), nil
|
||||
}
|
||||
|
||||
func buildHysteria2Url(conPass string, hostname string, port int, obfsType string, obfsPassword string, sni string, remark string) string {
|
||||
// isShareableObfsType перечисляет типы обфускации, которые официальная
|
||||
// URI-схема Hysteria умеет передавать клиенту.
|
||||
func isShareableObfsType(obfsType string) bool {
|
||||
return obfsType == "salamander" || obfsType == "gecko"
|
||||
}
|
||||
|
||||
func buildHysteria2Url(conPass string, hostname string, port int, obfs bo.ObfsShareConfig, sni string, remark string) string {
|
||||
query := url.Values{}
|
||||
if obfsType == "salamander" && obfsPassword != "" {
|
||||
query.Set("obfs", "salamander")
|
||||
query.Set("obfs-password", obfsPassword)
|
||||
if isShareableObfsType(obfs.Type) && obfs.Password != "" {
|
||||
query.Set("obfs", obfs.Type)
|
||||
query.Set("obfs-password", obfs.Password)
|
||||
}
|
||||
if sni != "" {
|
||||
query.Set("sni", sni)
|
||||
|
||||
Reference in New Issue
Block a user