Skip to content

Commit a53f663

Browse files
committed
hysteria2: Add gecko obfs
1 parent d301e65 commit a53f663

10 files changed

Lines changed: 149 additions & 16 deletions

File tree

constant/hysteria2.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package constant
22

3+
const (
4+
Hysteria2ObfsTypeSalamander = "salamander"
5+
Hysteria2ObfsTypeGecko = "gecko"
6+
)
7+
38
const (
49
Hysterai2MasqueradeTypeFile = "file"
510
Hysterai2MasqueradeTypeProxy = "proxy"

docs/configuration/inbound/hysteria2.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ icon: material/alert-decagram
55
!!! quote "Changes in sing-box 1.14.0"
66

77
:material-plus: [bbr_profile](#bbr_profile)
8-
:material-plus: [realm](#realm)
8+
:material-plus: [realm](#realm)
9+
:material-alert: [obfs](#obfstype)
910

1011
!!! quote "Changes in sing-box 1.11.0"
1112

@@ -75,14 +76,30 @@ Conflict with `ignore_client_bandwidth`.
7576

7677
#### obfs.type
7778

78-
QUIC traffic obfuscator type, only available with `salamander`.
79+
QUIC traffic obfuscator type, one of `salamander` `gecko`.
7980

8081
Disabled if empty.
8182

8283
#### obfs.password
8384

8485
QUIC traffic obfuscator password.
8586

87+
#### obfs.min_packet_size
88+
89+
!!! question "Since sing-box 1.14.0"
90+
91+
Minimum on-wire packet size in bytes. Gecko only.
92+
93+
`512` is used by default.
94+
95+
#### obfs.max_packet_size
96+
97+
!!! question "Since sing-box 1.14.0"
98+
99+
Maximum on-wire packet size in bytes. Gecko only.
100+
101+
`1200` is used by default.
102+
86103
#### users
87104

88105
Hysteria2 users

docs/configuration/inbound/hysteria2.zh.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ icon: material/alert-decagram
55
!!! quote "sing-box 1.14.0 中的更改"
66

77
:material-plus: [bbr_profile](#bbr_profile)
8-
:material-plus: [realm](#realm)
8+
:material-plus: [realm](#realm)
9+
:material-alert: [obfs](#obfstype)
910

1011
!!! quote "sing-box 1.11.0 中的更改"
1112

@@ -72,13 +73,29 @@ icon: material/alert-decagram
7273

7374
#### obfs.type
7475

75-
QUIC 流量混淆器类型,仅可设为 `salamander`
76+
QUIC 流量混淆器类型,可选 `salamander` `gecko`
7677

7778
如果为空则禁用。
7879

7980
#### obfs.password
8081

81-
QUIC 流量混淆器密码.
82+
QUIC 流量混淆器密码。
83+
84+
#### obfs.min_packet_size
85+
86+
!!! question "自 sing-box 1.14.0 起"
87+
88+
最小线上数据包大小(字节)。仅限 Gecko。
89+
90+
默认使用 `512`
91+
92+
#### obfs.max_packet_size
93+
94+
!!! question "自 sing-box 1.14.0 起"
95+
96+
最大线上数据包大小(字节)。仅限 Gecko。
97+
98+
默认使用 `1200`
8299

83100
#### users
84101

docs/configuration/outbound/hysteria2.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
:material-plus: [hop_interval_max](#hop_interval_max)
44
:material-plus: [bbr_profile](#bbr_profile)
5-
:material-plus: [realm](#realm)
5+
:material-plus: [realm](#realm)
6+
:material-alert: [obfs](#obfstype)
67

78
!!! quote "Changes in sing-box 1.11.0"
89

@@ -113,14 +114,30 @@ If empty, the BBR congestion control algorithm will be used instead of Hysteria
113114

114115
#### obfs.type
115116

116-
QUIC traffic obfuscator type, only available with `salamander`.
117+
QUIC traffic obfuscator type, one of `salamander` `gecko`.
117118

118119
Disabled if empty.
119120

120121
#### obfs.password
121122

122123
QUIC traffic obfuscator password.
123124

125+
#### obfs.min_packet_size
126+
127+
!!! question "Since sing-box 1.14.0"
128+
129+
Minimum on-wire packet size in bytes. Gecko only.
130+
131+
`512` is used by default.
132+
133+
#### obfs.max_packet_size
134+
135+
!!! question "Since sing-box 1.14.0"
136+
137+
Maximum on-wire packet size in bytes. Gecko only.
138+
139+
`1200` is used by default.
140+
124141
#### password
125142

126143
Authentication password.

docs/configuration/outbound/hysteria2.zh.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
:material-plus: [hop_interval_max](#hop_interval_max)
44
:material-plus: [bbr_profile](#bbr_profile)
5-
:material-plus: [realm](#realm)
5+
:material-plus: [realm](#realm)
6+
:material-alert: [obfs](#obfstype)
67

78
!!! quote "sing-box 1.11.0 中的更改"
89

@@ -111,13 +112,29 @@
111112

112113
#### obfs.type
113114

114-
QUIC 流量混淆器类型,仅可设为 `salamander`
115+
QUIC 流量混淆器类型,可选 `salamander` `gecko`
115116

116117
如果为空则禁用。
117118

118119
#### obfs.password
119120

120-
QUIC 流量混淆器密码.
121+
QUIC 流量混淆器密码。
122+
123+
#### obfs.min_packet_size
124+
125+
!!! question "自 sing-box 1.14.0 起"
126+
127+
最小线上数据包大小(字节)。仅限 Gecko。
128+
129+
默认使用 `512`
130+
131+
#### obfs.max_packet_size
132+
133+
!!! question "自 sing-box 1.14.0 起"
134+
135+
最大线上数据包大小(字节)。仅限 Gecko。
136+
137+
默认使用 `1200`
121138

122139
#### password
123140

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ require (
4040
github.com/sagernet/sing v0.8.11-0.20260514110501-905ad103a4df
4141
github.com/sagernet/sing-cloudflared v0.1.0
4242
github.com/sagernet/sing-mux v0.3.4
43-
github.com/sagernet/sing-quic v0.6.2-0.20260520073201-c8655743eb6e
43+
github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7
4444
github.com/sagernet/sing-shadowsocks v0.2.8
4545
github.com/sagernet/sing-shadowsocks2 v0.2.1
4646
github.com/sagernet/sing-shadowtls v0.2.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,8 @@ github.com/sagernet/sing-cloudflared v0.1.0 h1:to+2fcCx8zu4X/DirRw9Ihc+FrEZ7oEyI
248248
github.com/sagernet/sing-cloudflared v0.1.0/go.mod h1:bH2NKX+NpDTY1Zkxfboxw6MXB/ZywaNLmrDJYgKMJ2Y=
249249
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
250250
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
251-
github.com/sagernet/sing-quic v0.6.2-0.20260520073201-c8655743eb6e h1:CCJxS/iXhUA2KlQ7vNjcYUVGTYjNeHQ5s5MC32WlVPw=
252-
github.com/sagernet/sing-quic v0.6.2-0.20260520073201-c8655743eb6e/go.mod h1:+oqD54aHel4ALKkp1hVXWCgLU/EjLojvm6AUzDfvj0I=
251+
github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7 h1:hFLPJ21uNZSbRnzhOKz4Zv0b4F93mpDorWyN93BeRcM=
252+
github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7/go.mod h1:+oqD54aHel4ALKkp1hVXWCgLU/EjLojvm6AUzDfvj0I=
253253
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
254254
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
255255
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=

option/hysteria2.go

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,51 @@ type Hysteria2InboundRealm struct {
3838
STUNDomainResolver *DomainResolveOptions `json:"stun_domain_resolver,omitempty"`
3939
}
4040

41-
type Hysteria2Obfs struct {
42-
Type string `json:"type,omitempty"`
43-
Password string `json:"password,omitempty"`
41+
type Hysteria2ObfsGecko struct {
42+
MinPacketSize int `json:"min_packet_size,omitempty"`
43+
MaxPacketSize int `json:"max_packet_size,omitempty"`
44+
}
45+
46+
type _Hysteria2Obfs struct {
47+
Type string `json:"type,omitempty"`
48+
Password string `json:"password,omitempty"`
49+
GeckoOptions Hysteria2ObfsGecko `json:"-"`
50+
}
51+
52+
type Hysteria2Obfs _Hysteria2Obfs
53+
54+
func (o Hysteria2Obfs) MarshalJSON() ([]byte, error) {
55+
var v any
56+
switch o.Type {
57+
case C.Hysteria2ObfsTypeSalamander:
58+
case C.Hysteria2ObfsTypeGecko:
59+
v = o.GeckoOptions
60+
default:
61+
return nil, E.New("unknown obfs type: ", o.Type)
62+
}
63+
if v == nil {
64+
return json.Marshal((_Hysteria2Obfs)(o))
65+
}
66+
return badjson.MarshallObjects((_Hysteria2Obfs)(o), v)
67+
}
68+
69+
func (o *Hysteria2Obfs) UnmarshalJSON(bytes []byte) error {
70+
err := json.Unmarshal(bytes, (*_Hysteria2Obfs)(o))
71+
if err != nil {
72+
return err
73+
}
74+
var v any
75+
switch o.Type {
76+
case C.Hysteria2ObfsTypeSalamander:
77+
case C.Hysteria2ObfsTypeGecko:
78+
v = &o.GeckoOptions
79+
default:
80+
return E.New("unknown obfs type: ", o.Type)
81+
}
82+
if v == nil {
83+
return nil
84+
}
85+
return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Obfs)(o), v)
4486
}
4587

4688
type Hysteria2User struct {

protocol/hysteria2/inbound.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,19 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
5252
return nil, err
5353
}
5454
var salamanderPassword string
55+
var geckoPassword string
56+
var geckoMinPacketSize, geckoMaxPacketSize int
5557
if options.Obfs != nil {
5658
if options.Obfs.Password == "" {
5759
return nil, E.New("missing obfs password")
5860
}
5961
switch options.Obfs.Type {
6062
case hysteria2.ObfsTypeSalamander:
6163
salamanderPassword = options.Obfs.Password
64+
case hysteria2.ObfsTypeGecko:
65+
geckoPassword = options.Obfs.Password
66+
geckoMinPacketSize = options.Obfs.GeckoOptions.MinPacketSize
67+
geckoMaxPacketSize = options.Obfs.GeckoOptions.MaxPacketSize
6268
default:
6369
return nil, E.New("unknown obfs type: ", options.Obfs.Type)
6470
}
@@ -154,6 +160,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
154160
SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps),
155161
ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps),
156162
SalamanderPassword: salamanderPassword,
163+
GeckoPassword: geckoPassword,
164+
GeckoMinPacketSize: geckoMinPacketSize,
165+
GeckoMaxPacketSize: geckoMaxPacketSize,
157166
TLSConfig: tlsConfig,
158167
QUICOptions: qtls.QUICOptions{
159168
IdleTimeout: options.IdleTimeout.Build(),

protocol/hysteria2/outbound.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,19 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
5959
return nil, err
6060
}
6161
var salamanderPassword string
62+
var geckoPassword string
63+
var geckoMinPacketSize, geckoMaxPacketSize int
6264
if options.Obfs != nil {
6365
if options.Obfs.Password == "" {
6466
return nil, E.New("missing obfs password")
6567
}
6668
switch options.Obfs.Type {
6769
case hysteria2.ObfsTypeSalamander:
6870
salamanderPassword = options.Obfs.Password
71+
case hysteria2.ObfsTypeGecko:
72+
geckoPassword = options.Obfs.Password
73+
geckoMinPacketSize = options.Obfs.GeckoOptions.MinPacketSize
74+
geckoMaxPacketSize = options.Obfs.GeckoOptions.MaxPacketSize
6975
default:
7076
return nil, E.New("unknown obfs type: ", options.Obfs.Type)
7177
}
@@ -121,6 +127,9 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
121127
SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps),
122128
ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps),
123129
SalamanderPassword: salamanderPassword,
130+
GeckoPassword: geckoPassword,
131+
GeckoMinPacketSize: geckoMinPacketSize,
132+
GeckoMaxPacketSize: geckoMaxPacketSize,
124133
Password: options.Password,
125134
TLSConfig: tlsConfig,
126135
QUICOptions: qtls.QUICOptions{

0 commit comments

Comments
 (0)