diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go index 55caf58b..bd92260a 100644 --- a/adapter/outbound/hysteria.go +++ b/adapter/outbound/hysteria.go @@ -12,6 +12,7 @@ import ( "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/proxydialer" tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" @@ -44,6 +45,9 @@ type Hysteria struct { option *HysteriaOption client *core.Client + + tlsConfig *tlsC.Config + echConfig *ech.Config } 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 { return cDialer.ListenPacket(ctx, network, "", rAddrPort) }, remoteAddr: func(addr string) (net.Addr, error) { - return resolveUDPAddr(ctx, "udp", addr, h.prefer) + udpAddr, err := resolveUDPAddr(ctx, "udp", addr, h.prefer) + if err != nil { + return nil, err + } + err = h.echConfig.ClientHandle(ctx, h.tlsConfig) + if err != nil { + return nil, err + } + return udpAddr, nil }, } } @@ -93,30 +105,31 @@ func (h *Hysteria) ProxyInfo() C.ProxyInfo { type HysteriaOption struct { BasicOption - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port,omitempty"` - Ports string `proxy:"ports,omitempty"` - Protocol string `proxy:"protocol,omitempty"` - ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash - Up string `proxy:"up"` - UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash - Down string `proxy:"down"` - DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash - Auth string `proxy:"auth,omitempty"` - AuthString string `proxy:"auth-str,omitempty"` - Obfs string `proxy:"obfs,omitempty"` - SNI string `proxy:"sni,omitempty"` - SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` - Fingerprint string `proxy:"fingerprint,omitempty"` - ALPN []string `proxy:"alpn,omitempty"` - CustomCA string `proxy:"ca,omitempty"` - CustomCAString string `proxy:"ca-str,omitempty"` - ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"` - ReceiveWindow int `proxy:"recv-window,omitempty"` - DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"` - FastOpen bool `proxy:"fast-open,omitempty"` - HopInterval int `proxy:"hop-interval,omitempty"` + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port,omitempty"` + Ports string `proxy:"ports,omitempty"` + Protocol string `proxy:"protocol,omitempty"` + ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash + Up string `proxy:"up"` + UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash + Down string `proxy:"down"` + DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash + Auth string `proxy:"auth,omitempty"` + AuthString string `proxy:"auth-str,omitempty"` + Obfs string `proxy:"obfs,omitempty"` + SNI string `proxy:"sni,omitempty"` + ECHOpts ECHOptions `proxy:"ech-opts,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + Fingerprint string `proxy:"fingerprint,omitempty"` + ALPN []string `proxy:"alpn,omitempty"` + CustomCA string `proxy:"ca,omitempty"` + CustomCAString string `proxy:"ca-str,omitempty"` + ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"` + ReceiveWindow int `proxy:"recv-window,omitempty"` + DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"` + FastOpen bool `proxy:"fast-open,omitempty"` + HopInterval int `proxy:"hop-interval,omitempty"` } func (c *HysteriaOption) Speed() (uint64, uint64, error) { @@ -161,6 +174,13 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { } else { tlsConfig.NextProtos = []string{DefaultALPN} } + + echConfig, err := option.ECHOpts.Parse() + if err != nil { + return nil, err + } + tlsClientConfig := tlsC.UConfig(tlsConfig) + quicConfig := &quic.Config{ InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn), MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn), @@ -215,7 +235,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { down = uint64(option.DownSpeed * mbpsToBps) } client, err := core.NewClient( - addr, ports, option.Protocol, auth, tlsC.UConfig(tlsConfig), quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl { + addr, ports, option.Protocol, auth, tlsClientConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl { return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) }, obfuscator, hopInterval, option.FastOpen, ) @@ -233,8 +253,10 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), }, - option: &option, - client: client, + option: &option, + client: client, + tlsConfig: tlsClientConfig, + echConfig: echConfig, } return outbound, nil diff --git a/adapter/outbound/hysteria2.go b/adapter/outbound/hysteria2.go index 3e028e0e..b9e41c46 100644 --- a/adapter/outbound/hysteria2.go +++ b/adapter/outbound/hysteria2.go @@ -41,24 +41,25 @@ type Hysteria2 struct { type Hysteria2Option struct { BasicOption - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port,omitempty"` - Ports string `proxy:"ports,omitempty"` - HopInterval int `proxy:"hop-interval,omitempty"` - Up string `proxy:"up,omitempty"` - Down string `proxy:"down,omitempty"` - Password string `proxy:"password,omitempty"` - Obfs string `proxy:"obfs,omitempty"` - ObfsPassword string `proxy:"obfs-password,omitempty"` - SNI string `proxy:"sni,omitempty"` - SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` - Fingerprint string `proxy:"fingerprint,omitempty"` - ALPN []string `proxy:"alpn,omitempty"` - CustomCA string `proxy:"ca,omitempty"` - CustomCAString string `proxy:"ca-str,omitempty"` - CWND int `proxy:"cwnd,omitempty"` - UdpMTU int `proxy:"udp-mtu,omitempty"` + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port,omitempty"` + Ports string `proxy:"ports,omitempty"` + HopInterval int `proxy:"hop-interval,omitempty"` + Up string `proxy:"up,omitempty"` + Down string `proxy:"down,omitempty"` + Password string `proxy:"password,omitempty"` + Obfs string `proxy:"obfs,omitempty"` + ObfsPassword string `proxy:"obfs-password,omitempty"` + SNI string `proxy:"sni,omitempty"` + ECHOpts ECHOptions `proxy:"ech-opts,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + Fingerprint string `proxy:"fingerprint,omitempty"` + ALPN []string `proxy:"alpn,omitempty"` + CustomCA string `proxy:"ca,omitempty"` + CustomCAString string `proxy:"ca-str,omitempty"` + CWND int `proxy:"cwnd,omitempty"` + UdpMTU int `proxy:"udp-mtu,omitempty"` // quic-go special config InitialStreamReceiveWindow uint64 `proxy:"initial-stream-receive-window,omitempty"` @@ -153,6 +154,12 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { tlsConfig.NextProtos = option.ALPN } + tlsClientConfig := tlsC.UConfig(tlsConfig) + echConfig, err := option.ECHOpts.Parse() + if err != nil { + return nil, err + } + if option.UdpMTU == 0 { // "1200" from quic-go's MaxDatagramSize // "-3" from quic-go's DatagramFrame.MaxDataLen @@ -174,13 +181,21 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { ReceiveBPS: StringToBps(option.Down), SalamanderPassword: salamanderPassword, Password: option.Password, - TLSConfig: tlsC.UConfig(tlsConfig), + TLSConfig: tlsClientConfig, QUICConfig: quicConfig, UDPDisabled: false, CWND: option.CWND, UdpMTU: option.UdpMTU, ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) { - return resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion)) + udpAddr, err := resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion)) + if err != nil { + return nil, err + } + err = echConfig.ClientHandle(ctx, tlsClientConfig) + if err != nil { + return nil, err + } + return udpAddr, nil }, } diff --git a/adapter/outbound/tuic.go b/adapter/outbound/tuic.go index f062f830..525f9ec6 100644 --- a/adapter/outbound/tuic.go +++ b/adapter/outbound/tuic.go @@ -12,6 +12,7 @@ import ( "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/resolver" tlsC "github.com/metacubex/mihomo/component/tls" @@ -28,6 +29,9 @@ type Tuic struct { *Base option *TuicOption client *tuic.PoolClient + + tlsConfig *tlsC.Config + echConfig *ech.Config } type TuicOption struct { @@ -48,18 +52,19 @@ type TuicOption struct { DisableSni bool `proxy:"disable-sni,omitempty"` MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"` - FastOpen bool `proxy:"fast-open,omitempty"` - MaxOpenStreams int `proxy:"max-open-streams,omitempty"` - CWND int `proxy:"cwnd,omitempty"` - SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` - Fingerprint string `proxy:"fingerprint,omitempty"` - CustomCA string `proxy:"ca,omitempty"` - CustomCAString string `proxy:"ca-str,omitempty"` - ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"` - ReceiveWindow int `proxy:"recv-window,omitempty"` - DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"` - MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"` - SNI string `proxy:"sni,omitempty"` + FastOpen bool `proxy:"fast-open,omitempty"` + MaxOpenStreams int `proxy:"max-open-streams,omitempty"` + CWND int `proxy:"cwnd,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + Fingerprint string `proxy:"fingerprint,omitempty"` + CustomCA string `proxy:"ca,omitempty"` + CustomCAString string `proxy:"ca-str,omitempty"` + ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"` + ReceiveWindow int `proxy:"recv-window,omitempty"` + DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"` + MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"` + SNI string `proxy:"sni,omitempty"` + ECHOpts ECHOptions `proxy:"ech-opts,omitempty"` UDPOverStream bool `proxy:"udp-over-stream,omitempty"` UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"` @@ -135,6 +140,10 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport * if err != nil { return nil, nil, err } + err = t.echConfig.ClientHandle(ctx, t.tlsConfig) + if err != nil { + return nil, nil, err + } addr = udpAddr var pc net.PacketConn pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort()) @@ -249,6 +258,12 @@ func NewTuic(option TuicOption) (*Tuic, error) { tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config } + tlsClientConfig := tlsC.UConfig(tlsConfig) + echConfig, err := option.ECHOpts.Parse() + if err != nil { + return nil, err + } + switch option.UDPOverStreamVersion { case uot.Version, uot.LegacyVersion: case 0: @@ -268,7 +283,9 @@ func NewTuic(option TuicOption) (*Tuic, error) { rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), }, - option: &option, + option: &option, + tlsConfig: tlsClientConfig, + echConfig: echConfig, } clientMaxOpenStreams := int64(option.MaxOpenStreams) @@ -285,7 +302,7 @@ func NewTuic(option TuicOption) (*Tuic, error) { if len(option.Token) > 0 { tkn := tuic.GenTKN(option.Token) clientOption := &tuic.ClientOptionV4{ - TlsConfig: tlsC.UConfig(tlsConfig), + TlsConfig: tlsClientConfig, QuicConfig: quicConfig, Token: tkn, UdpRelayMode: udpRelayMode, @@ -305,7 +322,7 @@ func NewTuic(option TuicOption) (*Tuic, error) { maxUdpRelayPacketSize = tuic.MaxFragSizeV5 } clientOption := &tuic.ClientOptionV5{ - TlsConfig: tlsC.UConfig(tlsConfig), + TlsConfig: tlsClientConfig, QuicConfig: quicConfig, Uuid: uuid.FromStringOrNil(option.UUID), Password: option.Password, diff --git a/docs/config.yaml b/docs/config.yaml index 26895341..221dbe46 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -756,6 +756,10 @@ proxies: # socks5 up: "30 Mbps" # 若不写单位,默认为 Mbps down: "200 Mbps" # 若不写单位,默认为 Mbps # sni: server.com + # ech-opts: + # enable: true # 必须手动开启 + # # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev) + # config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA # skip-cert-verify: false # recv-window-conn: 12582912 # recv-window: 52428800 @@ -779,6 +783,10 @@ proxies: # socks5 # obfs: salamander # 默认为空,如果填写则开启 obfs,目前仅支持 salamander # obfs-password: yourpassword # sni: server.com + # ech-opts: + # enable: true # 必须手动开启 + # # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev) + # config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA # skip-cert-verify: false # fingerprint: xxxx # alpn: @@ -854,6 +862,10 @@ proxies: # socks5 # skip-cert-verify: true # max-open-streams: 20 # default 100, too many open streams may hurt performance # sni: example.com + # ech-opts: + # enable: true # 必须手动开启 + # # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev) + # config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA # # meta 和 sing-box 私有扩展,将 ss-uot 用于 udp 中继,开启此选项后 udp-relay-mode 将失效 # 警告,与原版 tuic 不兼容!!!