mirror of
https://gitclone.com/github.com/MetaCubeX/Clash.Meta
synced 2025-05-24 02:48:02 +08:00
Compare commits
9 Commits
bb8c47d83d
...
303f5e33bf
Author | SHA1 | Date | |
---|---|---|---|
|
303f5e33bf | ||
|
c4d4648e02 | ||
|
e952997711 | ||
|
a596ff1dea | ||
|
188372cb04 | ||
|
a1350d4985 | ||
|
dc958e6a39 | ||
|
8a5f3b8909 | ||
|
c6d7ef8cb8 |
@ -28,19 +28,20 @@ type AnyTLS struct {
|
|||||||
|
|
||||||
type AnyTLSOption struct {
|
type AnyTLSOption struct {
|
||||||
BasicOption
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
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"`
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
|
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
|
||||||
MinIdleSession int `proxy:"min-idle-session,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) {
|
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,
|
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
|
||||||
|
}
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"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"
|
||||||
@ -44,6 +45,9 @@ type Hysteria struct {
|
|||||||
|
|
||||||
option *HysteriaOption
|
option *HysteriaOption
|
||||||
client *core.Client
|
client *core.Client
|
||||||
|
|
||||||
|
tlsConfig *tlsC.Config
|
||||||
|
echConfig *ech.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
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)
|
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
|
||||||
},
|
},
|
||||||
remoteAddr: func(addr string) (net.Addr, error) {
|
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 {
|
type HysteriaOption struct {
|
||||||
BasicOption
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port,omitempty"`
|
Port int `proxy:"port,omitempty"`
|
||||||
Ports string `proxy:"ports,omitempty"`
|
Ports string `proxy:"ports,omitempty"`
|
||||||
Protocol string `proxy:"protocol,omitempty"`
|
Protocol string `proxy:"protocol,omitempty"`
|
||||||
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
|
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
|
||||||
Up string `proxy:"up"`
|
Up string `proxy:"up"`
|
||||||
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
|
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
|
||||||
Down string `proxy:"down"`
|
Down string `proxy:"down"`
|
||||||
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
|
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
|
||||||
Auth string `proxy:"auth,omitempty"`
|
Auth string `proxy:"auth,omitempty"`
|
||||||
AuthString string `proxy:"auth-str,omitempty"`
|
AuthString string `proxy:"auth-str,omitempty"`
|
||||||
Obfs string `proxy:"obfs,omitempty"`
|
Obfs string `proxy:"obfs,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
CustomCA string `proxy:"ca,omitempty"`
|
ALPN []string `proxy:"alpn,omitempty"`
|
||||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
CustomCA string `proxy:"ca,omitempty"`
|
||||||
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||||
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
||||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
||||||
FastOpen bool `proxy:"fast-open,omitempty"`
|
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||||
HopInterval int `proxy:"hop-interval,omitempty"`
|
FastOpen bool `proxy:"fast-open,omitempty"`
|
||||||
|
HopInterval int `proxy:"hop-interval,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
||||||
@ -161,6 +174,13 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
|||||||
} else {
|
} else {
|
||||||
tlsConfig.NextProtos = []string{DefaultALPN}
|
tlsConfig.NextProtos = []string{DefaultALPN}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
echConfig, err := option.ECHOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsClientConfig := tlsC.UConfig(tlsConfig)
|
||||||
|
|
||||||
quicConfig := &quic.Config{
|
quicConfig := &quic.Config{
|
||||||
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
||||||
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
||||||
@ -215,7 +235,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
|||||||
down = uint64(option.DownSpeed * mbpsToBps)
|
down = uint64(option.DownSpeed * mbpsToBps)
|
||||||
}
|
}
|
||||||
client, err := core.NewClient(
|
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))
|
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||||
}, obfuscator, hopInterval, option.FastOpen,
|
}, obfuscator, hopInterval, option.FastOpen,
|
||||||
)
|
)
|
||||||
@ -233,8 +253,10 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
|||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
},
|
},
|
||||||
option: &option,
|
option: &option,
|
||||||
client: client,
|
client: client,
|
||||||
|
tlsConfig: tlsClientConfig,
|
||||||
|
echConfig: echConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
tuicCommon "github.com/metacubex/mihomo/transport/tuic/common"
|
tuicCommon "github.com/metacubex/mihomo/transport/tuic/common"
|
||||||
|
|
||||||
"github.com/metacubex/quic-go"
|
"github.com/metacubex/quic-go"
|
||||||
"github.com/metacubex/randv2"
|
|
||||||
"github.com/metacubex/sing-quic/hysteria2"
|
"github.com/metacubex/sing-quic/hysteria2"
|
||||||
M "github.com/metacubex/sing/common/metadata"
|
M "github.com/metacubex/sing/common/metadata"
|
||||||
)
|
)
|
||||||
@ -42,24 +41,25 @@ type Hysteria2 struct {
|
|||||||
|
|
||||||
type Hysteria2Option struct {
|
type Hysteria2Option struct {
|
||||||
BasicOption
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port,omitempty"`
|
Port int `proxy:"port,omitempty"`
|
||||||
Ports string `proxy:"ports,omitempty"`
|
Ports string `proxy:"ports,omitempty"`
|
||||||
HopInterval int `proxy:"hop-interval,omitempty"`
|
HopInterval int `proxy:"hop-interval,omitempty"`
|
||||||
Up string `proxy:"up,omitempty"`
|
Up string `proxy:"up,omitempty"`
|
||||||
Down string `proxy:"down,omitempty"`
|
Down string `proxy:"down,omitempty"`
|
||||||
Password string `proxy:"password,omitempty"`
|
Password string `proxy:"password,omitempty"`
|
||||||
Obfs string `proxy:"obfs,omitempty"`
|
Obfs string `proxy:"obfs,omitempty"`
|
||||||
ObfsPassword string `proxy:"obfs-password,omitempty"`
|
ObfsPassword string `proxy:"obfs-password,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
CustomCA string `proxy:"ca,omitempty"`
|
ALPN []string `proxy:"alpn,omitempty"`
|
||||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
CustomCA string `proxy:"ca,omitempty"`
|
||||||
CWND int `proxy:"cwnd,omitempty"`
|
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||||
UdpMTU int `proxy:"udp-mtu,omitempty"`
|
CWND int `proxy:"cwnd,omitempty"`
|
||||||
|
UdpMTU int `proxy:"udp-mtu,omitempty"`
|
||||||
|
|
||||||
// quic-go special config
|
// quic-go special config
|
||||||
InitialStreamReceiveWindow uint64 `proxy:"initial-stream-receive-window,omitempty"`
|
InitialStreamReceiveWindow uint64 `proxy:"initial-stream-receive-window,omitempty"`
|
||||||
@ -154,6 +154,12 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
|||||||
tlsConfig.NextProtos = option.ALPN
|
tlsConfig.NextProtos = option.ALPN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tlsClientConfig := tlsC.UConfig(tlsConfig)
|
||||||
|
echConfig, err := option.ECHOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if option.UdpMTU == 0 {
|
if option.UdpMTU == 0 {
|
||||||
// "1200" from quic-go's MaxDatagramSize
|
// "1200" from quic-go's MaxDatagramSize
|
||||||
// "-3" from quic-go's DatagramFrame.MaxDataLen
|
// "-3" from quic-go's DatagramFrame.MaxDataLen
|
||||||
@ -175,41 +181,46 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
|||||||
ReceiveBPS: StringToBps(option.Down),
|
ReceiveBPS: StringToBps(option.Down),
|
||||||
SalamanderPassword: salamanderPassword,
|
SalamanderPassword: salamanderPassword,
|
||||||
Password: option.Password,
|
Password: option.Password,
|
||||||
TLSConfig: tlsC.UConfig(tlsConfig),
|
TLSConfig: tlsClientConfig,
|
||||||
QUICConfig: quicConfig,
|
QUICConfig: quicConfig,
|
||||||
UDPDisabled: false,
|
UDPDisabled: false,
|
||||||
CWND: option.CWND,
|
CWND: option.CWND,
|
||||||
UdpMTU: option.UdpMTU,
|
UdpMTU: option.UdpMTU,
|
||||||
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
|
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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ranges utils.IntRanges[uint16]
|
var ranges utils.IntRanges[uint16]
|
||||||
var serverAddress []string
|
var serverPorts []uint16
|
||||||
if option.Ports != "" {
|
if option.Ports != "" {
|
||||||
ranges, err = utils.NewUnsignedRanges[uint16](option.Ports)
|
ranges, err = utils.NewUnsignedRanges[uint16](option.Ports)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ranges.Range(func(port uint16) bool {
|
ranges.Range(func(port uint16) bool {
|
||||||
serverAddress = append(serverAddress, net.JoinHostPort(option.Server, strconv.Itoa(int(port))))
|
serverPorts = append(serverPorts, port)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
if len(serverAddress) > 0 {
|
if len(serverPorts) > 0 {
|
||||||
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
|
|
||||||
return resolveUDPAddr(ctx, "udp", serverAddress[randv2.IntN(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.HopInterval == 0 {
|
if option.HopInterval == 0 {
|
||||||
option.HopInterval = defaultHopInterval
|
option.HopInterval = defaultHopInterval
|
||||||
} else if option.HopInterval < minHopInterval {
|
} else if option.HopInterval < minHopInterval {
|
||||||
option.HopInterval = minHopInterval
|
option.HopInterval = minHopInterval
|
||||||
}
|
}
|
||||||
clientOptions.HopInterval = time.Duration(option.HopInterval) * time.Second
|
clientOptions.HopInterval = time.Duration(option.HopInterval) * time.Second
|
||||||
|
clientOptions.ServerPorts = serverPorts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if option.Port == 0 && len(serverAddress) == 0 {
|
if option.Port == 0 && len(serverPorts) == 0 {
|
||||||
return nil, errors.New("invalid port")
|
return nil, errors.New("invalid port")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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{
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"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"
|
||||||
@ -28,6 +29,9 @@ type Tuic struct {
|
|||||||
*Base
|
*Base
|
||||||
option *TuicOption
|
option *TuicOption
|
||||||
client *tuic.PoolClient
|
client *tuic.PoolClient
|
||||||
|
|
||||||
|
tlsConfig *tlsC.Config
|
||||||
|
echConfig *ech.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type TuicOption struct {
|
type TuicOption struct {
|
||||||
@ -48,18 +52,19 @@ type TuicOption struct {
|
|||||||
DisableSni bool `proxy:"disable-sni,omitempty"`
|
DisableSni bool `proxy:"disable-sni,omitempty"`
|
||||||
MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"`
|
MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"`
|
||||||
|
|
||||||
FastOpen bool `proxy:"fast-open,omitempty"`
|
FastOpen bool `proxy:"fast-open,omitempty"`
|
||||||
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
|
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
|
||||||
CWND int `proxy:"cwnd,omitempty"`
|
CWND int `proxy:"cwnd,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"`
|
||||||
CustomCA string `proxy:"ca,omitempty"`
|
CustomCA string `proxy:"ca,omitempty"`
|
||||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||||
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
||||||
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
||||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||||
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
|
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
|
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||||
|
|
||||||
UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
|
UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
|
||||||
UDPOverStreamVersion int `proxy:"udp-over-stream-version,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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
err = t.echConfig.ClientHandle(ctx, t.tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
addr = udpAddr
|
addr = udpAddr
|
||||||
var pc net.PacketConn
|
var pc net.PacketConn
|
||||||
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
|
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
|
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 {
|
switch option.UDPOverStreamVersion {
|
||||||
case uot.Version, uot.LegacyVersion:
|
case uot.Version, uot.LegacyVersion:
|
||||||
case 0:
|
case 0:
|
||||||
@ -268,7 +283,9 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
|||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
},
|
},
|
||||||
option: &option,
|
option: &option,
|
||||||
|
tlsConfig: tlsClientConfig,
|
||||||
|
echConfig: echConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
clientMaxOpenStreams := int64(option.MaxOpenStreams)
|
clientMaxOpenStreams := int64(option.MaxOpenStreams)
|
||||||
@ -285,7 +302,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
|||||||
if len(option.Token) > 0 {
|
if len(option.Token) > 0 {
|
||||||
tkn := tuic.GenTKN(option.Token)
|
tkn := tuic.GenTKN(option.Token)
|
||||||
clientOption := &tuic.ClientOptionV4{
|
clientOption := &tuic.ClientOptionV4{
|
||||||
TlsConfig: tlsC.UConfig(tlsConfig),
|
TlsConfig: tlsClientConfig,
|
||||||
QuicConfig: quicConfig,
|
QuicConfig: quicConfig,
|
||||||
Token: tkn,
|
Token: tkn,
|
||||||
UdpRelayMode: udpRelayMode,
|
UdpRelayMode: udpRelayMode,
|
||||||
@ -305,7 +322,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
|||||||
maxUdpRelayPacketSize = tuic.MaxFragSizeV5
|
maxUdpRelayPacketSize = tuic.MaxFragSizeV5
|
||||||
}
|
}
|
||||||
clientOption := &tuic.ClientOptionV5{
|
clientOption := &tuic.ClientOptionV5{
|
||||||
TlsConfig: tlsC.UConfig(tlsConfig),
|
TlsConfig: tlsClientConfig,
|
||||||
QuicConfig: quicConfig,
|
QuicConfig: quicConfig,
|
||||||
Uuid: uuid.FromStringOrNil(option.UUID),
|
Uuid: uuid.FromStringOrNil(option.UUID),
|
||||||
Password: option.Password,
|
Password: option.Password,
|
||||||
|
@ -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
|
||||||
|
}
|
143
component/ech/key.go
Normal file
143
component/ech/key.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package ech
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AEAD_AES_128_GCM = 0x0001
|
||||||
|
AEAD_AES_256_GCM = 0x0002
|
||||||
|
AEAD_ChaCha20Poly1305 = 0x0003
|
||||||
|
)
|
||||||
|
|
||||||
|
const extensionEncryptedClientHello = 0xfe0d
|
||||||
|
const DHKEM_X25519_HKDF_SHA256 = 0x0020
|
||||||
|
const KDF_HKDF_SHA256 = 0x0001
|
||||||
|
|
||||||
|
// sortedSupportedAEADs is just a sorted version of hpke.SupportedAEADS.
|
||||||
|
// We need this so that when we insert them into ECHConfigs the ordering
|
||||||
|
// is stable.
|
||||||
|
var sortedSupportedAEADs = []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305}
|
||||||
|
|
||||||
|
func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) []byte {
|
||||||
|
builder := cryptobyte.NewBuilder(nil)
|
||||||
|
|
||||||
|
builder.AddUint16(extensionEncryptedClientHello)
|
||||||
|
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||||
|
builder.AddUint8(id)
|
||||||
|
|
||||||
|
builder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
|
||||||
|
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||||
|
builder.AddBytes(pubKey)
|
||||||
|
})
|
||||||
|
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||||
|
for _, aeadID := range sortedSupportedAEADs {
|
||||||
|
builder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support
|
||||||
|
builder.AddUint16(aeadID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
builder.AddUint8(maxNameLen)
|
||||||
|
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||||
|
builder.AddBytes([]byte(publicName))
|
||||||
|
})
|
||||||
|
builder.AddUint16(0) // extensions
|
||||||
|
})
|
||||||
|
|
||||||
|
return builder.BytesOrPanic()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenECHConfig(publicName string) (configBase64 string, keyPem string, err error) {
|
||||||
|
echKey, err := ecdh.X25519().GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
echConfig := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)
|
||||||
|
|
||||||
|
builder := cryptobyte.NewBuilder(nil)
|
||||||
|
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||||
|
builder.AddBytes(echConfig)
|
||||||
|
})
|
||||||
|
echConfigList := builder.BytesOrPanic()
|
||||||
|
|
||||||
|
builder2 := cryptobyte.NewBuilder(nil)
|
||||||
|
builder2.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||||
|
builder.AddBytes(echKey.Bytes())
|
||||||
|
})
|
||||||
|
builder2.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||||
|
builder.AddBytes(echConfig)
|
||||||
|
})
|
||||||
|
echConfigKeys := builder2.BytesOrPanic()
|
||||||
|
|
||||||
|
configBase64 = base64.StdEncoding.EncodeToString(echConfigList)
|
||||||
|
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: echConfigKeys}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalECHKeys(raw []byte) ([]tlsC.EncryptedClientHelloKey, error) {
|
||||||
|
var keys []tlsC.EncryptedClientHelloKey
|
||||||
|
rawString := cryptobyte.String(raw)
|
||||||
|
for !rawString.Empty() {
|
||||||
|
var key tlsC.EncryptedClientHelloKey
|
||||||
|
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) {
|
||||||
|
return nil, errors.New("error parsing private key")
|
||||||
|
}
|
||||||
|
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.Config)) {
|
||||||
|
return nil, errors.New("error parsing config")
|
||||||
|
}
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil, errors.New("empty ECH keys")
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadECHKey(key string, tlsConfig *tlsC.Config, path ca.Path) error {
|
||||||
|
if key == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
painTextErr := loadECHKey([]byte(key), tlsConfig)
|
||||||
|
if painTextErr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
key = path.Resolve(key)
|
||||||
|
var loadErr error
|
||||||
|
if !path.IsSafePath(key) {
|
||||||
|
loadErr = path.ErrNotSafePath(key)
|
||||||
|
} else {
|
||||||
|
var echKey []byte
|
||||||
|
echKey, loadErr = os.ReadFile(key)
|
||||||
|
if loadErr == nil {
|
||||||
|
loadErr = loadECHKey(echKey, tlsConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if loadErr != nil {
|
||||||
|
return fmt.Errorf("parse ECH keys failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadECHKey(echKey []byte, tlsConfig *tlsC.Config) error {
|
||||||
|
block, rest := pem.Decode(echKey)
|
||||||
|
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
||||||
|
return errors.New("invalid ECH keys pem")
|
||||||
|
}
|
||||||
|
echKeys, err := UnmarshalECHKeys(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse ECH keys: %w", err)
|
||||||
|
}
|
||||||
|
tlsConfig.EncryptedClientHelloKeys = echKeys
|
||||||
|
return nil
|
||||||
|
}
|
@ -4,12 +4,14 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Main(args []string) {
|
func Main(args []string) {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
panic("Using: generate uuid/reality-keypair/wg-keypair")
|
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair")
|
||||||
}
|
}
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "uuid":
|
case "uuid":
|
||||||
@ -33,5 +35,15 @@ func Main(args []string) {
|
|||||||
}
|
}
|
||||||
fmt.Println("PrivateKey: " + privateKey.String())
|
fmt.Println("PrivateKey: " + privateKey.String())
|
||||||
fmt.Println("PublicKey: " + privateKey.PublicKey().String())
|
fmt.Println("PublicKey: " + privateKey.PublicKey().String())
|
||||||
|
case "ech-keypair":
|
||||||
|
if len(args) < 2 {
|
||||||
|
panic("Using: generate ech-keypair <plain_server_name>")
|
||||||
|
}
|
||||||
|
configBase64, keyPem, err := ech.GenECHConfig(args[1])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Config:", configBase64)
|
||||||
|
fmt.Println("Key:", keyPem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -26,7 +26,6 @@ import (
|
|||||||
utls "github.com/metacubex/utls"
|
utls "github.com/metacubex/utls"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,7 +38,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,
|
||||||
@ -51,6 +50,10 @@ func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHello
|
|||||||
VerifyPeerCertificate: verifier.VerifyPeerCertificate,
|
VerifyPeerCertificate: verifier.VerifyPeerCertificate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !realityConfig.SupportX25519MLKEM768 && fingerprint == HelloChrome_Auto {
|
||||||
|
fingerprint = HelloChrome_120 // old reality server doesn't work with X25519MLKEM768
|
||||||
|
}
|
||||||
|
|
||||||
uConn := utls.UClient(conn, uConfig, fingerprint)
|
uConn := utls.UClient(conn, uConfig, fingerprint)
|
||||||
verifier.UConn = uConn
|
verifier.UConn = uConn
|
||||||
err := uConn.BuildHandshakeState()
|
err := uConn.BuildHandshakeState()
|
||||||
@ -58,29 +61,6 @@ func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHello
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !realityConfig.SupportX25519MLKEM768 {
|
|
||||||
// ------for X25519MLKEM768 does not work properly with the old reality server-------
|
|
||||||
// Iterate over extensions and check
|
|
||||||
for _, extension := range uConn.Extensions {
|
|
||||||
if ce, ok := extension.(*utls.SupportedCurvesExtension); ok {
|
|
||||||
ce.Curves = slices.DeleteFunc(ce.Curves, func(curveID utls.CurveID) bool {
|
|
||||||
return curveID == utls.X25519MLKEM768
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if ks, ok := extension.(*utls.KeyShareExtension); ok {
|
|
||||||
ks.KeyShares = slices.DeleteFunc(ks.KeyShares, func(share utls.KeyShare) bool {
|
|
||||||
return share.Group == utls.X25519MLKEM768
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Rebuild the client hello
|
|
||||||
err = uConn.BuildHandshakeState()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
}
|
|
||||||
|
|
||||||
hello := uConn.HandshakeState.Hello
|
hello := uConn.HandshakeState.Hello
|
||||||
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID
|
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID
|
||||||
for i := range rawSessionID { // https://github.com/golang/go/issues/5373
|
for i := range rawSessionID { // https://github.com/golang/go/issues/5373
|
||||||
|
@ -16,6 +16,7 @@ type Conn = utls.Conn
|
|||||||
type UConn = utls.UConn
|
type UConn = utls.UConn
|
||||||
type UClientHelloID = utls.ClientHelloID
|
type UClientHelloID = utls.ClientHelloID
|
||||||
|
|
||||||
|
const VersionTLS12 = utls.VersionTLS12
|
||||||
const VersionTLS13 = utls.VersionTLS13
|
const VersionTLS13 = utls.VersionTLS13
|
||||||
|
|
||||||
func Client(c net.Conn, config *utls.Config) *Conn {
|
func Client(c net.Conn, config *utls.Config) *Conn {
|
||||||
@ -26,6 +27,14 @@ func UClient(c net.Conn, config *utls.Config, fingerprint UClientHelloID) *UConn
|
|||||||
return utls.UClient(c, config, fingerprint)
|
return utls.UClient(c, config, fingerprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Server(c net.Conn, config *utls.Config) *Conn {
|
||||||
|
return utls.Server(c, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListener(inner net.Listener, config *Config) net.Listener {
|
||||||
|
return utls.NewListener(inner, config)
|
||||||
|
}
|
||||||
|
|
||||||
func GetFingerprint(clientFingerprint string) (UClientHelloID, bool) {
|
func GetFingerprint(clientFingerprint string) (UClientHelloID, bool) {
|
||||||
if len(clientFingerprint) == 0 {
|
if len(clientFingerprint) == 0 {
|
||||||
clientFingerprint = globalFingerprint
|
clientFingerprint = globalFingerprint
|
||||||
@ -65,21 +74,26 @@ var randomFingerprint = once.OnceValue(func() UClientHelloID {
|
|||||||
return fingerprint
|
return fingerprint
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var HelloChrome_Auto = utls.HelloChrome_Auto
|
||||||
|
var HelloChrome_120 = utls.HelloChrome_120 // special fingerprint for some old protocols doesn't work with HelloChrome_Auto
|
||||||
|
|
||||||
var fingerprints = map[string]UClientHelloID{
|
var fingerprints = map[string]UClientHelloID{
|
||||||
"chrome": utls.HelloChrome_Auto,
|
"chrome": utls.HelloChrome_Auto,
|
||||||
|
"firefox": utls.HelloFirefox_Auto,
|
||||||
|
"safari": utls.HelloSafari_Auto,
|
||||||
|
"ios": utls.HelloIOS_Auto,
|
||||||
|
"android": utls.HelloAndroid_11_OkHttp,
|
||||||
|
"edge": utls.HelloEdge_Auto,
|
||||||
|
"360": utls.Hello360_Auto,
|
||||||
|
"qq": utls.HelloQQ_Auto,
|
||||||
|
"random": {},
|
||||||
|
|
||||||
|
// deprecated fingerprints should not be used
|
||||||
"chrome_psk": utls.HelloChrome_100_PSK,
|
"chrome_psk": utls.HelloChrome_100_PSK,
|
||||||
"chrome_psk_shuffle": utls.HelloChrome_106_Shuffle,
|
"chrome_psk_shuffle": utls.HelloChrome_106_Shuffle,
|
||||||
"chrome_padding_psk_shuffle": utls.HelloChrome_114_Padding_PSK_Shuf,
|
"chrome_padding_psk_shuffle": utls.HelloChrome_114_Padding_PSK_Shuf,
|
||||||
"chrome_pq": utls.HelloChrome_115_PQ,
|
"chrome_pq": utls.HelloChrome_115_PQ,
|
||||||
"chrome_pq_psk": utls.HelloChrome_115_PQ_PSK,
|
"chrome_pq_psk": utls.HelloChrome_115_PQ_PSK,
|
||||||
"firefox": utls.HelloFirefox_Auto,
|
|
||||||
"safari": utls.HelloSafari_Auto,
|
|
||||||
"ios": utls.HelloIOS_Auto,
|
|
||||||
"android": utls.HelloAndroid_11_OkHttp,
|
|
||||||
"edge": utls.HelloEdge_Auto,
|
|
||||||
"360": utls.Hello360_Auto,
|
|
||||||
"qq": utls.HelloQQ_Auto,
|
|
||||||
"random": {},
|
|
||||||
"randomized": utls.HelloRandomized,
|
"randomized": utls.HelloRandomized,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +107,9 @@ func init() {
|
|||||||
fingerprints["randomized"] = randomized
|
fingerprints["randomized"] = randomized
|
||||||
}
|
}
|
||||||
|
|
||||||
func UCertificates(it tls.Certificate) utls.Certificate {
|
type Certificate = utls.Certificate
|
||||||
|
|
||||||
|
func UCertificate(it tls.Certificate) utls.Certificate {
|
||||||
return utls.Certificate{
|
return utls.Certificate{
|
||||||
Certificate: it.Certificate,
|
Certificate: it.Certificate,
|
||||||
PrivateKey: it.PrivateKey,
|
PrivateKey: it.PrivateKey,
|
||||||
@ -106,13 +122,15 @@ func UCertificates(it tls.Certificate) utls.Certificate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EncryptedClientHelloKey = utls.EncryptedClientHelloKey
|
||||||
|
|
||||||
type Config = utls.Config
|
type Config = utls.Config
|
||||||
|
|
||||||
func UConfig(config *tls.Config) *utls.Config {
|
func UConfig(config *tls.Config) *utls.Config {
|
||||||
return &utls.Config{
|
return &utls.Config{
|
||||||
Rand: config.Rand,
|
Rand: config.Rand,
|
||||||
Time: config.Time,
|
Time: config.Time,
|
||||||
Certificates: utils.Map(config.Certificates, UCertificates),
|
Certificates: utils.Map(config.Certificates, UCertificate),
|
||||||
VerifyPeerCertificate: config.VerifyPeerCertificate,
|
VerifyPeerCertificate: config.VerifyPeerCertificate,
|
||||||
RootCAs: config.RootCAs,
|
RootCAs: config.RootCAs,
|
||||||
NextProtos: config.NextProtos,
|
NextProtos: config.NextProtos,
|
||||||
|
@ -174,6 +174,7 @@ type Profile struct {
|
|||||||
type TLS struct {
|
type TLS struct {
|
||||||
Certificate string
|
Certificate string
|
||||||
PrivateKey string
|
PrivateKey string
|
||||||
|
EchKey string
|
||||||
CustomTrustCert []string
|
CustomTrustCert []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,6 +361,7 @@ type RawSniffingConfig struct {
|
|||||||
type RawTLS struct {
|
type RawTLS struct {
|
||||||
Certificate string `yaml:"certificate" json:"certificate"`
|
Certificate string `yaml:"certificate" json:"certificate"`
|
||||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||||
|
EchKey string `yaml:"ech-key" json:"ech-key"`
|
||||||
CustomTrustCert []string `yaml:"custom-certifactes" json:"custom-certifactes"`
|
CustomTrustCert []string `yaml:"custom-certifactes" json:"custom-certifactes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -814,6 +816,7 @@ func parseTLS(cfg *RawConfig) (*TLS, error) {
|
|||||||
return &TLS{
|
return &TLS{
|
||||||
Certificate: cfg.TLS.Certificate,
|
Certificate: cfg.TLS.Certificate,
|
||||||
PrivateKey: cfg.TLS.PrivateKey,
|
PrivateKey: cfg.TLS.PrivateKey,
|
||||||
|
EchKey: cfg.TLS.EchKey,
|
||||||
CustomTrustCert: cfg.TLS.CustomTrustCert,
|
CustomTrustCert: cfg.TLS.CustomTrustCert,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -48,6 +48,13 @@ ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS
|
|||||||
tls:
|
tls:
|
||||||
certificate: string # 证书 PEM 格式,或者 证书的路径
|
certificate: string # 证书 PEM 格式,或者 证书的路径
|
||||||
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||||
|
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||||
|
# ech-key: |
|
||||||
|
# -----BEGIN ECH KEYS-----
|
||||||
|
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||||
|
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||||
|
# dC5jb20AAA==
|
||||||
|
# -----END ECH KEYS-----
|
||||||
custom-certifactes:
|
custom-certifactes:
|
||||||
- |
|
- |
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
@ -427,6 +434,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 +538,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 +614,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 +702,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
|
||||||
@ -740,6 +763,10 @@ proxies: # socks5
|
|||||||
up: "30 Mbps" # 若不写单位,默认为 Mbps
|
up: "30 Mbps" # 若不写单位,默认为 Mbps
|
||||||
down: "200 Mbps" # 若不写单位,默认为 Mbps
|
down: "200 Mbps" # 若不写单位,默认为 Mbps
|
||||||
# sni: server.com
|
# 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
|
# skip-cert-verify: false
|
||||||
# recv-window-conn: 12582912
|
# recv-window-conn: 12582912
|
||||||
# recv-window: 52428800
|
# recv-window: 52428800
|
||||||
@ -763,6 +790,10 @@ proxies: # socks5
|
|||||||
# obfs: salamander # 默认为空,如果填写则开启 obfs,目前仅支持 salamander
|
# obfs: salamander # 默认为空,如果填写则开启 obfs,目前仅支持 salamander
|
||||||
# obfs-password: yourpassword
|
# obfs-password: yourpassword
|
||||||
# sni: server.com
|
# 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
|
# skip-cert-verify: false
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# alpn:
|
# alpn:
|
||||||
@ -838,6 +869,10 @@ proxies: # socks5
|
|||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
||||||
# sni: example.com
|
# 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 将失效
|
# meta 和 sing-box 私有扩展,将 ss-uot 用于 udp 中继,开启此选项后 udp-relay-mode 将失效
|
||||||
# 警告,与原版 tuic 不兼容!!!
|
# 警告,与原版 tuic 不兼容!!!
|
||||||
@ -1127,6 +1162,13 @@ listeners:
|
|||||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
|
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||||
|
# ech-key: |
|
||||||
|
# -----BEGIN ECH KEYS-----
|
||||||
|
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||||
|
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||||
|
# dC5jb20AAA==
|
||||||
|
# -----END ECH KEYS-----
|
||||||
|
|
||||||
- name: http-in-1
|
- name: http-in-1
|
||||||
type: http
|
type: http
|
||||||
@ -1140,6 +1182,13 @@ listeners:
|
|||||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
|
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||||
|
# ech-key: |
|
||||||
|
# -----BEGIN ECH KEYS-----
|
||||||
|
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||||
|
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||||
|
# dC5jb20AAA==
|
||||||
|
# -----END ECH KEYS-----
|
||||||
|
|
||||||
- name: mixed-in-1
|
- name: mixed-in-1
|
||||||
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
||||||
@ -1154,6 +1203,13 @@ listeners:
|
|||||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
|
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||||
|
# ech-key: |
|
||||||
|
# -----BEGIN ECH KEYS-----
|
||||||
|
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||||
|
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||||
|
# dC5jb20AAA==
|
||||||
|
# -----END ECH KEYS-----
|
||||||
|
|
||||||
- name: reidr-in-1
|
- name: reidr-in-1
|
||||||
type: redir
|
type: redir
|
||||||
@ -1203,6 +1259,13 @@ listeners:
|
|||||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
|
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||||
|
# ech-key: |
|
||||||
|
# -----BEGIN ECH KEYS-----
|
||||||
|
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||||
|
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||||
|
# dC5jb20AAA==
|
||||||
|
# -----END ECH KEYS-----
|
||||||
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
||||||
# reality-config:
|
# reality-config:
|
||||||
# dest: test.com:443
|
# dest: test.com:443
|
||||||
@ -1225,6 +1288,13 @@ listeners:
|
|||||||
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
|
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||||
|
# ech-key: |
|
||||||
|
# -----BEGIN ECH KEYS-----
|
||||||
|
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||||
|
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||||
|
# dC5jb20AAA==
|
||||||
|
# -----END ECH KEYS-----
|
||||||
# congestion-controller: bbr
|
# congestion-controller: bbr
|
||||||
# max-idle-time: 15000
|
# max-idle-time: 15000
|
||||||
# authentication-timeout: 1000
|
# authentication-timeout: 1000
|
||||||
@ -1256,6 +1326,13 @@ listeners:
|
|||||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
|
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||||
|
# ech-key: |
|
||||||
|
# -----BEGIN ECH KEYS-----
|
||||||
|
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||||
|
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||||
|
# dC5jb20AAA==
|
||||||
|
# -----END ECH KEYS-----
|
||||||
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
||||||
reality-config:
|
reality-config:
|
||||||
dest: test.com:443
|
dest: test.com:443
|
||||||
@ -1276,6 +1353,13 @@ listeners:
|
|||||||
# "certificate" and "private-key" are required
|
# "certificate" and "private-key" are required
|
||||||
certificate: ./server.crt
|
certificate: ./server.crt
|
||||||
private-key: ./server.key
|
private-key: ./server.key
|
||||||
|
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||||
|
# ech-key: |
|
||||||
|
# -----BEGIN ECH KEYS-----
|
||||||
|
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||||
|
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||||
|
# dC5jb20AAA==
|
||||||
|
# -----END ECH KEYS-----
|
||||||
# padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme
|
# padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme
|
||||||
|
|
||||||
- name: trojan-in-1
|
- name: trojan-in-1
|
||||||
@ -1292,6 +1376,13 @@ listeners:
|
|||||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
certificate: ./server.crt
|
certificate: ./server.crt
|
||||||
private-key: ./server.key
|
private-key: ./server.key
|
||||||
|
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||||
|
# ech-key: |
|
||||||
|
# -----BEGIN ECH KEYS-----
|
||||||
|
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||||
|
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||||
|
# dC5jb20AAA==
|
||||||
|
# -----END ECH KEYS-----
|
||||||
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
||||||
# reality-config:
|
# reality-config:
|
||||||
# dest: test.com:443
|
# dest: test.com:443
|
||||||
@ -1317,6 +1408,13 @@ listeners:
|
|||||||
00000000-0000-0000-0000-000000000001: PASSWORD_1
|
00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
|
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||||
|
# ech-key: |
|
||||||
|
# -----BEGIN ECH KEYS-----
|
||||||
|
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||||
|
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||||
|
# dC5jb20AAA==
|
||||||
|
# -----END ECH KEYS-----
|
||||||
## up 和 down 均不写或为 0 则使用 BBR 流控
|
## up 和 down 均不写或为 0 则使用 BBR 流控
|
||||||
# up: "30 Mbps" # 若不写单位,默认为 Mbps
|
# up: "30 Mbps" # 若不写单位,默认为 Mbps
|
||||||
# down: "200 Mbps" # 若不写单位,默认为 Mbps
|
# down: "200 Mbps" # 若不写单位,默认为 Mbps
|
||||||
|
2
go.mod
2
go.mod
@ -27,7 +27,7 @@ require (
|
|||||||
github.com/metacubex/randv2 v0.2.0
|
github.com/metacubex/randv2 v0.2.0
|
||||||
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7
|
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7
|
||||||
github.com/metacubex/sing-mux v0.3.2
|
github.com/metacubex/sing-mux v0.3.2
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2
|
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.9
|
github.com/metacubex/sing-shadowsocks v0.2.9
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.3
|
github.com/metacubex/sing-shadowsocks2 v0.2.3
|
||||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||||
|
2
go.sum
2
go.sum
@ -122,6 +122,8 @@ github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7
|
|||||||
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2 h1:wfmYgtECbEYo1slMtyo+2kMqscYYDSjU/TVgS3018F4=
|
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2 h1:wfmYgtECbEYo1slMtyo+2kMqscYYDSjU/TVgS3018F4=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2/go.mod h1:P1kd57U6XXmXv9PbwWdznUGT0k9bKgFJXF0fEORbIlk=
|
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2/go.mod h1:P1kd57U6XXmXv9PbwWdznUGT0k9bKgFJXF0fEORbIlk=
|
||||||
|
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336 h1:5BgpaFkTzkePwF1A8rmhCqgyOMG79BLsAhFR8W8SiRo=
|
||||||
|
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
|
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
|
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=
|
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=
|
||||||
|
@ -57,6 +57,7 @@ func applyRoute(cfg *config.Config) {
|
|||||||
Secret: cfg.Controller.Secret,
|
Secret: cfg.Controller.Secret,
|
||||||
Certificate: cfg.TLS.Certificate,
|
Certificate: cfg.TLS.Certificate,
|
||||||
PrivateKey: cfg.TLS.PrivateKey,
|
PrivateKey: cfg.TLS.PrivateKey,
|
||||||
|
EchKey: cfg.TLS.EchKey,
|
||||||
DohServer: cfg.Controller.ExternalDohServer,
|
DohServer: cfg.Controller.ExternalDohServer,
|
||||||
IsDebug: cfg.General.LogLevel == log.DEBUG,
|
IsDebug: cfg.General.LogLevel == log.DEBUG,
|
||||||
Cors: route.Cors{
|
Cors: route.Cors{
|
||||||
|
@ -3,7 +3,6 @@ package route
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -17,6 +16,8 @@ import (
|
|||||||
"github.com/metacubex/mihomo/adapter/inbound"
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
"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/ech"
|
||||||
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||||
@ -27,6 +28,8 @@ import (
|
|||||||
"github.com/gobwas/ws"
|
"github.com/gobwas/ws"
|
||||||
"github.com/gobwas/ws/wsutil"
|
"github.com/gobwas/ws/wsutil"
|
||||||
"github.com/sagernet/cors"
|
"github.com/sagernet/cors"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -62,6 +65,7 @@ type Config struct {
|
|||||||
Secret string
|
Secret string
|
||||||
Certificate string
|
Certificate string
|
||||||
PrivateKey string
|
PrivateKey string
|
||||||
|
EchKey string
|
||||||
DohServer string
|
DohServer string
|
||||||
IsDebug bool
|
IsDebug bool
|
||||||
Cors Cors
|
Cors Cors
|
||||||
@ -95,7 +99,7 @@ func SetUIPath(path string) {
|
|||||||
uiPath = C.Path.Resolve(path)
|
uiPath = C.Path.Resolve(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func router(isDebug bool, secret string, dohServer string, cors Cors) *chi.Mux {
|
func router(isDebug bool, secret string, dohServer string, cors Cors) http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
cors.Apply(r)
|
cors.Apply(r)
|
||||||
if isDebug {
|
if isDebug {
|
||||||
@ -148,7 +152,8 @@ func router(isDebug bool, secret string, dohServer string, cors Cors) *chi.Mux {
|
|||||||
r.Mount(dohServer, dohRouter())
|
r.Mount(dohServer, dohRouter())
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
// using h2c.NewHandler to ensure we can work in plain http2, and some tls conn is not *tls.Conn
|
||||||
|
return h2c.NewHandler(r, &http2.Server{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(cfg *Config) {
|
func start(cfg *Config) {
|
||||||
@ -186,7 +191,7 @@ func startTLS(cfg *Config) {
|
|||||||
|
|
||||||
// handle tlsAddr
|
// handle tlsAddr
|
||||||
if len(cfg.TLSAddr) > 0 {
|
if len(cfg.TLSAddr) > 0 {
|
||||||
c, err := ca.LoadTLSKeyPair(cfg.Certificate, cfg.PrivateKey, C.Path)
|
cert, err := ca.LoadTLSKeyPair(cfg.Certificate, cfg.PrivateKey, C.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("External controller tls listen error: %s", err)
|
log.Errorln("External controller tls listen error: %s", err)
|
||||||
return
|
return
|
||||||
@ -199,14 +204,22 @@ func startTLS(cfg *Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Infoln("RESTful API tls listening at: %s", l.Addr().String())
|
log.Infoln("RESTful API tls listening at: %s", l.Addr().String())
|
||||||
|
tlsConfig := &tlsC.Config{}
|
||||||
|
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
|
||||||
|
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||||
|
|
||||||
|
if cfg.EchKey != "" {
|
||||||
|
err = ech.LoadECHKey(cfg.EchKey, tlsConfig, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("External controller tls serve error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors),
|
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors),
|
||||||
TLSConfig: &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{c},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
tlsServer = server
|
tlsServer = server
|
||||||
if err = server.ServeTLS(l, "", ""); err != nil {
|
if err = server.Serve(tlsC.NewListener(l, tlsConfig)); err != nil {
|
||||||
log.Errorln("External controller tls serve error: %s", err)
|
log.Errorln("External controller tls serve error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package anytls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
@ -13,6 +12,8 @@ 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/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
"github.com/metacubex/mihomo/listener/sing"
|
"github.com/metacubex/mihomo/listener/sing"
|
||||||
@ -28,7 +29,7 @@ type Listener struct {
|
|||||||
closed bool
|
closed bool
|
||||||
config LC.AnyTLSServer
|
config LC.AnyTLSServer
|
||||||
listeners []net.Listener
|
listeners []net.Listener
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tlsC.Config
|
||||||
userMap map[[32]byte]string
|
userMap map[[32]byte]string
|
||||||
padding atomic.TypedValue[*padding.PaddingFactory]
|
padding atomic.TypedValue[*padding.PaddingFactory]
|
||||||
}
|
}
|
||||||
@ -41,13 +42,20 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := &tls.Config{}
|
tlsConfig := &tlsC.Config{}
|
||||||
if config.Certificate != "" && config.PrivateKey != "" {
|
if config.Certificate != "" && config.PrivateKey != "" {
|
||||||
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
|
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||||
|
|
||||||
|
if config.EchKey != "" {
|
||||||
|
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sl = &Listener{
|
sl = &Listener{
|
||||||
@ -87,7 +95,7 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(tlsConfig.Certificates) > 0 {
|
if len(tlsConfig.Certificates) > 0 {
|
||||||
l = tls.NewListener(l, tlsConfig)
|
l = tlsC.NewListener(l, tlsConfig)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("disallow using AnyTLS without certificates config")
|
return nil, errors.New("disallow using AnyTLS without certificates config")
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ type AnyTLSServer struct {
|
|||||||
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||||
Certificate string `yaml:"certificate" json:"certificate"`
|
Certificate string `yaml:"certificate" json:"certificate"`
|
||||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||||
|
EchKey string `yaml:"ech-key" json:"ech-key"`
|
||||||
PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"`
|
PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,5 +12,6 @@ type AuthServer struct {
|
|||||||
AuthStore auth.AuthStore
|
AuthStore auth.AuthStore
|
||||||
Certificate string
|
Certificate string
|
||||||
PrivateKey string
|
PrivateKey string
|
||||||
|
EchKey string
|
||||||
RealityConfig reality.Config
|
RealityConfig reality.Config
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ type Hysteria2Server struct {
|
|||||||
ObfsPassword string `yaml:"obfs-password" json:"obfs-password,omitempty"`
|
ObfsPassword string `yaml:"obfs-password" json:"obfs-password,omitempty"`
|
||||||
Certificate string `yaml:"certificate" json:"certificate"`
|
Certificate string `yaml:"certificate" json:"certificate"`
|
||||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||||
|
EchKey string `yaml:"ech-key" json:"ech-key,omitempty"`
|
||||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||||
Up string `yaml:"up" json:"up,omitempty"`
|
Up string `yaml:"up" json:"up,omitempty"`
|
||||||
|
@ -20,6 +20,7 @@ type TrojanServer struct {
|
|||||||
GrpcServiceName string
|
GrpcServiceName string
|
||||||
Certificate string
|
Certificate string
|
||||||
PrivateKey string
|
PrivateKey string
|
||||||
|
EchKey string
|
||||||
RealityConfig reality.Config
|
RealityConfig reality.Config
|
||||||
MuxOption sing.MuxOption
|
MuxOption sing.MuxOption
|
||||||
TrojanSSOption TrojanSSOption
|
TrojanSSOption TrojanSSOption
|
||||||
|
@ -13,6 +13,7 @@ type TuicServer struct {
|
|||||||
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||||
Certificate string `yaml:"certificate" json:"certificate"`
|
Certificate string `yaml:"certificate" json:"certificate"`
|
||||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||||
|
EchKey string `yaml:"ech-key" json:"ech-key"`
|
||||||
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||||
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||||
|
@ -21,6 +21,7 @@ type VlessServer struct {
|
|||||||
GrpcServiceName string
|
GrpcServiceName string
|
||||||
Certificate string
|
Certificate string
|
||||||
PrivateKey string
|
PrivateKey string
|
||||||
|
EchKey string
|
||||||
RealityConfig reality.Config
|
RealityConfig reality.Config
|
||||||
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ type VmessServer struct {
|
|||||||
GrpcServiceName string
|
GrpcServiceName string
|
||||||
Certificate string
|
Certificate string
|
||||||
PrivateKey string
|
PrivateKey string
|
||||||
|
EchKey string
|
||||||
RealityConfig reality.Config
|
RealityConfig reality.Config
|
||||||
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter/inbound"
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
"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"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
authStore "github.com/metacubex/mihomo/listener/auth"
|
authStore "github.com/metacubex/mihomo/listener/auth"
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
@ -64,7 +65,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := &tls.Config{}
|
tlsConfig := &tlsC.Config{}
|
||||||
var realityBuilder *reality.Builder
|
var realityBuilder *reality.Builder
|
||||||
|
|
||||||
if config.Certificate != "" && config.PrivateKey != "" {
|
if config.Certificate != "" && config.PrivateKey != "" {
|
||||||
@ -72,7 +73,14 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||||
|
|
||||||
|
if config.EchKey != "" {
|
||||||
|
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if config.RealityConfig.PrivateKey != "" {
|
if config.RealityConfig.PrivateKey != "" {
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
@ -87,7 +95,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
|||||||
if realityBuilder != nil {
|
if realityBuilder != nil {
|
||||||
l = realityBuilder.NewListener(l)
|
l = realityBuilder.NewListener(l)
|
||||||
} else if len(tlsConfig.Certificates) > 0 {
|
} else if len(tlsConfig.Certificates) > 0 {
|
||||||
l = tls.NewListener(l, tlsConfig)
|
l = tlsC.NewListener(l, tlsConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
hl := &Listener{
|
hl := &Listener{
|
||||||
|
@ -14,6 +14,7 @@ type AnyTLSOption struct {
|
|||||||
Users map[string]string `inbound:"users,omitempty"`
|
Users map[string]string `inbound:"users,omitempty"`
|
||||||
Certificate string `inbound:"certificate"`
|
Certificate string `inbound:"certificate"`
|
||||||
PrivateKey string `inbound:"private-key"`
|
PrivateKey string `inbound:"private-key"`
|
||||||
|
EchKey string `inbound:"ech-key,omitempty"`
|
||||||
PaddingScheme string `inbound:"padding-scheme,omitempty"`
|
PaddingScheme string `inbound:"padding-scheme,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ func NewAnyTLS(options *AnyTLSOption) (*AnyTLS, error) {
|
|||||||
Users: options.Users,
|
Users: options.Users,
|
||||||
Certificate: options.Certificate,
|
Certificate: options.Certificate,
|
||||||
PrivateKey: options.PrivateKey,
|
PrivateKey: options.PrivateKey,
|
||||||
|
EchKey: options.EchKey,
|
||||||
PaddingScheme: options.PaddingScheme,
|
PaddingScheme: options.PaddingScheme,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -60,4 +60,14 @@ func TestInboundAnyTLS_TLS(t *testing.T) {
|
|||||||
Fingerprint: tlsFingerprint,
|
Fingerprint: tlsFingerprint,
|
||||||
}
|
}
|
||||||
testInboundAnyTLS(t, inboundOptions, outboundOptions)
|
testInboundAnyTLS(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundAnyTLS(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,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/generater"
|
"github.com/metacubex/mihomo/component/generater"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
|
||||||
@ -38,6 +39,8 @@ var realityPrivateKey, realityPublickey string
|
|||||||
var realityDest = "itunes.apple.com"
|
var realityDest = "itunes.apple.com"
|
||||||
var realityShortid = "10f897e26c4b9478"
|
var realityShortid = "10f897e26c4b9478"
|
||||||
var realityRealDial = false
|
var realityRealDial = false
|
||||||
|
var echPublicSni = "public.sni"
|
||||||
|
var echConfigBase64, echKeyPem, _ = ech.GenECHConfig(echPublicSni)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rand.Read(httpData)
|
rand.Read(httpData)
|
||||||
|
@ -16,6 +16,7 @@ type HTTPOption struct {
|
|||||||
Users AuthUsers `inbound:"users,omitempty"`
|
Users AuthUsers `inbound:"users,omitempty"`
|
||||||
Certificate string `inbound:"certificate,omitempty"`
|
Certificate string `inbound:"certificate,omitempty"`
|
||||||
PrivateKey string `inbound:"private-key,omitempty"`
|
PrivateKey string `inbound:"private-key,omitempty"`
|
||||||
|
EchKey string `inbound:"ech-key,omitempty"`
|
||||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ func (h *HTTP) Listen(tunnel C.Tunnel) error {
|
|||||||
AuthStore: h.config.Users.GetAuthStore(),
|
AuthStore: h.config.Users.GetAuthStore(),
|
||||||
Certificate: h.config.Certificate,
|
Certificate: h.config.Certificate,
|
||||||
PrivateKey: h.config.PrivateKey,
|
PrivateKey: h.config.PrivateKey,
|
||||||
|
EchKey: h.config.EchKey,
|
||||||
RealityConfig: h.config.RealityConfig.Build(),
|
RealityConfig: h.config.RealityConfig.Build(),
|
||||||
},
|
},
|
||||||
tunnel,
|
tunnel,
|
||||||
|
@ -16,6 +16,7 @@ type Hysteria2Option struct {
|
|||||||
ObfsPassword string `inbound:"obfs-password,omitempty"`
|
ObfsPassword string `inbound:"obfs-password,omitempty"`
|
||||||
Certificate string `inbound:"certificate"`
|
Certificate string `inbound:"certificate"`
|
||||||
PrivateKey string `inbound:"private-key"`
|
PrivateKey string `inbound:"private-key"`
|
||||||
|
EchKey string `inbound:"ech-key,omitempty"`
|
||||||
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
|
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
|
||||||
ALPN []string `inbound:"alpn,omitempty"`
|
ALPN []string `inbound:"alpn,omitempty"`
|
||||||
Up string `inbound:"up,omitempty"`
|
Up string `inbound:"up,omitempty"`
|
||||||
@ -60,6 +61,7 @@ func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) {
|
|||||||
ObfsPassword: options.ObfsPassword,
|
ObfsPassword: options.ObfsPassword,
|
||||||
Certificate: options.Certificate,
|
Certificate: options.Certificate,
|
||||||
PrivateKey: options.PrivateKey,
|
PrivateKey: options.PrivateKey,
|
||||||
|
EchKey: options.EchKey,
|
||||||
MaxIdleTime: options.MaxIdleTime,
|
MaxIdleTime: options.MaxIdleTime,
|
||||||
ALPN: options.ALPN,
|
ALPN: options.ALPN,
|
||||||
Up: options.Up,
|
Up: options.Up,
|
||||||
|
@ -60,6 +60,16 @@ func TestInboundHysteria2_TLS(t *testing.T) {
|
|||||||
Fingerprint: tlsFingerprint,
|
Fingerprint: tlsFingerprint,
|
||||||
}
|
}
|
||||||
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundHysteria2_Salamander(t *testing.T) {
|
func TestInboundHysteria2_Salamander(t *testing.T) {
|
||||||
@ -75,6 +85,16 @@ func TestInboundHysteria2_Salamander(t *testing.T) {
|
|||||||
ObfsPassword: userUUID,
|
ObfsPassword: userUUID,
|
||||||
}
|
}
|
||||||
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundHysteria2_Brutal(t *testing.T) {
|
func TestInboundHysteria2_Brutal(t *testing.T) {
|
||||||
@ -90,4 +110,14 @@ func TestInboundHysteria2_Brutal(t *testing.T) {
|
|||||||
Down: "200 Mbps",
|
Down: "200 Mbps",
|
||||||
}
|
}
|
||||||
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ type MixedOption struct {
|
|||||||
UDP bool `inbound:"udp,omitempty"`
|
UDP bool `inbound:"udp,omitempty"`
|
||||||
Certificate string `inbound:"certificate,omitempty"`
|
Certificate string `inbound:"certificate,omitempty"`
|
||||||
PrivateKey string `inbound:"private-key,omitempty"`
|
PrivateKey string `inbound:"private-key,omitempty"`
|
||||||
|
EchKey string `inbound:"ech-key,omitempty"`
|
||||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +70,7 @@ func (m *Mixed) Listen(tunnel C.Tunnel) error {
|
|||||||
AuthStore: m.config.Users.GetAuthStore(),
|
AuthStore: m.config.Users.GetAuthStore(),
|
||||||
Certificate: m.config.Certificate,
|
Certificate: m.config.Certificate,
|
||||||
PrivateKey: m.config.PrivateKey,
|
PrivateKey: m.config.PrivateKey,
|
||||||
|
EchKey: m.config.EchKey,
|
||||||
RealityConfig: m.config.RealityConfig.Build(),
|
RealityConfig: m.config.RealityConfig.Build(),
|
||||||
},
|
},
|
||||||
tunnel,
|
tunnel,
|
||||||
|
@ -17,6 +17,7 @@ type SocksOption struct {
|
|||||||
UDP bool `inbound:"udp,omitempty"`
|
UDP bool `inbound:"udp,omitempty"`
|
||||||
Certificate string `inbound:"certificate,omitempty"`
|
Certificate string `inbound:"certificate,omitempty"`
|
||||||
PrivateKey string `inbound:"private-key,omitempty"`
|
PrivateKey string `inbound:"private-key,omitempty"`
|
||||||
|
EchKey string `inbound:"ech-key,omitempty"`
|
||||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,6 +90,7 @@ func (s *Socks) Listen(tunnel C.Tunnel) error {
|
|||||||
AuthStore: s.config.Users.GetAuthStore(),
|
AuthStore: s.config.Users.GetAuthStore(),
|
||||||
Certificate: s.config.Certificate,
|
Certificate: s.config.Certificate,
|
||||||
PrivateKey: s.config.PrivateKey,
|
PrivateKey: s.config.PrivateKey,
|
||||||
|
EchKey: s.config.EchKey,
|
||||||
RealityConfig: s.config.RealityConfig.Build(),
|
RealityConfig: s.config.RealityConfig.Build(),
|
||||||
},
|
},
|
||||||
tunnel,
|
tunnel,
|
||||||
|
@ -16,6 +16,7 @@ type TrojanOption struct {
|
|||||||
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
||||||
Certificate string `inbound:"certificate,omitempty"`
|
Certificate string `inbound:"certificate,omitempty"`
|
||||||
PrivateKey string `inbound:"private-key,omitempty"`
|
PrivateKey string `inbound:"private-key,omitempty"`
|
||||||
|
EchKey string `inbound:"ech-key,omitempty"`
|
||||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||||
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
||||||
SSOption TrojanSSOption `inbound:"ss-option,omitempty"`
|
SSOption TrojanSSOption `inbound:"ss-option,omitempty"`
|
||||||
@ -67,6 +68,7 @@ func NewTrojan(options *TrojanOption) (*Trojan, error) {
|
|||||||
GrpcServiceName: options.GrpcServiceName,
|
GrpcServiceName: options.GrpcServiceName,
|
||||||
Certificate: options.Certificate,
|
Certificate: options.Certificate,
|
||||||
PrivateKey: options.PrivateKey,
|
PrivateKey: options.PrivateKey,
|
||||||
|
EchKey: options.EchKey,
|
||||||
RealityConfig: options.RealityConfig.Build(),
|
RealityConfig: options.RealityConfig.Build(),
|
||||||
MuxOption: options.MuxOption.Build(),
|
MuxOption: options.MuxOption.Build(),
|
||||||
TrojanSSOption: LC.TrojanSSOption{
|
TrojanSSOption: LC.TrojanSSOption{
|
||||||
|
@ -64,6 +64,16 @@ func TestInboundTrojan_TLS(t *testing.T) {
|
|||||||
Fingerprint: tlsFingerprint,
|
Fingerprint: tlsFingerprint,
|
||||||
}
|
}
|
||||||
testInboundTrojan(t, inboundOptions, outboundOptions)
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundTrojan_Wss1(t *testing.T) {
|
func TestInboundTrojan_Wss1(t *testing.T) {
|
||||||
@ -80,6 +90,16 @@ func TestInboundTrojan_Wss1(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
testInboundTrojan(t, inboundOptions, outboundOptions)
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundTrojan_Wss2(t *testing.T) {
|
func TestInboundTrojan_Wss2(t *testing.T) {
|
||||||
@ -97,6 +117,16 @@ func TestInboundTrojan_Wss2(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
testInboundTrojan(t, inboundOptions, outboundOptions)
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundTrojan_Grpc1(t *testing.T) {
|
func TestInboundTrojan_Grpc1(t *testing.T) {
|
||||||
@ -111,6 +141,16 @@ func TestInboundTrojan_Grpc1(t *testing.T) {
|
|||||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
}
|
}
|
||||||
testInboundTrojan(t, inboundOptions, outboundOptions)
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundTrojan_Grpc2(t *testing.T) {
|
func TestInboundTrojan_Grpc2(t *testing.T) {
|
||||||
@ -126,6 +166,16 @@ func TestInboundTrojan_Grpc2(t *testing.T) {
|
|||||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
}
|
}
|
||||||
testInboundTrojan(t, inboundOptions, outboundOptions)
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundTrojan_Reality(t *testing.T) {
|
func TestInboundTrojan_Reality(t *testing.T) {
|
||||||
@ -190,6 +240,16 @@ func TestInboundTrojan_TLS_TrojanSS(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
testInboundTrojan(t, inboundOptions, outboundOptions)
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundTrojan_Wss_TrojanSS(t *testing.T) {
|
func TestInboundTrojan_Wss_TrojanSS(t *testing.T) {
|
||||||
@ -216,4 +276,14 @@ func TestInboundTrojan_Wss_TrojanSS(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
testInboundTrojan(t, inboundOptions, outboundOptions)
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ type TuicOption struct {
|
|||||||
Users map[string]string `inbound:"users,omitempty"`
|
Users map[string]string `inbound:"users,omitempty"`
|
||||||
Certificate string `inbound:"certificate"`
|
Certificate string `inbound:"certificate"`
|
||||||
PrivateKey string `inbound:"private-key"`
|
PrivateKey string `inbound:"private-key"`
|
||||||
|
EchKey string `inbound:"ech-key,omitempty"`
|
||||||
CongestionController string `inbound:"congestion-controller,omitempty"`
|
CongestionController string `inbound:"congestion-controller,omitempty"`
|
||||||
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
|
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
|
||||||
AuthenticationTimeout int `inbound:"authentication-timeout,omitempty"`
|
AuthenticationTimeout int `inbound:"authentication-timeout,omitempty"`
|
||||||
@ -50,6 +51,7 @@ func NewTuic(options *TuicOption) (*Tuic, error) {
|
|||||||
Users: options.Users,
|
Users: options.Users,
|
||||||
Certificate: options.Certificate,
|
Certificate: options.Certificate,
|
||||||
PrivateKey: options.PrivateKey,
|
PrivateKey: options.PrivateKey,
|
||||||
|
EchKey: options.EchKey,
|
||||||
CongestionController: options.CongestionController,
|
CongestionController: options.CongestionController,
|
||||||
MaxIdleTime: options.MaxIdleTime,
|
MaxIdleTime: options.MaxIdleTime,
|
||||||
AuthenticationTimeout: options.AuthenticationTimeout,
|
AuthenticationTimeout: options.AuthenticationTimeout,
|
||||||
|
@ -89,4 +89,14 @@ func TestInboundTuic_TLS(t *testing.T) {
|
|||||||
Fingerprint: tlsFingerprint,
|
Fingerprint: tlsFingerprint,
|
||||||
}
|
}
|
||||||
testInboundTuic(t, inboundOptions, outboundOptions)
|
testInboundTuic(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundTuic(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ type VlessOption struct {
|
|||||||
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
||||||
Certificate string `inbound:"certificate,omitempty"`
|
Certificate string `inbound:"certificate,omitempty"`
|
||||||
PrivateKey string `inbound:"private-key,omitempty"`
|
PrivateKey string `inbound:"private-key,omitempty"`
|
||||||
|
EchKey string `inbound:"ech-key,omitempty"`
|
||||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||||
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
||||||
}
|
}
|
||||||
@ -61,6 +62,7 @@ func NewVless(options *VlessOption) (*Vless, error) {
|
|||||||
GrpcServiceName: options.GrpcServiceName,
|
GrpcServiceName: options.GrpcServiceName,
|
||||||
Certificate: options.Certificate,
|
Certificate: options.Certificate,
|
||||||
PrivateKey: options.PrivateKey,
|
PrivateKey: options.PrivateKey,
|
||||||
|
EchKey: options.EchKey,
|
||||||
RealityConfig: options.RealityConfig.Build(),
|
RealityConfig: options.RealityConfig.Build(),
|
||||||
MuxOption: options.MuxOption.Build(),
|
MuxOption: options.MuxOption.Build(),
|
||||||
},
|
},
|
||||||
|
@ -66,9 +66,25 @@ func TestInboundVless_TLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions := outboundOptions
|
||||||
outboundOptions.Flow = "xtls-rprx-vision"
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
})
|
})
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVless_Wss1(t *testing.T) {
|
func TestInboundVless_Wss1(t *testing.T) {
|
||||||
@ -87,9 +103,25 @@ func TestInboundVless_Wss1(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions := outboundOptions
|
||||||
outboundOptions.Flow = "xtls-rprx-vision"
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
})
|
})
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVless_Wss2(t *testing.T) {
|
func TestInboundVless_Wss2(t *testing.T) {
|
||||||
@ -109,9 +141,25 @@ func TestInboundVless_Wss2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions := outboundOptions
|
||||||
outboundOptions.Flow = "xtls-rprx-vision"
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
})
|
})
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVless_Grpc1(t *testing.T) {
|
func TestInboundVless_Grpc1(t *testing.T) {
|
||||||
@ -127,6 +175,16 @@ func TestInboundVless_Grpc1(t *testing.T) {
|
|||||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
}
|
}
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVless_Grpc2(t *testing.T) {
|
func TestInboundVless_Grpc2(t *testing.T) {
|
||||||
@ -143,6 +201,16 @@ func TestInboundVless_Grpc2(t *testing.T) {
|
|||||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
}
|
}
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVless_Reality(t *testing.T) {
|
func TestInboundVless_Reality(t *testing.T) {
|
||||||
@ -165,9 +233,25 @@ func TestInboundVless_Reality(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions := outboundOptions
|
||||||
outboundOptions.Flow = "xtls-rprx-vision"
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
})
|
})
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVless_Reality_Grpc(t *testing.T) {
|
func TestInboundVless_Reality_Grpc(t *testing.T) {
|
||||||
@ -192,4 +276,14 @@ func TestInboundVless_Reality_Grpc(t *testing.T) {
|
|||||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
}
|
}
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ type VmessOption struct {
|
|||||||
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
||||||
Certificate string `inbound:"certificate,omitempty"`
|
Certificate string `inbound:"certificate,omitempty"`
|
||||||
PrivateKey string `inbound:"private-key,omitempty"`
|
PrivateKey string `inbound:"private-key,omitempty"`
|
||||||
|
EchKey string `inbound:"ech-key,omitempty"`
|
||||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||||
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
||||||
}
|
}
|
||||||
@ -61,6 +62,7 @@ func NewVmess(options *VmessOption) (*Vmess, error) {
|
|||||||
GrpcServiceName: options.GrpcServiceName,
|
GrpcServiceName: options.GrpcServiceName,
|
||||||
Certificate: options.Certificate,
|
Certificate: options.Certificate,
|
||||||
PrivateKey: options.PrivateKey,
|
PrivateKey: options.PrivateKey,
|
||||||
|
EchKey: options.EchKey,
|
||||||
RealityConfig: options.RealityConfig.Build(),
|
RealityConfig: options.RealityConfig.Build(),
|
||||||
MuxOption: options.MuxOption.Build(),
|
MuxOption: options.MuxOption.Build(),
|
||||||
},
|
},
|
||||||
|
@ -73,6 +73,16 @@ func TestInboundVMess_TLS(t *testing.T) {
|
|||||||
Fingerprint: tlsFingerprint,
|
Fingerprint: tlsFingerprint,
|
||||||
}
|
}
|
||||||
testInboundVMess(t, inboundOptions, outboundOptions)
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVMess_Ws(t *testing.T) {
|
func TestInboundVMess_Ws(t *testing.T) {
|
||||||
@ -160,6 +170,16 @@ func TestInboundVMess_Wss1(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
testInboundVMess(t, inboundOptions, outboundOptions)
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVMess_Wss2(t *testing.T) {
|
func TestInboundVMess_Wss2(t *testing.T) {
|
||||||
@ -178,6 +198,16 @@ func TestInboundVMess_Wss2(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
testInboundVMess(t, inboundOptions, outboundOptions)
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVMess_Grpc1(t *testing.T) {
|
func TestInboundVMess_Grpc1(t *testing.T) {
|
||||||
@ -193,6 +223,16 @@ func TestInboundVMess_Grpc1(t *testing.T) {
|
|||||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
}
|
}
|
||||||
testInboundVMess(t, inboundOptions, outboundOptions)
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVMess_Grpc2(t *testing.T) {
|
func TestInboundVMess_Grpc2(t *testing.T) {
|
||||||
@ -209,6 +249,16 @@ func TestInboundVMess_Grpc2(t *testing.T) {
|
|||||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
}
|
}
|
||||||
testInboundVMess(t, inboundOptions, outboundOptions)
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("ECH", func(t *testing.T) {
|
||||||
|
inboundOptions := inboundOptions
|
||||||
|
outboundOptions := outboundOptions
|
||||||
|
inboundOptions.EchKey = echKeyPem
|
||||||
|
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||||
|
Enable: true,
|
||||||
|
Config: echConfigBase64,
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVMess_Reality(t *testing.T) {
|
func TestInboundVMess_Reality(t *testing.T) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package mixed
|
package mixed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
@ -9,6 +8,8 @@ import (
|
|||||||
N "github.com/metacubex/mihomo/common/net"
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
"github.com/metacubex/mihomo/component/auth"
|
"github.com/metacubex/mihomo/component/auth"
|
||||||
"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"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
authStore "github.com/metacubex/mihomo/listener/auth"
|
authStore "github.com/metacubex/mihomo/listener/auth"
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
@ -60,7 +61,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := &tls.Config{}
|
tlsConfig := &tlsC.Config{}
|
||||||
var realityBuilder *reality.Builder
|
var realityBuilder *reality.Builder
|
||||||
|
|
||||||
if config.Certificate != "" && config.PrivateKey != "" {
|
if config.Certificate != "" && config.PrivateKey != "" {
|
||||||
@ -68,7 +69,14 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||||
|
|
||||||
|
if config.EchKey != "" {
|
||||||
|
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if config.RealityConfig.PrivateKey != "" {
|
if config.RealityConfig.PrivateKey != "" {
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
@ -83,7 +91,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
|||||||
if realityBuilder != nil {
|
if realityBuilder != nil {
|
||||||
l = realityBuilder.NewListener(l)
|
l = realityBuilder.NewListener(l)
|
||||||
} else if len(tlsConfig.Certificates) > 0 {
|
} else if len(tlsConfig.Certificates) > 0 {
|
||||||
l = tls.NewListener(l, tlsConfig)
|
l = tlsC.NewListener(l, tlsConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
ml := &Listener{
|
ml := &Listener{
|
||||||
|
@ -2,7 +2,6 @@ package sing_hysteria2
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -15,6 +14,7 @@ import (
|
|||||||
"github.com/metacubex/mihomo/adapter/outbound"
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
"github.com/metacubex/mihomo/common/sockopt"
|
"github.com/metacubex/mihomo/common/sockopt"
|
||||||
"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"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
@ -60,9 +60,16 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tlsC.Config{
|
||||||
MinVersion: tls.VersionTLS13,
|
MinVersion: tlsC.VersionTLS13,
|
||||||
Certificates: []tls.Certificate{cert},
|
}
|
||||||
|
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||||
|
|
||||||
|
if config.EchKey != "" {
|
||||||
|
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(config.ALPN) > 0 {
|
if len(config.ALPN) > 0 {
|
||||||
tlsConfig.NextProtos = config.ALPN
|
tlsConfig.NextProtos = config.ALPN
|
||||||
@ -125,7 +132,7 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
|
|||||||
SendBPS: outbound.StringToBps(config.Up),
|
SendBPS: outbound.StringToBps(config.Up),
|
||||||
ReceiveBPS: outbound.StringToBps(config.Down),
|
ReceiveBPS: outbound.StringToBps(config.Down),
|
||||||
SalamanderPassword: salamanderPassword,
|
SalamanderPassword: salamanderPassword,
|
||||||
TLSConfig: tlsC.UConfig(tlsConfig),
|
TLSConfig: tlsConfig,
|
||||||
QUICConfig: quicConfig,
|
QUICConfig: quicConfig,
|
||||||
IgnoreClientBandwidth: config.IgnoreClientBandwidth,
|
IgnoreClientBandwidth: config.IgnoreClientBandwidth,
|
||||||
UDPTimeout: sing.UDPTimeout,
|
UDPTimeout: sing.UDPTimeout,
|
||||||
|
@ -2,7 +2,6 @@ package sing_vless
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -12,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter/inbound"
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
"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"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
@ -82,7 +82,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
|
|
||||||
sl = &Listener{false, config, nil, service}
|
sl = &Listener{false, config, nil, service}
|
||||||
|
|
||||||
tlsConfig := &tls.Config{}
|
tlsConfig := &tlsC.Config{}
|
||||||
var realityBuilder *reality.Builder
|
var realityBuilder *reality.Builder
|
||||||
var httpHandler http.Handler
|
var httpHandler http.Handler
|
||||||
|
|
||||||
@ -91,7 +91,14 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||||
|
|
||||||
|
if config.EchKey != "" {
|
||||||
|
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if config.RealityConfig.PrivateKey != "" {
|
if config.RealityConfig.PrivateKey != "" {
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
@ -137,7 +144,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
if realityBuilder != nil {
|
if realityBuilder != nil {
|
||||||
l = realityBuilder.NewListener(l)
|
l = realityBuilder.NewListener(l)
|
||||||
} else if len(tlsConfig.Certificates) > 0 {
|
} else if len(tlsConfig.Certificates) > 0 {
|
||||||
l = tls.NewListener(l, tlsConfig)
|
l = tlsC.NewListener(l, tlsConfig)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("disallow using Vless without both certificates/reality config")
|
return nil, errors.New("disallow using Vless without both certificates/reality config")
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package sing_vmess
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -11,6 +10,8 @@ import (
|
|||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter/inbound"
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
"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"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
"github.com/metacubex/mihomo/listener/reality"
|
"github.com/metacubex/mihomo/listener/reality"
|
||||||
@ -75,7 +76,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
|
|
||||||
sl = &Listener{false, config, nil, service}
|
sl = &Listener{false, config, nil, service}
|
||||||
|
|
||||||
tlsConfig := &tls.Config{}
|
tlsConfig := &tlsC.Config{}
|
||||||
var realityBuilder *reality.Builder
|
var realityBuilder *reality.Builder
|
||||||
var httpHandler http.Handler
|
var httpHandler http.Handler
|
||||||
|
|
||||||
@ -84,7 +85,14 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||||
|
|
||||||
|
if config.EchKey != "" {
|
||||||
|
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if config.RealityConfig.PrivateKey != "" {
|
if config.RealityConfig.PrivateKey != "" {
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
@ -130,7 +138,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
if realityBuilder != nil {
|
if realityBuilder != nil {
|
||||||
l = realityBuilder.NewListener(l)
|
l = realityBuilder.NewListener(l)
|
||||||
} else if len(tlsConfig.Certificates) > 0 {
|
} else if len(tlsConfig.Certificates) > 0 {
|
||||||
l = tls.NewListener(l, tlsConfig)
|
l = tlsC.NewListener(l, tlsConfig)
|
||||||
}
|
}
|
||||||
sl.listeners = append(sl.listeners, l)
|
sl.listeners = append(sl.listeners, l)
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package socks
|
package socks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@ -10,6 +9,8 @@ import (
|
|||||||
N "github.com/metacubex/mihomo/common/net"
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
"github.com/metacubex/mihomo/component/auth"
|
"github.com/metacubex/mihomo/component/auth"
|
||||||
"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"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
authStore "github.com/metacubex/mihomo/listener/auth"
|
authStore "github.com/metacubex/mihomo/listener/auth"
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
@ -59,7 +60,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := &tls.Config{}
|
tlsConfig := &tlsC.Config{}
|
||||||
var realityBuilder *reality.Builder
|
var realityBuilder *reality.Builder
|
||||||
|
|
||||||
if config.Certificate != "" && config.PrivateKey != "" {
|
if config.Certificate != "" && config.PrivateKey != "" {
|
||||||
@ -67,7 +68,14 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||||
|
|
||||||
|
if config.EchKey != "" {
|
||||||
|
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if config.RealityConfig.PrivateKey != "" {
|
if config.RealityConfig.PrivateKey != "" {
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
@ -82,7 +90,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
|||||||
if realityBuilder != nil {
|
if realityBuilder != nil {
|
||||||
l = realityBuilder.NewListener(l)
|
l = realityBuilder.NewListener(l)
|
||||||
} else if len(tlsConfig.Certificates) > 0 {
|
} else if len(tlsConfig.Certificates) > 0 {
|
||||||
l = tls.NewListener(l, tlsConfig)
|
l = tlsC.NewListener(l, tlsConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := &Listener{
|
sl := &Listener{
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package trojan
|
package trojan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@ -10,6 +9,8 @@ import (
|
|||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter/inbound"
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
"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"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
"github.com/metacubex/mihomo/listener/reality"
|
"github.com/metacubex/mihomo/listener/reality"
|
||||||
@ -69,7 +70,7 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
}
|
}
|
||||||
sl = &Listener{false, config, nil, keys, pickCipher, h}
|
sl = &Listener{false, config, nil, keys, pickCipher, h}
|
||||||
|
|
||||||
tlsConfig := &tls.Config{}
|
tlsConfig := &tlsC.Config{}
|
||||||
var realityBuilder *reality.Builder
|
var realityBuilder *reality.Builder
|
||||||
var httpHandler http.Handler
|
var httpHandler http.Handler
|
||||||
|
|
||||||
@ -78,7 +79,14 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||||
|
|
||||||
|
if config.EchKey != "" {
|
||||||
|
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if config.RealityConfig.PrivateKey != "" {
|
if config.RealityConfig.PrivateKey != "" {
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
@ -124,7 +132,7 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
if realityBuilder != nil {
|
if realityBuilder != nil {
|
||||||
l = realityBuilder.NewListener(l)
|
l = realityBuilder.NewListener(l)
|
||||||
} else if len(tlsConfig.Certificates) > 0 {
|
} else if len(tlsConfig.Certificates) > 0 {
|
||||||
l = tls.NewListener(l, tlsConfig)
|
l = tlsC.NewListener(l, tlsConfig)
|
||||||
} else if !config.TrojanSSOption.Enabled {
|
} else if !config.TrojanSSOption.Enabled {
|
||||||
return nil, errors.New("disallow using Trojan without both certificates/reality/ss config")
|
return nil, errors.New("disallow using Trojan without both certificates/reality/ss config")
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package tuic
|
package tuic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -9,6 +8,7 @@ import (
|
|||||||
"github.com/metacubex/mihomo/adapter/inbound"
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
"github.com/metacubex/mihomo/common/sockopt"
|
"github.com/metacubex/mihomo/common/sockopt"
|
||||||
"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"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
@ -52,9 +52,16 @@ func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tlsC.Config{
|
||||||
MinVersion: tls.VersionTLS13,
|
MinVersion: tlsC.VersionTLS13,
|
||||||
Certificates: []tls.Certificate{cert},
|
}
|
||||||
|
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||||
|
|
||||||
|
if config.EchKey != "" {
|
||||||
|
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(config.ALPN) > 0 {
|
if len(config.ALPN) > 0 {
|
||||||
tlsConfig.NextProtos = config.ALPN
|
tlsConfig.NextProtos = config.ALPN
|
||||||
@ -125,7 +132,7 @@ func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) (
|
|||||||
option := &tuic.ServerOption{
|
option := &tuic.ServerOption{
|
||||||
HandleTcpFn: handleTcpFn,
|
HandleTcpFn: handleTcpFn,
|
||||||
HandleUdpFn: handleUdpFn,
|
HandleUdpFn: handleUdpFn,
|
||||||
TlsConfig: tlsC.UConfig(tlsConfig),
|
TlsConfig: tlsConfig,
|
||||||
QuicConfig: quicConfig,
|
QuicConfig: quicConfig,
|
||||||
CongestionController: config.CongestionController,
|
CongestionController: config.CongestionController,
|
||||||
AuthenticationTimeout: time.Duration(config.AuthenticationTimeout) * time.Millisecond,
|
AuthenticationTimeout: time.Duration(config.AuthenticationTimeout) * time.Millisecond,
|
||||||
|
@ -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
|
||||||
@ -48,10 +50,11 @@ func NewGostWebsocket(ctx context.Context, conn net.Conn, option *Option) (net.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
config := &vmess.WebsocketConfig{
|
config := &vmess.WebsocketConfig{
|
||||||
Host: option.Host,
|
Host: option.Host,
|
||||||
Port: option.Port,
|
Port: option.Port,
|
||||||
Path: option.Path,
|
Path: option.Path,
|
||||||
Headers: header,
|
ECHConfig: option.ECHConfig,
|
||||||
|
Headers: header,
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.TLS {
|
if option.TLS {
|
||||||
|
@ -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"
|
||||||
|
|
||||||
@ -213,6 +214,13 @@ func (g *Conn) SetReadDeadline(t time.Time) error { return g.SetDeadline(t) }
|
|||||||
func (g *Conn) SetWriteDeadline(t time.Time) error { return g.SetDeadline(t) }
|
func (g *Conn) SetWriteDeadline(t time.Time) error { return g.SetDeadline(t) }
|
||||||
|
|
||||||
func (g *Conn) SetDeadline(t time.Time) error {
|
func (g *Conn) SetDeadline(t time.Time) error {
|
||||||
|
if t.IsZero() {
|
||||||
|
if g.deadline != nil {
|
||||||
|
g.deadline.Stop()
|
||||||
|
g.deadline = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
d := time.Until(t)
|
d := time.Until(t)
|
||||||
if g.deadline != nil {
|
if g.deadline != nil {
|
||||||
g.deadline.Reset(d)
|
g.deadline.Reset(d)
|
||||||
@ -224,7 +232,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 +246,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(cfg)
|
||||||
|
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 +266,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 +283,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(cfg)
|
||||||
|
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 +381,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
|
||||||
|
@ -49,7 +49,7 @@ func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsHandshake := uTLSHandshakeFunc(tlsConfig, option.ClientFingerprint)
|
tlsHandshake := uTLSHandshakeFunc(tlsConfig, option.ClientFingerprint, option.Version)
|
||||||
client, err := shadowtls.NewClient(shadowtls.ClientConfig{
|
client, err := shadowtls.NewClient(shadowtls.ClientConfig{
|
||||||
Version: option.Version,
|
Version: option.Version,
|
||||||
Password: option.Password,
|
Password: option.Password,
|
||||||
@ -62,15 +62,19 @@ func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (
|
|||||||
return client.DialContextConn(ctx, conn)
|
return client.DialContextConn(ctx, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func uTLSHandshakeFunc(config *tls.Config, clientFingerprint string) shadowtls.TLSHandshakeFunc {
|
func uTLSHandshakeFunc(config *tls.Config, clientFingerprint string, version int) shadowtls.TLSHandshakeFunc {
|
||||||
return func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {
|
return func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {
|
||||||
tlsConfig := tlsC.UConfig(config)
|
tlsConfig := tlsC.UConfig(config)
|
||||||
tlsConfig.SessionIDGenerator = sessionIDGenerator
|
tlsConfig.SessionIDGenerator = sessionIDGenerator
|
||||||
if config.MaxVersion == tls.VersionTLS12 { // for ShadowTLS v1
|
if version == 1 {
|
||||||
|
tlsConfig.MaxVersion = tlsC.VersionTLS12 // ShadowTLS v1 only support TLS 1.2
|
||||||
tlsConn := tlsC.Client(conn, tlsConfig)
|
tlsConn := tlsC.Client(conn, tlsConfig)
|
||||||
return tlsConn.HandshakeContext(ctx)
|
return tlsConn.HandshakeContext(ctx)
|
||||||
}
|
}
|
||||||
if clientFingerprint, ok := tlsC.GetFingerprint(clientFingerprint); ok {
|
if clientFingerprint, ok := tlsC.GetFingerprint(clientFingerprint); ok {
|
||||||
|
if version == 2 && clientFingerprint == tlsC.HelloChrome_Auto {
|
||||||
|
clientFingerprint = tlsC.HelloChrome_120 // ShadowTLS v2 not work with X25519MLKEM768
|
||||||
|
}
|
||||||
tlsConn := tlsC.UClient(conn, tlsConfig, clientFingerprint)
|
tlsConn := tlsC.UClient(conn, tlsConfig, clientFingerprint)
|
||||||
if slices.Equal(tlsConfig.NextProtos, WsALPN) {
|
if slices.Equal(tlsConfig.NextProtos, WsALPN) {
|
||||||
err := tlsC.BuildWebsocketHandshakeState(tlsConn)
|
err := tlsC.BuildWebsocketHandshakeState(tlsConn)
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,12 @@ func NewConn(conn connWithUpstream, userUUID *uuid.UUID) (*Conn, error) {
|
|||||||
c.tlsConn = underlying
|
c.tlsConn = underlying
|
||||||
t = reflect.TypeOf(underlying).Elem()
|
t = reflect.TypeOf(underlying).Elem()
|
||||||
p = unsafe.Pointer(underlying)
|
p = unsafe.Pointer(underlying)
|
||||||
|
case *tlsC.Conn:
|
||||||
|
//log.Debugln("type *tlsC.Conn")
|
||||||
|
c.Conn = underlying.NetConn()
|
||||||
|
c.tlsConn = underlying
|
||||||
|
t = reflect.TypeOf(underlying).Elem()
|
||||||
|
p = unsafe.Pointer(underlying)
|
||||||
case *tlsC.UConn:
|
case *tlsC.UConn:
|
||||||
//log.Debugln("type *tlsC.UConn")
|
//log.Debugln("type *tlsC.UConn")
|
||||||
c.Conn = underlying.NetConn()
|
c.Conn = underlying.NetConn()
|
||||||
|
@ -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