hysteria2: Add gecko obfs

This commit is contained in:
世界 2026-05-25 13:11:59 +08:00
parent 6a8071d1ce
commit d0ef5c028d
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
10 changed files with 149 additions and 16 deletions

View file

@ -1,5 +1,10 @@
package constant
const (
Hysteria2ObfsTypeSalamander = "salamander"
Hysteria2ObfsTypeGecko = "gecko"
)
const (
Hysterai2MasqueradeTypeFile = "file"
Hysterai2MasqueradeTypeProxy = "proxy"

View file

@ -5,7 +5,8 @@ icon: material/alert-decagram
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [bbr_profile](#bbr_profile)
:material-plus: [realm](#realm)
:material-plus: [realm](#realm)
:material-alert: [obfs](#obfstype)
!!! quote "Changes in sing-box 1.11.0"
@ -75,7 +76,7 @@ Conflict with `ignore_client_bandwidth`.
#### obfs.type
QUIC traffic obfuscator type, only available with `salamander`.
QUIC traffic obfuscator type, one of `salamander` `gecko`.
Disabled if empty.
@ -83,6 +84,22 @@ Disabled if empty.
QUIC traffic obfuscator password.
#### obfs.min_packet_size
!!! question "Since sing-box 1.14.0"
Minimum on-wire packet size in bytes. Gecko only.
`512` is used by default.
#### obfs.max_packet_size
!!! question "Since sing-box 1.14.0"
Maximum on-wire packet size in bytes. Gecko only.
`1200` is used by default.
#### users
Hysteria2 users

View file

@ -5,7 +5,8 @@ icon: material/alert-decagram
!!! quote "sing-box 1.14.0 中的更改"
:material-plus: [bbr_profile](#bbr_profile)
:material-plus: [realm](#realm)
:material-plus: [realm](#realm)
:material-alert: [obfs](#obfstype)
!!! quote "sing-box 1.11.0 中的更改"
@ -72,13 +73,29 @@ icon: material/alert-decagram
#### obfs.type
QUIC 流量混淆器类型,仅可设为 `salamander`。
QUIC 流量混淆器类型,可选 `salamander` `gecko`。
如果为空则禁用。
#### obfs.password
QUIC 流量混淆器密码.
QUIC 流量混淆器密码。
#### obfs.min_packet_size
!!! question "自 sing-box 1.14.0 起"
最小线上数据包大小(字节)。仅限 Gecko。
默认使用 `512`
#### obfs.max_packet_size
!!! question "自 sing-box 1.14.0 起"
最大线上数据包大小(字节)。仅限 Gecko。
默认使用 `1200`
#### users

View file

@ -2,7 +2,8 @@
:material-plus: [hop_interval_max](#hop_interval_max)
:material-plus: [bbr_profile](#bbr_profile)
:material-plus: [realm](#realm)
:material-plus: [realm](#realm)
:material-alert: [obfs](#obfstype)
!!! quote "Changes in sing-box 1.11.0"
@ -113,7 +114,7 @@ If empty, the BBR congestion control algorithm will be used instead of Hysteria
#### obfs.type
QUIC traffic obfuscator type, only available with `salamander`.
QUIC traffic obfuscator type, one of `salamander` `gecko`.
Disabled if empty.
@ -121,6 +122,22 @@ Disabled if empty.
QUIC traffic obfuscator password.
#### obfs.min_packet_size
!!! question "Since sing-box 1.14.0"
Minimum on-wire packet size in bytes. Gecko only.
`512` is used by default.
#### obfs.max_packet_size
!!! question "Since sing-box 1.14.0"
Maximum on-wire packet size in bytes. Gecko only.
`1200` is used by default.
#### password
Authentication password.

View file

@ -2,7 +2,8 @@
:material-plus: [hop_interval_max](#hop_interval_max)
:material-plus: [bbr_profile](#bbr_profile)
:material-plus: [realm](#realm)
:material-plus: [realm](#realm)
:material-alert: [obfs](#obfstype)
!!! quote "sing-box 1.11.0 中的更改"
@ -111,13 +112,29 @@
#### obfs.type
QUIC 流量混淆器类型,仅可设为 `salamander`。
QUIC 流量混淆器类型,可选 `salamander` `gecko`。
如果为空则禁用。
#### obfs.password
QUIC 流量混淆器密码.
QUIC 流量混淆器密码。
#### obfs.min_packet_size
!!! question "自 sing-box 1.14.0 起"
最小线上数据包大小(字节)。仅限 Gecko。
默认使用 `512`
#### obfs.max_packet_size
!!! question "自 sing-box 1.14.0 起"
最大线上数据包大小(字节)。仅限 Gecko。
默认使用 `1200`
#### password

2
go.mod
View file

@ -40,7 +40,7 @@ require (
github.com/sagernet/sing v0.8.12-0.20260625092856-31bbf21d4b12
github.com/sagernet/sing-cloudflared v0.1.1
github.com/sagernet/sing-mux v0.3.5
github.com/sagernet/sing-quic v0.6.2-0.20260520073201-c8655743eb6e
github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7
github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.1

4
go.sum
View file

@ -248,8 +248,8 @@ github.com/sagernet/sing-cloudflared v0.1.1 h1:By29ZWMJl8QU6UcC5pmBv803rYigAoSmz
github.com/sagernet/sing-cloudflared v0.1.1/go.mod h1:bH2NKX+NpDTY1Zkxfboxw6MXB/ZywaNLmrDJYgKMJ2Y=
github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI=
github.com/sagernet/sing-mux v0.3.5/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
github.com/sagernet/sing-quic v0.6.2-0.20260520073201-c8655743eb6e h1:CCJxS/iXhUA2KlQ7vNjcYUVGTYjNeHQ5s5MC32WlVPw=
github.com/sagernet/sing-quic v0.6.2-0.20260520073201-c8655743eb6e/go.mod h1:+oqD54aHel4ALKkp1hVXWCgLU/EjLojvm6AUzDfvj0I=
github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7 h1:hFLPJ21uNZSbRnzhOKz4Zv0b4F93mpDorWyN93BeRcM=
github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7/go.mod h1:+oqD54aHel4ALKkp1hVXWCgLU/EjLojvm6AUzDfvj0I=
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=

View file

@ -38,9 +38,51 @@ type Hysteria2InboundRealm struct {
STUNDomainResolver *DomainResolveOptions `json:"stun_domain_resolver,omitempty"`
}
type Hysteria2Obfs struct {
Type string `json:"type,omitempty"`
Password string `json:"password,omitempty"`
type Hysteria2ObfsGecko struct {
MinPacketSize int `json:"min_packet_size,omitempty"`
MaxPacketSize int `json:"max_packet_size,omitempty"`
}
type _Hysteria2Obfs struct {
Type string `json:"type,omitempty"`
Password string `json:"password,omitempty"`
GeckoOptions Hysteria2ObfsGecko `json:"-"`
}
type Hysteria2Obfs _Hysteria2Obfs
func (o Hysteria2Obfs) MarshalJSON() ([]byte, error) {
var v any
switch o.Type {
case C.Hysteria2ObfsTypeSalamander:
case C.Hysteria2ObfsTypeGecko:
v = o.GeckoOptions
default:
return nil, E.New("unknown obfs type: ", o.Type)
}
if v == nil {
return json.Marshal((_Hysteria2Obfs)(o))
}
return badjson.MarshallObjects((_Hysteria2Obfs)(o), v)
}
func (o *Hysteria2Obfs) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_Hysteria2Obfs)(o))
if err != nil {
return err
}
var v any
switch o.Type {
case C.Hysteria2ObfsTypeSalamander:
case C.Hysteria2ObfsTypeGecko:
v = &o.GeckoOptions
default:
return E.New("unknown obfs type: ", o.Type)
}
if v == nil {
return nil
}
return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Obfs)(o), v)
}
type Hysteria2User struct {

View file

@ -52,6 +52,8 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
return nil, err
}
var salamanderPassword string
var geckoPassword string
var geckoMinPacketSize, geckoMaxPacketSize int
if options.Obfs != nil {
if options.Obfs.Password == "" {
return nil, E.New("missing obfs password")
@ -59,6 +61,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
switch options.Obfs.Type {
case hysteria2.ObfsTypeSalamander:
salamanderPassword = options.Obfs.Password
case hysteria2.ObfsTypeGecko:
geckoPassword = options.Obfs.Password
geckoMinPacketSize = options.Obfs.GeckoOptions.MinPacketSize
geckoMaxPacketSize = options.Obfs.GeckoOptions.MaxPacketSize
default:
return nil, E.New("unknown obfs type: ", options.Obfs.Type)
}
@ -154,6 +160,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps),
ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps),
SalamanderPassword: salamanderPassword,
GeckoPassword: geckoPassword,
GeckoMinPacketSize: geckoMinPacketSize,
GeckoMaxPacketSize: geckoMaxPacketSize,
TLSConfig: tlsConfig,
QUICOptions: qtls.QUICOptions{
IdleTimeout: options.IdleTimeout.Build(),

View file

@ -59,6 +59,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, err
}
var salamanderPassword string
var geckoPassword string
var geckoMinPacketSize, geckoMaxPacketSize int
if options.Obfs != nil {
if options.Obfs.Password == "" {
return nil, E.New("missing obfs password")
@ -66,6 +68,10 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
switch options.Obfs.Type {
case hysteria2.ObfsTypeSalamander:
salamanderPassword = options.Obfs.Password
case hysteria2.ObfsTypeGecko:
geckoPassword = options.Obfs.Password
geckoMinPacketSize = options.Obfs.GeckoOptions.MinPacketSize
geckoMaxPacketSize = options.Obfs.GeckoOptions.MaxPacketSize
default:
return nil, E.New("unknown obfs type: ", options.Obfs.Type)
}
@ -121,6 +127,9 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps),
ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps),
SalamanderPassword: salamanderPassword,
GeckoPassword: geckoPassword,
GeckoMinPacketSize: geckoMinPacketSize,
GeckoMaxPacketSize: geckoMaxPacketSize,
Password: options.Password,
TLSConfig: tlsConfig,
QUICOptions: qtls.QUICOptions{