mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-02-26 16:57:08 +00:00
feat: add ech-opts for anytls/shadowsocks/trojan/vmess/vless outbound
This commit is contained in:
@@ -28,19 +28,20 @@ type AnyTLS struct {
|
||||
|
||||
type AnyTLSOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
|
||||
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
|
||||
MinIdleSession int `proxy:"min-idle-session,omitempty"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
|
||||
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
|
||||
MinIdleSession int `proxy:"min-idle-session,omitempty"`
|
||||
}
|
||||
|
||||
func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
@@ -115,12 +116,17 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||
IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second,
|
||||
MinIdleSession: option.MinIdleSession,
|
||||
}
|
||||
echConfig, err := option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig := &vmess.TLSConfig{
|
||||
Host: option.SNI,
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
NextProtos: option.ALPN,
|
||||
FingerPrint: option.Fingerprint,
|
||||
ClientFingerprint: option.ClientFingerprint,
|
||||
ECH: echConfig,
|
||||
}
|
||||
if tlsConfig.Host == "" {
|
||||
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"`
|
||||
Path string `obfs:"path,omitempty"`
|
||||
TLS bool `obfs:"tls,omitempty"`
|
||||
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Headers map[string]string `obfs:"headers,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
@@ -77,6 +78,7 @@ type gostObfsOption struct {
|
||||
Host string `obfs:"host,omitempty"`
|
||||
Path string `obfs:"path,omitempty"`
|
||||
TLS bool `obfs:"tls,omitempty"`
|
||||
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Headers map[string]string `obfs:"headers,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
@@ -303,6 +305,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
v2rayOption.TLS = true
|
||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||
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" {
|
||||
opts := gostObfsOption{Host: "bing.com", Mux: true}
|
||||
@@ -325,6 +333,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
gostOption.TLS = true
|
||||
gostOption.SkipCertVerify = opts.SkipCertVerify
|
||||
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 {
|
||||
obfsMode = shadowtls.Mode
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"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"
|
||||
@@ -32,6 +33,7 @@ type Trojan struct {
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
echConfig *ech.Config
|
||||
|
||||
ssCipher core.Cipher
|
||||
}
|
||||
@@ -48,6 +50,7 @@ type TrojanOption struct {
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-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,
|
||||
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||
ClientFingerprint: t.option.ClientFingerprint,
|
||||
ECHConfig: t.echConfig,
|
||||
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)
|
||||
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 tcp network
|
||||
// handle TLS
|
||||
@@ -124,6 +128,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
FingerPrint: t.option.Fingerprint,
|
||||
ClientFingerprint: t.option.ClientFingerprint,
|
||||
NextProtos: alpn,
|
||||
ECH: t.echConfig,
|
||||
Reality: t.realityConfig,
|
||||
})
|
||||
}
|
||||
@@ -321,6 +326,11 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.echConfig, err = option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if option.SSOpts.Enabled {
|
||||
if option.SSOpts.Password == "" {
|
||||
return nil, errors.New("empty password")
|
||||
@@ -365,7 +375,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
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.gunConfig = &gun.Config{
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"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"
|
||||
@@ -46,6 +47,7 @@ type Vless struct {
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
echConfig *ech.Config
|
||||
}
|
||||
|
||||
type VlessOption struct {
|
||||
@@ -62,6 +64,7 @@ type VlessOption struct {
|
||||
XUDP bool `proxy:"xudp,omitempty"`
|
||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-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,
|
||||
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECHConfig: v.echConfig,
|
||||
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)
|
||||
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 tcp network
|
||||
// handle TLS
|
||||
@@ -206,6 +210,7 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
@@ -563,6 +568,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v.echConfig, err = v.option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch option.Network {
|
||||
case "h2":
|
||||
if len(option.HTTP2Opts.Host) == 0 {
|
||||
@@ -611,7 +621,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
v.gunTLSConfig = tlsConfig
|
||||
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
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"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"
|
||||
@@ -41,6 +42,7 @@ type Vmess struct {
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
echConfig *ech.Config
|
||||
}
|
||||
|
||||
type VmessOption struct {
|
||||
@@ -58,6 +60,7 @@ type VmessOption struct {
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-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,
|
||||
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECHConfig: v.echConfig,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
||||
@@ -146,6 +150,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
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)
|
||||
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:
|
||||
// handle 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,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
@@ -474,6 +480,11 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v.echConfig, err = v.option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch option.Network {
|
||||
case "h2":
|
||||
if len(option.HTTP2Opts.Host) == 0 {
|
||||
@@ -522,7 +533,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
v.gunTLSConfig = tlsConfig
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user