Skip to content

Commit dc958e6

Browse files
committed
feat: add ech-opts for hysteria/hysteria2/tuic outbound
1 parent 8a5f3b8 commit dc958e6

File tree

4 files changed

+129
-63
lines changed

4 files changed

+129
-63
lines changed

adapter/outbound/hysteria.go

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/metacubex/mihomo/component/ca"
1414
"github.com/metacubex/mihomo/component/dialer"
15+
"github.com/metacubex/mihomo/component/ech"
1516
"github.com/metacubex/mihomo/component/proxydialer"
1617
tlsC "github.com/metacubex/mihomo/component/tls"
1718
C "github.com/metacubex/mihomo/constant"
@@ -44,6 +45,9 @@ type Hysteria struct {
4445

4546
option *HysteriaOption
4647
client *core.Client
48+
49+
tlsConfig *tlsC.Config
50+
echConfig *ech.Config
4751
}
4852

4953
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
@@ -79,7 +83,15 @@ func (h *Hysteria) genHdc(ctx context.Context) utils.PacketDialer {
7983
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
8084
},
8185
remoteAddr: func(addr string) (net.Addr, error) {
82-
return resolveUDPAddr(ctx, "udp", addr, h.prefer)
86+
udpAddr, err := resolveUDPAddr(ctx, "udp", addr, h.prefer)
87+
if err != nil {
88+
return nil, err
89+
}
90+
err = h.echConfig.ClientHandle(ctx, h.tlsConfig)
91+
if err != nil {
92+
return nil, err
93+
}
94+
return udpAddr, nil
8395
},
8496
}
8597
}
@@ -93,30 +105,31 @@ func (h *Hysteria) ProxyInfo() C.ProxyInfo {
93105

94106
type HysteriaOption struct {
95107
BasicOption
96-
Name string `proxy:"name"`
97-
Server string `proxy:"server"`
98-
Port int `proxy:"port,omitempty"`
99-
Ports string `proxy:"ports,omitempty"`
100-
Protocol string `proxy:"protocol,omitempty"`
101-
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
102-
Up string `proxy:"up"`
103-
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
104-
Down string `proxy:"down"`
105-
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
106-
Auth string `proxy:"auth,omitempty"`
107-
AuthString string `proxy:"auth-str,omitempty"`
108-
Obfs string `proxy:"obfs,omitempty"`
109-
SNI string `proxy:"sni,omitempty"`
110-
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
111-
Fingerprint string `proxy:"fingerprint,omitempty"`
112-
ALPN []string `proxy:"alpn,omitempty"`
113-
CustomCA string `proxy:"ca,omitempty"`
114-
CustomCAString string `proxy:"ca-str,omitempty"`
115-
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
116-
ReceiveWindow int `proxy:"recv-window,omitempty"`
117-
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
118-
FastOpen bool `proxy:"fast-open,omitempty"`
119-
HopInterval int `proxy:"hop-interval,omitempty"`
108+
Name string `proxy:"name"`
109+
Server string `proxy:"server"`
110+
Port int `proxy:"port,omitempty"`
111+
Ports string `proxy:"ports,omitempty"`
112+
Protocol string `proxy:"protocol,omitempty"`
113+
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
114+
Up string `proxy:"up"`
115+
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
116+
Down string `proxy:"down"`
117+
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
118+
Auth string `proxy:"auth,omitempty"`
119+
AuthString string `proxy:"auth-str,omitempty"`
120+
Obfs string `proxy:"obfs,omitempty"`
121+
SNI string `proxy:"sni,omitempty"`
122+
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
123+
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
124+
Fingerprint string `proxy:"fingerprint,omitempty"`
125+
ALPN []string `proxy:"alpn,omitempty"`
126+
CustomCA string `proxy:"ca,omitempty"`
127+
CustomCAString string `proxy:"ca-str,omitempty"`
128+
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
129+
ReceiveWindow int `proxy:"recv-window,omitempty"`
130+
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
131+
FastOpen bool `proxy:"fast-open,omitempty"`
132+
HopInterval int `proxy:"hop-interval,omitempty"`
120133
}
121134

122135
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
@@ -161,6 +174,13 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
161174
} else {
162175
tlsConfig.NextProtos = []string{DefaultALPN}
163176
}
177+
178+
echConfig, err := option.ECHOpts.Parse()
179+
if err != nil {
180+
return nil, err
181+
}
182+
tlsClientConfig := tlsC.UConfig(tlsConfig)
183+
164184
quicConfig := &quic.Config{
165185
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
166186
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
@@ -215,7 +235,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
215235
down = uint64(option.DownSpeed * mbpsToBps)
216236
}
217237
client, err := core.NewClient(
218-
addr, ports, option.Protocol, auth, tlsC.UConfig(tlsConfig), quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
238+
addr, ports, option.Protocol, auth, tlsClientConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
219239
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
220240
}, obfuscator, hopInterval, option.FastOpen,
221241
)
@@ -233,8 +253,10 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
233253
rmark: option.RoutingMark,
234254
prefer: C.NewDNSPrefer(option.IPVersion),
235255
},
236-
option: &option,
237-
client: client,
256+
option: &option,
257+
client: client,
258+
tlsConfig: tlsClientConfig,
259+
echConfig: echConfig,
238260
}
239261

240262
return outbound, nil

adapter/outbound/hysteria2.go

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,25 @@ type Hysteria2 struct {
4141

4242
type Hysteria2Option struct {
4343
BasicOption
44-
Name string `proxy:"name"`
45-
Server string `proxy:"server"`
46-
Port int `proxy:"port,omitempty"`
47-
Ports string `proxy:"ports,omitempty"`
48-
HopInterval int `proxy:"hop-interval,omitempty"`
49-
Up string `proxy:"up,omitempty"`
50-
Down string `proxy:"down,omitempty"`
51-
Password string `proxy:"password,omitempty"`
52-
Obfs string `proxy:"obfs,omitempty"`
53-
ObfsPassword string `proxy:"obfs-password,omitempty"`
54-
SNI string `proxy:"sni,omitempty"`
55-
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
56-
Fingerprint string `proxy:"fingerprint,omitempty"`
57-
ALPN []string `proxy:"alpn,omitempty"`
58-
CustomCA string `proxy:"ca,omitempty"`
59-
CustomCAString string `proxy:"ca-str,omitempty"`
60-
CWND int `proxy:"cwnd,omitempty"`
61-
UdpMTU int `proxy:"udp-mtu,omitempty"`
44+
Name string `proxy:"name"`
45+
Server string `proxy:"server"`
46+
Port int `proxy:"port,omitempty"`
47+
Ports string `proxy:"ports,omitempty"`
48+
HopInterval int `proxy:"hop-interval,omitempty"`
49+
Up string `proxy:"up,omitempty"`
50+
Down string `proxy:"down,omitempty"`
51+
Password string `proxy:"password,omitempty"`
52+
Obfs string `proxy:"obfs,omitempty"`
53+
ObfsPassword string `proxy:"obfs-password,omitempty"`
54+
SNI string `proxy:"sni,omitempty"`
55+
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
56+
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
57+
Fingerprint string `proxy:"fingerprint,omitempty"`
58+
ALPN []string `proxy:"alpn,omitempty"`
59+
CustomCA string `proxy:"ca,omitempty"`
60+
CustomCAString string `proxy:"ca-str,omitempty"`
61+
CWND int `proxy:"cwnd,omitempty"`
62+
UdpMTU int `proxy:"udp-mtu,omitempty"`
6263

6364
// quic-go special config
6465
InitialStreamReceiveWindow uint64 `proxy:"initial-stream-receive-window,omitempty"`
@@ -153,6 +154,12 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
153154
tlsConfig.NextProtos = option.ALPN
154155
}
155156

157+
tlsClientConfig := tlsC.UConfig(tlsConfig)
158+
echConfig, err := option.ECHOpts.Parse()
159+
if err != nil {
160+
return nil, err
161+
}
162+
156163
if option.UdpMTU == 0 {
157164
// "1200" from quic-go's MaxDatagramSize
158165
// "-3" from quic-go's DatagramFrame.MaxDataLen
@@ -174,13 +181,21 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
174181
ReceiveBPS: StringToBps(option.Down),
175182
SalamanderPassword: salamanderPassword,
176183
Password: option.Password,
177-
TLSConfig: tlsC.UConfig(tlsConfig),
184+
TLSConfig: tlsClientConfig,
178185
QUICConfig: quicConfig,
179186
UDPDisabled: false,
180187
CWND: option.CWND,
181188
UdpMTU: option.UdpMTU,
182189
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
183-
return resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
190+
udpAddr, err := resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
191+
if err != nil {
192+
return nil, err
193+
}
194+
err = echConfig.ClientHandle(ctx, tlsClientConfig)
195+
if err != nil {
196+
return nil, err
197+
}
198+
return udpAddr, nil
184199
},
185200
}
186201

adapter/outbound/tuic.go

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/metacubex/mihomo/component/ca"
1414
"github.com/metacubex/mihomo/component/dialer"
15+
"github.com/metacubex/mihomo/component/ech"
1516
"github.com/metacubex/mihomo/component/proxydialer"
1617
"github.com/metacubex/mihomo/component/resolver"
1718
tlsC "github.com/metacubex/mihomo/component/tls"
@@ -28,6 +29,9 @@ type Tuic struct {
2829
*Base
2930
option *TuicOption
3031
client *tuic.PoolClient
32+
33+
tlsConfig *tlsC.Config
34+
echConfig *ech.Config
3135
}
3236

3337
type TuicOption struct {
@@ -48,18 +52,19 @@ type TuicOption struct {
4852
DisableSni bool `proxy:"disable-sni,omitempty"`
4953
MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"`
5054

51-
FastOpen bool `proxy:"fast-open,omitempty"`
52-
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
53-
CWND int `proxy:"cwnd,omitempty"`
54-
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
55-
Fingerprint string `proxy:"fingerprint,omitempty"`
56-
CustomCA string `proxy:"ca,omitempty"`
57-
CustomCAString string `proxy:"ca-str,omitempty"`
58-
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
59-
ReceiveWindow int `proxy:"recv-window,omitempty"`
60-
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
61-
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
62-
SNI string `proxy:"sni,omitempty"`
55+
FastOpen bool `proxy:"fast-open,omitempty"`
56+
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
57+
CWND int `proxy:"cwnd,omitempty"`
58+
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
59+
Fingerprint string `proxy:"fingerprint,omitempty"`
60+
CustomCA string `proxy:"ca,omitempty"`
61+
CustomCAString string `proxy:"ca-str,omitempty"`
62+
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
63+
ReceiveWindow int `proxy:"recv-window,omitempty"`
64+
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
65+
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
66+
SNI string `proxy:"sni,omitempty"`
67+
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
6368

6469
UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
6570
UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"`
@@ -135,6 +140,10 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
135140
if err != nil {
136141
return nil, nil, err
137142
}
143+
err = t.echConfig.ClientHandle(ctx, t.tlsConfig)
144+
if err != nil {
145+
return nil, nil, err
146+
}
138147
addr = udpAddr
139148
var pc net.PacketConn
140149
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
@@ -249,6 +258,12 @@ func NewTuic(option TuicOption) (*Tuic, error) {
249258
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
250259
}
251260

261+
tlsClientConfig := tlsC.UConfig(tlsConfig)
262+
echConfig, err := option.ECHOpts.Parse()
263+
if err != nil {
264+
return nil, err
265+
}
266+
252267
switch option.UDPOverStreamVersion {
253268
case uot.Version, uot.LegacyVersion:
254269
case 0:
@@ -268,7 +283,9 @@ func NewTuic(option TuicOption) (*Tuic, error) {
268283
rmark: option.RoutingMark,
269284
prefer: C.NewDNSPrefer(option.IPVersion),
270285
},
271-
option: &option,
286+
option: &option,
287+
tlsConfig: tlsClientConfig,
288+
echConfig: echConfig,
272289
}
273290

274291
clientMaxOpenStreams := int64(option.MaxOpenStreams)
@@ -285,7 +302,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
285302
if len(option.Token) > 0 {
286303
tkn := tuic.GenTKN(option.Token)
287304
clientOption := &tuic.ClientOptionV4{
288-
TlsConfig: tlsC.UConfig(tlsConfig),
305+
TlsConfig: tlsClientConfig,
289306
QuicConfig: quicConfig,
290307
Token: tkn,
291308
UdpRelayMode: udpRelayMode,
@@ -305,7 +322,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
305322
maxUdpRelayPacketSize = tuic.MaxFragSizeV5
306323
}
307324
clientOption := &tuic.ClientOptionV5{
308-
TlsConfig: tlsC.UConfig(tlsConfig),
325+
TlsConfig: tlsClientConfig,
309326
QuicConfig: quicConfig,
310327
Uuid: uuid.FromStringOrNil(option.UUID),
311328
Password: option.Password,

docs/config.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,10 @@ proxies: # socks5
756756
up: "30 Mbps" # 若不写单位,默认为 Mbps
757757
down: "200 Mbps" # 若不写单位,默认为 Mbps
758758
# sni: server.com
759+
# ech-opts:
760+
# enable: true # 必须手动开启
761+
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
762+
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
759763
# skip-cert-verify: false
760764
# recv-window-conn: 12582912
761765
# recv-window: 52428800
@@ -779,6 +783,10 @@ proxies: # socks5
779783
# obfs: salamander # 默认为空,如果填写则开启 obfs,目前仅支持 salamander
780784
# obfs-password: yourpassword
781785
# sni: server.com
786+
# ech-opts:
787+
# enable: true # 必须手动开启
788+
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
789+
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
782790
# skip-cert-verify: false
783791
# fingerprint: xxxx
784792
# alpn:
@@ -854,6 +862,10 @@ proxies: # socks5
854862
# skip-cert-verify: true
855863
# max-open-streams: 20 # default 100, too many open streams may hurt performance
856864
# sni: example.com
865+
# ech-opts:
866+
# enable: true # 必须手动开启
867+
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
868+
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
857869
#
858870
# meta 和 sing-box 私有扩展,将 ss-uot 用于 udp 中继,开启此选项后 udp-relay-mode 将失效
859871
# 警告,与原版 tuic 不兼容!!!

0 commit comments

Comments
 (0)