mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-20 09:00:04 +08:00
feat: add ech-opts for anytls/shadowsocks/trojan/vmess/vless outbound
This commit is contained in:
parent
bb8c47d83d
commit
c6d7ef8cb8
@ -34,6 +34,7 @@ type AnyTLSOption struct {
|
|||||||
Password string `proxy:"password"`
|
Password string `proxy:"password"`
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
ALPN []string `proxy:"alpn,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
|
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
@ -115,12 +116,17 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
|||||||
IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second,
|
IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second,
|
||||||
MinIdleSession: option.MinIdleSession,
|
MinIdleSession: option.MinIdleSession,
|
||||||
}
|
}
|
||||||
|
echConfig, err := option.ECHOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
tlsConfig := &vmess.TLSConfig{
|
tlsConfig := &vmess.TLSConfig{
|
||||||
Host: option.SNI,
|
Host: option.SNI,
|
||||||
SkipCertVerify: option.SkipCertVerify,
|
SkipCertVerify: option.SkipCertVerify,
|
||||||
NextProtos: option.ALPN,
|
NextProtos: option.ALPN,
|
||||||
FingerPrint: option.Fingerprint,
|
FingerPrint: option.Fingerprint,
|
||||||
ClientFingerprint: option.ClientFingerprint,
|
ClientFingerprint: option.ClientFingerprint,
|
||||||
|
ECH: echConfig,
|
||||||
}
|
}
|
||||||
if tlsConfig.Host == "" {
|
if tlsConfig.Host == "" {
|
||||||
tlsConfig.Host = option.Server
|
tlsConfig.Host = option.Server
|
||||||
|
|||||||
28
adapter/outbound/ech.go
Normal file
28
adapter/outbound/ech.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ECHOptions struct {
|
||||||
|
Enable bool `proxy:"enable,omitempty" obfs:"enable,omitempty"`
|
||||||
|
Config string `proxy:"config,omitempty" obfs:"config,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o ECHOptions) Parse() (*ech.Config, error) {
|
||||||
|
if !o.Enable {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
echConfig := &ech.Config{}
|
||||||
|
if o.Config != "" {
|
||||||
|
list, err := base64.StdEncoding.DecodeString(o.Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("base64 decode ech config string failed: %v", err)
|
||||||
|
}
|
||||||
|
echConfig.EncryptedClientHelloConfigList = list
|
||||||
|
}
|
||||||
|
return echConfig, nil
|
||||||
|
}
|
||||||
@ -64,6 +64,7 @@ type v2rayObfsOption struct {
|
|||||||
Host string `obfs:"host,omitempty"`
|
Host string `obfs:"host,omitempty"`
|
||||||
Path string `obfs:"path,omitempty"`
|
Path string `obfs:"path,omitempty"`
|
||||||
TLS bool `obfs:"tls,omitempty"`
|
TLS bool `obfs:"tls,omitempty"`
|
||||||
|
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
|
||||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||||
Headers map[string]string `obfs:"headers,omitempty"`
|
Headers map[string]string `obfs:"headers,omitempty"`
|
||||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||||
@ -77,6 +78,7 @@ type gostObfsOption struct {
|
|||||||
Host string `obfs:"host,omitempty"`
|
Host string `obfs:"host,omitempty"`
|
||||||
Path string `obfs:"path,omitempty"`
|
Path string `obfs:"path,omitempty"`
|
||||||
TLS bool `obfs:"tls,omitempty"`
|
TLS bool `obfs:"tls,omitempty"`
|
||||||
|
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
|
||||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||||
Headers map[string]string `obfs:"headers,omitempty"`
|
Headers map[string]string `obfs:"headers,omitempty"`
|
||||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||||
@ -303,6 +305,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
v2rayOption.TLS = true
|
v2rayOption.TLS = true
|
||||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||||
v2rayOption.Fingerprint = opts.Fingerprint
|
v2rayOption.Fingerprint = opts.Fingerprint
|
||||||
|
|
||||||
|
echConfig, err := opts.ECHOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", addr, err)
|
||||||
|
}
|
||||||
|
v2rayOption.ECHConfig = echConfig
|
||||||
}
|
}
|
||||||
} else if option.Plugin == "gost-plugin" {
|
} else if option.Plugin == "gost-plugin" {
|
||||||
opts := gostObfsOption{Host: "bing.com", Mux: true}
|
opts := gostObfsOption{Host: "bing.com", Mux: true}
|
||||||
@ -325,6 +333,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
gostOption.TLS = true
|
gostOption.TLS = true
|
||||||
gostOption.SkipCertVerify = opts.SkipCertVerify
|
gostOption.SkipCertVerify = opts.SkipCertVerify
|
||||||
gostOption.Fingerprint = opts.Fingerprint
|
gostOption.Fingerprint = opts.Fingerprint
|
||||||
|
|
||||||
|
echConfig, err := opts.ECHOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ss %s initialize gost-plugin error: %w", addr, err)
|
||||||
|
}
|
||||||
|
gostOption.ECHConfig = echConfig
|
||||||
}
|
}
|
||||||
} else if option.Plugin == shadowtls.Mode {
|
} else if option.Plugin == shadowtls.Mode {
|
||||||
obfsMode = shadowtls.Mode
|
obfsMode = shadowtls.Mode
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
N "github.com/metacubex/mihomo/common/net"
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
"github.com/metacubex/mihomo/component/proxydialer"
|
"github.com/metacubex/mihomo/component/proxydialer"
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
@ -32,6 +33,7 @@ type Trojan struct {
|
|||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
realityConfig *tlsC.RealityConfig
|
realityConfig *tlsC.RealityConfig
|
||||||
|
echConfig *ech.Config
|
||||||
|
|
||||||
ssCipher core.Cipher
|
ssCipher core.Cipher
|
||||||
}
|
}
|
||||||
@ -48,6 +50,7 @@ type TrojanOption struct {
|
|||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
|
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
@ -77,6 +80,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
|||||||
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
|
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
|
||||||
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||||
ClientFingerprint: t.option.ClientFingerprint,
|
ClientFingerprint: t.option.ClientFingerprint,
|
||||||
|
ECHConfig: t.echConfig,
|
||||||
Headers: http.Header{},
|
Headers: http.Header{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +114,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
|||||||
|
|
||||||
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
|
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
|
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.echConfig, t.realityConfig)
|
||||||
default:
|
default:
|
||||||
// default tcp network
|
// default tcp network
|
||||||
// handle TLS
|
// handle TLS
|
||||||
@ -124,6 +128,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
|||||||
FingerPrint: t.option.Fingerprint,
|
FingerPrint: t.option.Fingerprint,
|
||||||
ClientFingerprint: t.option.ClientFingerprint,
|
ClientFingerprint: t.option.ClientFingerprint,
|
||||||
NextProtos: alpn,
|
NextProtos: alpn,
|
||||||
|
ECH: t.echConfig,
|
||||||
Reality: t.realityConfig,
|
Reality: t.realityConfig,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -321,6 +326,11 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.echConfig, err = option.ECHOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if option.SSOpts.Enabled {
|
if option.SSOpts.Enabled {
|
||||||
if option.SSOpts.Password == "" {
|
if option.SSOpts.Password == "" {
|
||||||
return nil, errors.New("empty password")
|
return nil, errors.New("empty password")
|
||||||
@ -365,7 +375,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.ClientFingerprint, t.realityConfig)
|
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.ClientFingerprint, t.echConfig, t.realityConfig)
|
||||||
|
|
||||||
t.gunTLSConfig = tlsConfig
|
t.gunTLSConfig = tlsConfig
|
||||||
t.gunConfig = &gun.Config{
|
t.gunConfig = &gun.Config{
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/metacubex/mihomo/common/utils"
|
"github.com/metacubex/mihomo/common/utils"
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
"github.com/metacubex/mihomo/component/proxydialer"
|
"github.com/metacubex/mihomo/component/proxydialer"
|
||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
@ -46,6 +47,7 @@ type Vless struct {
|
|||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
realityConfig *tlsC.RealityConfig
|
realityConfig *tlsC.RealityConfig
|
||||||
|
echConfig *ech.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type VlessOption struct {
|
type VlessOption struct {
|
||||||
@ -62,6 +64,7 @@ type VlessOption struct {
|
|||||||
XUDP bool `proxy:"xudp,omitempty"`
|
XUDP bool `proxy:"xudp,omitempty"`
|
||||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
|
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
@ -88,6 +91,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||||
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
ECHConfig: v.echConfig,
|
||||||
Headers: http.Header{},
|
Headers: http.Header{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +155,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
|
|
||||||
c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
|
c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.echConfig, v.realityConfig)
|
||||||
default:
|
default:
|
||||||
// default tcp network
|
// default tcp network
|
||||||
// handle TLS
|
// handle TLS
|
||||||
@ -206,6 +210,7 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
|||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
FingerPrint: v.option.Fingerprint,
|
FingerPrint: v.option.Fingerprint,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
ECH: v.echConfig,
|
||||||
Reality: v.realityConfig,
|
Reality: v.realityConfig,
|
||||||
NextProtos: v.option.ALPN,
|
NextProtos: v.option.ALPN,
|
||||||
}
|
}
|
||||||
@ -563,6 +568,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.echConfig, err = v.option.ECHOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
switch option.Network {
|
switch option.Network {
|
||||||
case "h2":
|
case "h2":
|
||||||
if len(option.HTTP2Opts.Host) == 0 {
|
if len(option.HTTP2Opts.Host) == 0 {
|
||||||
@ -611,7 +621,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
v.gunTLSConfig = tlsConfig
|
v.gunTLSConfig = tlsConfig
|
||||||
v.gunConfig = gunConfig
|
v.gunConfig = gunConfig
|
||||||
|
|
||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.echConfig, v.realityConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/metacubex/mihomo/common/utils"
|
"github.com/metacubex/mihomo/common/utils"
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
"github.com/metacubex/mihomo/component/proxydialer"
|
"github.com/metacubex/mihomo/component/proxydialer"
|
||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
@ -41,6 +42,7 @@ type Vmess struct {
|
|||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
realityConfig *tlsC.RealityConfig
|
realityConfig *tlsC.RealityConfig
|
||||||
|
echConfig *ech.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
@ -58,6 +60,7 @@ type VmessOption struct {
|
|||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
ServerName string `proxy:"servername,omitempty"`
|
ServerName string `proxy:"servername,omitempty"`
|
||||||
|
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
@ -109,6 +112,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||||
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
ECHConfig: v.echConfig,
|
||||||
Headers: http.Header{},
|
Headers: http.Header{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,6 +150,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
ECH: v.echConfig,
|
||||||
Reality: v.realityConfig,
|
Reality: v.realityConfig,
|
||||||
NextProtos: v.option.ALPN,
|
NextProtos: v.option.ALPN,
|
||||||
}
|
}
|
||||||
@ -195,7 +200,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
|
|
||||||
c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts)
|
c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.echConfig, v.realityConfig)
|
||||||
default:
|
default:
|
||||||
// handle TLS
|
// handle TLS
|
||||||
if v.option.TLS {
|
if v.option.TLS {
|
||||||
@ -205,6 +210,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
FingerPrint: v.option.Fingerprint,
|
FingerPrint: v.option.Fingerprint,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
ECH: v.echConfig,
|
||||||
Reality: v.realityConfig,
|
Reality: v.realityConfig,
|
||||||
NextProtos: v.option.ALPN,
|
NextProtos: v.option.ALPN,
|
||||||
}
|
}
|
||||||
@ -474,6 +480,11 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.echConfig, err = v.option.ECHOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
switch option.Network {
|
switch option.Network {
|
||||||
case "h2":
|
case "h2":
|
||||||
if len(option.HTTP2Opts.Host) == 0 {
|
if len(option.HTTP2Opts.Host) == 0 {
|
||||||
@ -522,7 +533,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
v.gunTLSConfig = tlsConfig
|
v.gunTLSConfig = tlsConfig
|
||||||
v.gunConfig = gunConfig
|
v.gunConfig = gunConfig
|
||||||
|
|
||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.echConfig, v.realityConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
|||||||
35
component/ech/ech.go
Normal file
35
component/ech/ech.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package ech
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
EncryptedClientHelloConfigList []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) ClientHandle(ctx context.Context, tlsConfig *tlsC.Config) (err error) {
|
||||||
|
if cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
echConfigList := cfg.EncryptedClientHelloConfigList
|
||||||
|
if len(echConfigList) == 0 {
|
||||||
|
echConfigList, err = resolver.ResolveECH(ctx, tlsConfig.ServerName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("resolve ECH config error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig.EncryptedClientHelloConfigList = echConfigList
|
||||||
|
if tlsConfig.MinVersion != 0 && tlsConfig.MinVersion < tlsC.VersionTLS13 {
|
||||||
|
tlsConfig.MinVersion = tlsC.VersionTLS13
|
||||||
|
}
|
||||||
|
if tlsConfig.MaxVersion != 0 && tlsConfig.MaxVersion < tlsC.VersionTLS13 {
|
||||||
|
tlsConfig.MaxVersion = tlsC.VersionTLS13
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -49,6 +49,7 @@ type Resolver interface {
|
|||||||
LookupIP(ctx context.Context, host string) (ips []netip.Addr, err error)
|
LookupIP(ctx context.Context, host string) (ips []netip.Addr, err error)
|
||||||
LookupIPv4(ctx context.Context, host string) (ips []netip.Addr, err error)
|
LookupIPv4(ctx context.Context, host string) (ips []netip.Addr, err error)
|
||||||
LookupIPv6(ctx context.Context, host string) (ips []netip.Addr, err error)
|
LookupIPv6(ctx context.Context, host string) (ips []netip.Addr, err error)
|
||||||
|
ResolveECH(ctx context.Context, host string) ([]byte, error)
|
||||||
ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error)
|
ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error)
|
||||||
Invalid() bool
|
Invalid() bool
|
||||||
ClearCache()
|
ClearCache()
|
||||||
@ -216,6 +217,17 @@ func ResolveIPPrefer6(ctx context.Context, host string) (netip.Addr, error) {
|
|||||||
return ResolveIPPrefer6WithResolver(ctx, host, DefaultResolver)
|
return ResolveIPPrefer6WithResolver(ctx, host, DefaultResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ResolveECHWithResolver(ctx context.Context, host string, r Resolver) ([]byte, error) {
|
||||||
|
if r != nil && r.Invalid() {
|
||||||
|
return r.ResolveECH(ctx, host)
|
||||||
|
}
|
||||||
|
return SystemResolver.ResolveECH(ctx, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveECH(ctx context.Context, host string) ([]byte, error) {
|
||||||
|
return ResolveECHWithResolver(ctx, host, DefaultResolver)
|
||||||
|
}
|
||||||
|
|
||||||
func ResetConnection() {
|
func ResetConnection() {
|
||||||
if DefaultResolver != nil {
|
if DefaultResolver != nil {
|
||||||
go DefaultResolver.ResetConnection()
|
go DefaultResolver.ResetConnection()
|
||||||
|
|||||||
@ -39,7 +39,7 @@ type RealityConfig struct {
|
|||||||
SupportX25519MLKEM768 bool
|
SupportX25519MLKEM768 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHelloID, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHelloID, tlsConfig *Config, realityConfig *RealityConfig) (net.Conn, error) {
|
||||||
for retry := 0; ; retry++ {
|
for retry := 0; ; retry++ {
|
||||||
verifier := &realityVerifier{
|
verifier := &realityVerifier{
|
||||||
serverName: tlsConfig.ServerName,
|
serverName: tlsConfig.ServerName,
|
||||||
|
|||||||
@ -127,6 +127,28 @@ func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) ResolveECH(ctx context.Context, host string) ([]byte, error) {
|
||||||
|
query := &D.Msg{}
|
||||||
|
query.SetQuestion(D.Fqdn(host), D.TypeHTTPS)
|
||||||
|
|
||||||
|
msg, err := r.ExchangeContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range msg.Answer {
|
||||||
|
switch resource := rr.(type) {
|
||||||
|
case *D.HTTPS:
|
||||||
|
for _, value := range resource.Value {
|
||||||
|
if echConfig, ok := value.(*D.SVCBECHConfig); ok {
|
||||||
|
return echConfig.ECH, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("no ECH config found in DNS records")
|
||||||
|
}
|
||||||
|
|
||||||
// ExchangeContext a batch of dns request with context.Context, and it use cache
|
// ExchangeContext a batch of dns request with context.Context, and it use cache
|
||||||
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
if len(m.Question) == 0 {
|
if len(m.Question) == 0 {
|
||||||
|
|||||||
@ -427,6 +427,10 @@ proxies: # socks5
|
|||||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||||
# 配置指纹将实现 SSL Pining 效果
|
# 配置指纹将实现 SSL Pining 效果
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
|
# ech-opts:
|
||||||
|
# enable: true # 必须手动开启
|
||||||
|
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||||
|
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# host: bing.com
|
# host: bing.com
|
||||||
# path: "/"
|
# path: "/"
|
||||||
@ -527,6 +531,10 @@ proxies: # socks5
|
|||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# servername: example.com # priority over wss host
|
# servername: example.com # priority over wss host
|
||||||
# network: ws
|
# network: ws
|
||||||
|
# ech-opts:
|
||||||
|
# enable: true # 必须手动开启
|
||||||
|
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||||
|
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||||
# ws-opts:
|
# ws-opts:
|
||||||
# path: /path
|
# path: /path
|
||||||
# headers:
|
# headers:
|
||||||
@ -599,6 +607,10 @@ proxies: # socks5
|
|||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||||
|
# ech-opts:
|
||||||
|
# enable: true # 必须手动开启
|
||||||
|
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||||
|
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||||
|
|
||||||
- name: "vless-vision"
|
- name: "vless-vision"
|
||||||
type: vless
|
type: vless
|
||||||
@ -683,6 +695,10 @@ proxies: # socks5
|
|||||||
# enabled: false
|
# enabled: false
|
||||||
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
|
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
|
||||||
# password: "example"
|
# password: "example"
|
||||||
|
# ech-opts:
|
||||||
|
# enable: true # 必须手动开启
|
||||||
|
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||||
|
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||||
|
|
||||||
- name: trojan-grpc
|
- name: trojan-grpc
|
||||||
server: server
|
server: server
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
"github.com/metacubex/mihomo/transport/vmess"
|
"github.com/metacubex/mihomo/transport/vmess"
|
||||||
smux "github.com/metacubex/smux"
|
smux "github.com/metacubex/smux"
|
||||||
)
|
)
|
||||||
@ -18,6 +19,7 @@ type Option struct {
|
|||||||
Path string
|
Path string
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
TLS bool
|
TLS bool
|
||||||
|
ECHConfig *ech.Config
|
||||||
SkipCertVerify bool
|
SkipCertVerify bool
|
||||||
Fingerprint string
|
Fingerprint string
|
||||||
Mux bool
|
Mux bool
|
||||||
@ -51,6 +53,7 @@ func NewGostWebsocket(ctx context.Context, conn net.Conn, option *Option) (net.C
|
|||||||
Host: option.Host,
|
Host: option.Host,
|
||||||
Port: option.Port,
|
Port: option.Port,
|
||||||
Path: option.Path,
|
Path: option.Path,
|
||||||
|
ECHConfig: option.ECHConfig,
|
||||||
Headers: header,
|
Headers: header,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/metacubex/mihomo/common/atomic"
|
"github.com/metacubex/mihomo/common/atomic"
|
||||||
"github.com/metacubex/mihomo/common/buf"
|
"github.com/metacubex/mihomo/common/buf"
|
||||||
"github.com/metacubex/mihomo/common/pool"
|
"github.com/metacubex/mihomo/common/pool"
|
||||||
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
|
||||||
@ -224,7 +225,7 @@ func (g *Conn) SetDeadline(t time.Time) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap {
|
func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint string, echConfig *ech.Config, realityConfig *tlsC.RealityConfig) *TransportWrap {
|
||||||
dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
|
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -238,8 +239,15 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if clientFingerprint, ok := tlsC.GetFingerprint(clientFingerprint); ok {
|
if clientFingerprint, ok := tlsC.GetFingerprint(clientFingerprint); ok {
|
||||||
|
tlsConfig := tlsC.UConfig(tlsConfig)
|
||||||
|
err := echConfig.ClientHandle(ctx, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
pconn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if realityConfig == nil {
|
if realityConfig == nil {
|
||||||
tlsConn := tlsC.UClient(pconn, tlsC.UConfig(cfg), clientFingerprint)
|
tlsConn := tlsC.UClient(pconn, tlsConfig, clientFingerprint)
|
||||||
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||||
pconn.Close()
|
pconn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -251,7 +259,7 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint stri
|
|||||||
}
|
}
|
||||||
return tlsConn, nil
|
return tlsConn, nil
|
||||||
} else {
|
} else {
|
||||||
realityConn, err := tlsC.GetRealityConn(ctx, pconn, clientFingerprint, cfg, realityConfig)
|
realityConn, err := tlsC.GetRealityConn(ctx, pconn, clientFingerprint, tlsConfig, realityConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pconn.Close()
|
pconn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -268,6 +276,27 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint stri
|
|||||||
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if echConfig != nil {
|
||||||
|
tlsConfig := tlsC.UConfig(tlsConfig)
|
||||||
|
err := echConfig.ClientHandle(ctx, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
pconn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := tlsC.Client(pconn, tlsConfig)
|
||||||
|
if err := conn.HandshakeContext(ctx); err != nil {
|
||||||
|
pconn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
state := conn.ConnectionState()
|
||||||
|
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
conn := tls.Client(pconn, cfg)
|
conn := tls.Client(pconn, cfg)
|
||||||
if err := conn.HandshakeContext(ctx); err != nil {
|
if err := conn.HandshakeContext(ctx); err != nil {
|
||||||
pconn.Close()
|
pconn.Close()
|
||||||
@ -345,12 +374,12 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config, realityConfig *tlsC.RealityConfig) (net.Conn, error) {
|
func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config, echConfig *ech.Config, realityConfig *tlsC.RealityConfig) (net.Conn, error) {
|
||||||
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint, realityConfig)
|
transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint, echConfig, realityConfig)
|
||||||
c, err := StreamGunWithTransport(transport, cfg)
|
c, err := StreamGunWithTransport(transport, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
"github.com/metacubex/mihomo/transport/vmess"
|
"github.com/metacubex/mihomo/transport/vmess"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ type Option struct {
|
|||||||
Path string
|
Path string
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
TLS bool
|
TLS bool
|
||||||
|
ECHConfig *ech.Config
|
||||||
SkipCertVerify bool
|
SkipCertVerify bool
|
||||||
Fingerprint string
|
Fingerprint string
|
||||||
Mux bool
|
Mux bool
|
||||||
@ -37,6 +39,7 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn,
|
|||||||
Path: option.Path,
|
Path: option.Path,
|
||||||
V2rayHttpUpgrade: option.V2rayHttpUpgrade,
|
V2rayHttpUpgrade: option.V2rayHttpUpgrade,
|
||||||
V2rayHttpUpgradeFastOpen: option.V2rayHttpUpgradeFastOpen,
|
V2rayHttpUpgradeFastOpen: option.V2rayHttpUpgradeFastOpen,
|
||||||
|
ECHConfig: option.ECHConfig,
|
||||||
Headers: header,
|
Headers: header,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,9 +17,14 @@ type TLSConfig struct {
|
|||||||
FingerPrint string
|
FingerPrint string
|
||||||
ClientFingerprint string
|
ClientFingerprint string
|
||||||
NextProtos []string
|
NextProtos []string
|
||||||
|
ECH *ech.Config
|
||||||
Reality *tlsC.RealityConfig
|
Reality *tlsC.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ECHConfig struct {
|
||||||
|
Enable bool
|
||||||
|
}
|
||||||
|
|
||||||
func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
ServerName: cfg.Host,
|
ServerName: cfg.Host,
|
||||||
@ -33,8 +39,14 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
if clientFingerprint, ok := tlsC.GetFingerprint(cfg.ClientFingerprint); ok {
|
if clientFingerprint, ok := tlsC.GetFingerprint(cfg.ClientFingerprint); ok {
|
||||||
|
tlsConfig := tlsC.UConfig(tlsConfig)
|
||||||
|
err = cfg.ECH.ClientHandle(ctx, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.Reality == nil {
|
if cfg.Reality == nil {
|
||||||
tlsConn := tlsC.UClient(conn, tlsC.UConfig(tlsConfig), clientFingerprint)
|
tlsConn := tlsC.UClient(conn, tlsConfig, clientFingerprint)
|
||||||
err = tlsConn.HandshakeContext(ctx)
|
err = tlsConn.HandshakeContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -48,6 +60,19 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
|
|||||||
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.ECH != nil {
|
||||||
|
tlsConfig := tlsC.UConfig(tlsConfig)
|
||||||
|
err = cfg.ECH.ClientHandle(ctx, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConn := tlsC.Client(conn, tlsConfig)
|
||||||
|
|
||||||
|
err = tlsConn.HandshakeContext(ctx)
|
||||||
|
return tlsConn, err
|
||||||
|
}
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
|
|
||||||
err = tlsConn.HandshakeContext(ctx)
|
err = tlsConn.HandshakeContext(ctx)
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"github.com/metacubex/mihomo/common/buf"
|
"github.com/metacubex/mihomo/common/buf"
|
||||||
N "github.com/metacubex/mihomo/common/net"
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ type WebsocketConfig struct {
|
|||||||
Headers http.Header
|
Headers http.Header
|
||||||
TLS bool
|
TLS bool
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
|
ECHConfig *ech.Config
|
||||||
MaxEarlyData int
|
MaxEarlyData int
|
||||||
EarlyDataHeaderName string
|
EarlyDataHeaderName string
|
||||||
ClientFingerprint string
|
ClientFingerprint string
|
||||||
@ -355,6 +357,11 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if clientFingerprint, ok := tlsC.GetFingerprint(c.ClientFingerprint); ok {
|
if clientFingerprint, ok := tlsC.GetFingerprint(c.ClientFingerprint); ok {
|
||||||
|
tlsConfig := tlsC.UConfig(config)
|
||||||
|
err = c.ECHConfig.ClientHandle(ctx, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
tlsConn := tlsC.UClient(conn, tlsC.UConfig(config), clientFingerprint)
|
tlsConn := tlsC.UClient(conn, tlsC.UConfig(config), clientFingerprint)
|
||||||
if err = tlsC.BuildWebsocketHandshakeState(tlsConn); err != nil {
|
if err = tlsC.BuildWebsocketHandshakeState(tlsConn); err != nil {
|
||||||
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
|
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
|
||||||
@ -364,6 +371,16 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn = tlsConn
|
conn = tlsConn
|
||||||
|
} else if c.ECHConfig != nil {
|
||||||
|
tlsConfig := tlsC.UConfig(config)
|
||||||
|
err = c.ECHConfig.ClientHandle(ctx, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConn := tlsC.Client(conn, tlsConfig)
|
||||||
|
|
||||||
|
err = tlsConn.HandshakeContext(ctx)
|
||||||
|
conn = tlsConn
|
||||||
} else {
|
} else {
|
||||||
tlsConn := tls.Client(conn, config)
|
tlsConn := tls.Client(conn, config)
|
||||||
err = tlsConn.HandshakeContext(ctx)
|
err = tlsConn.HandshakeContext(ctx)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user