mirror of
https://github.com/bolucat/Archive.git
synced 2025-11-03 02:53:53 +08:00
Update On Sun Sep 21 20:37:03 CEST 2025
This commit is contained in:
1
.github/update.log
vendored
1
.github/update.log
vendored
@@ -1127,3 +1127,4 @@ Update On Wed Sep 17 20:39:45 CEST 2025
|
||||
Update On Thu Sep 18 20:45:33 CEST 2025
|
||||
Update On Fri Sep 19 20:36:14 CEST 2025
|
||||
Update On Sat Sep 20 20:32:16 CEST 2025
|
||||
Update On Sun Sep 21 20:36:55 CEST 2025
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||
- name: Revert Golang1.25 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.25' }}
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.25' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
|
||||
@@ -2,7 +2,6 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -236,6 +235,11 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
tlsConfig, err := ca.GetTLSConfig(ca.Option{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
DialContext: func(context.Context, string, string) (net.Conn, error) {
|
||||
return instance, nil
|
||||
@@ -245,7 +249,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
|
||||
@@ -36,6 +36,8 @@ type AnyTLSOption struct {
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
Certificate string `proxy:"certificate,omitempty"`
|
||||
PrivateKey string `proxy:"private-key,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
|
||||
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
|
||||
@@ -120,6 +122,8 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
NextProtos: option.ALPN,
|
||||
FingerPrint: option.Fingerprint,
|
||||
Certificate: option.Certificate,
|
||||
PrivateKey: option.PrivateKey,
|
||||
ClientFingerprint: option.ClientFingerprint,
|
||||
ECH: echConfig,
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ type HttpOption struct {
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
Certificate string `proxy:"certificate,omitempty"`
|
||||
PrivateKey string `proxy:"private-key,omitempty"`
|
||||
Headers map[string]string `proxy:"headers,omitempty"`
|
||||
}
|
||||
|
||||
@@ -167,10 +169,15 @@ func NewHttp(option HttpOption) (*Http, error) {
|
||||
sni = option.SNI
|
||||
}
|
||||
var err error
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||
tlsConfig, err = ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: sni,
|
||||
}, option.Fingerprint)
|
||||
},
|
||||
Fingerprint: option.Fingerprint,
|
||||
Certificate: option.Certificate,
|
||||
PrivateKey: option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -125,9 +125,9 @@ type HysteriaOption struct {
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
Certificate string `proxy:"certificate,omitempty"`
|
||||
PrivateKey string `proxy:"private-key,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
||||
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||
@@ -160,14 +160,16 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
serverName = option.SNI
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
tlsConfig, err := ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: serverName,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
|
||||
},
|
||||
Fingerprint: option.Fingerprint,
|
||||
Certificate: option.Certificate,
|
||||
PrivateKey: option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ type Hysteria2Option struct {
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
Certificate string `proxy:"certificate,omitempty"`
|
||||
PrivateKey string `proxy:"private-key,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
UdpMTU int `proxy:"udp-mtu,omitempty"`
|
||||
|
||||
@@ -141,14 +141,16 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
serverName = option.SNI
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
tlsConfig, err := ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: serverName,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
|
||||
},
|
||||
Fingerprint: option.Fingerprint,
|
||||
Certificate: option.Certificate,
|
||||
PrivateKey: option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
gost "github.com/metacubex/mihomo/transport/gost-plugin"
|
||||
"github.com/metacubex/mihomo/transport/restls"
|
||||
obfs "github.com/metacubex/mihomo/transport/simple-obfs"
|
||||
@@ -64,6 +65,8 @@ type v2rayObfsOption struct {
|
||||
TLS bool `obfs:"tls,omitempty"`
|
||||
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Certificate string `obfs:"certificate,omitempty"`
|
||||
PrivateKey string `obfs:"private-key,omitempty"`
|
||||
Headers map[string]string `obfs:"headers,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
Mux bool `obfs:"mux,omitempty"`
|
||||
@@ -78,6 +81,8 @@ type gostObfsOption struct {
|
||||
TLS bool `obfs:"tls,omitempty"`
|
||||
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Certificate string `obfs:"certificate,omitempty"`
|
||||
PrivateKey string `obfs:"private-key,omitempty"`
|
||||
Headers map[string]string `obfs:"headers,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
Mux bool `obfs:"mux,omitempty"`
|
||||
@@ -87,6 +92,8 @@ type shadowTLSOption struct {
|
||||
Password string `obfs:"password,omitempty"`
|
||||
Host string `obfs:"host"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Certificate string `obfs:"certificate,omitempty"`
|
||||
PrivateKey string `obfs:"private-key,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
Version int `obfs:"version,omitempty"`
|
||||
ALPN []string `obfs:"alpn,omitempty"`
|
||||
@@ -251,8 +258,9 @@ func (ss *ShadowSocks) SupportUOT() bool {
|
||||
|
||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
method, err := shadowsocks.CreateMethod(context.Background(), option.Cipher, shadowsocks.MethodOptions{
|
||||
method, err := shadowsocks.CreateMethod(option.Cipher, shadowsocks.MethodOptions{
|
||||
Password: option.Password,
|
||||
TimeFunc: ntp.Now,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s cipher: %s initialize error: %w", addr, option.Cipher, err)
|
||||
@@ -300,6 +308,8 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
v2rayOption.TLS = true
|
||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||
v2rayOption.Fingerprint = opts.Fingerprint
|
||||
v2rayOption.Certificate = opts.Certificate
|
||||
v2rayOption.PrivateKey = opts.PrivateKey
|
||||
|
||||
echConfig, err := opts.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
@@ -328,6 +338,8 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
gostOption.TLS = true
|
||||
gostOption.SkipCertVerify = opts.SkipCertVerify
|
||||
gostOption.Fingerprint = opts.Fingerprint
|
||||
gostOption.Certificate = opts.Certificate
|
||||
gostOption.PrivateKey = opts.PrivateKey
|
||||
|
||||
echConfig, err := opts.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
@@ -348,6 +360,8 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
Password: opt.Password,
|
||||
Host: opt.Host,
|
||||
Fingerprint: opt.Fingerprint,
|
||||
Certificate: opt.Certificate,
|
||||
PrivateKey: opt.PrivateKey,
|
||||
ClientFingerprint: option.ClientFingerprint,
|
||||
SkipCertVerify: opt.SkipCertVerify,
|
||||
Version: opt.Version,
|
||||
|
||||
@@ -39,6 +39,8 @@ type Socks5Option struct {
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
Certificate string `proxy:"certificate,omitempty"`
|
||||
PrivateKey string `proxy:"private-key,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConnContext implements C.ProxyAdapter
|
||||
@@ -193,13 +195,16 @@ func (ss *Socks5) clientHandshakeContext(ctx context.Context, c net.Conn, addr s
|
||||
func NewSocks5(option Socks5Option) (*Socks5, error) {
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
tlsConfig = &tls.Config{
|
||||
var err error
|
||||
tlsConfig, err = ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: option.Server,
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||
},
|
||||
Fingerprint: option.Fingerprint,
|
||||
Certificate: option.Certificate,
|
||||
PrivateKey: option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ type TrojanOption struct {
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
Certificate string `proxy:"certificate,omitempty"`
|
||||
PrivateKey string `proxy:"private-key,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
@@ -100,14 +102,17 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
}
|
||||
|
||||
wsOpts.TLS = true
|
||||
tlsConfig := &tls.Config{
|
||||
wsOpts.TLSConfig, err = ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
NextProtos: alpn,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: t.option.SkipCertVerify,
|
||||
ServerName: t.option.SNI,
|
||||
}
|
||||
|
||||
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint)
|
||||
},
|
||||
Fingerprint: t.option.Fingerprint,
|
||||
Certificate: t.option.Certificate,
|
||||
PrivateKey: t.option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -126,6 +131,8 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
Host: t.option.SNI,
|
||||
SkipCertVerify: t.option.SkipCertVerify,
|
||||
FingerPrint: t.option.Fingerprint,
|
||||
Certificate: t.option.Certificate,
|
||||
PrivateKey: t.option.PrivateKey,
|
||||
ClientFingerprint: t.option.ClientFingerprint,
|
||||
NextProtos: alpn,
|
||||
ECH: t.echConfig,
|
||||
@@ -363,15 +370,17 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
tlsConfig, err := ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
NextProtos: option.ALPN,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: option.SNI,
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||
},
|
||||
Fingerprint: option.Fingerprint,
|
||||
Certificate: option.Certificate,
|
||||
PrivateKey: option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ type TuicOption struct {
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||
Certificate string `proxy:"certificate,omitempty"`
|
||||
PrivateKey string `proxy:"private-key,omitempty"`
|
||||
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
||||
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||
@@ -161,17 +161,20 @@ func (t *Tuic) ProxyInfo() C.ProxyInfo {
|
||||
func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
serverName := option.Server
|
||||
tlsConfig := &tls.Config{
|
||||
if option.SNI != "" {
|
||||
serverName = option.SNI
|
||||
}
|
||||
|
||||
tlsConfig, err := ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: serverName,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
if option.SNI != "" {
|
||||
tlsConfig.ServerName = option.SNI
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
|
||||
},
|
||||
Fingerprint: option.Fingerprint,
|
||||
Certificate: option.Certificate,
|
||||
PrivateKey: option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ func resolveUDPAddr(ctx context.Context, network, address string, prefer C.DNSPr
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ip netip.Addr
|
||||
switch prefer {
|
||||
case C.IPv4Only:
|
||||
@@ -56,7 +57,17 @@ func resolveUDPAddr(ctx context.Context, network, address string, prefer C.DNSPr
|
||||
}
|
||||
|
||||
ip, port = resolver.LookupIP4P(ip, port)
|
||||
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
|
||||
|
||||
var uint16Port uint16
|
||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||
uint16Port = uint16(port)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
// our resolver always unmap before return, so unneeded unmap at here
|
||||
// which is different with net.ResolveUDPAddr maybe return 4in6 address
|
||||
// 4in6 addresses can cause some strange effects on sing-based code
|
||||
return net.UDPAddrFromAddrPort(netip.AddrPortFrom(ip, uint16Port)), nil
|
||||
}
|
||||
|
||||
func safeConnClose(c net.Conn, err error) {
|
||||
|
||||
@@ -64,10 +64,11 @@ type VlessOption struct {
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
WSPath string `proxy:"ws-path,omitempty"`
|
||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
Certificate string `proxy:"certificate,omitempty"`
|
||||
PrivateKey string `proxy:"private-key,omitempty"`
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
}
|
||||
@@ -96,14 +97,17 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
}
|
||||
if v.option.TLS {
|
||||
wsOpts.TLS = true
|
||||
tlsConfig := &tls.Config{
|
||||
wsOpts.TLSConfig, err = ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
|
||||
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
||||
},
|
||||
Fingerprint: v.option.Fingerprint,
|
||||
Certificate: v.option.Certificate,
|
||||
PrivateKey: v.option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -206,6 +210,8 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
Certificate: v.option.Certificate,
|
||||
PrivateKey: v.option.PrivateKey,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
@@ -407,7 +413,7 @@ func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
||||
|
||||
func NewVless(option VlessOption) (*Vless, error) {
|
||||
var addons *vless.Addons
|
||||
if option.Network != "ws" && len(option.Flow) >= 16 {
|
||||
if len(option.Flow) >= 16 {
|
||||
option.Flow = option.Flow[:16]
|
||||
if option.Flow != vless.XRV {
|
||||
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
|
||||
@@ -499,10 +505,15 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
}
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||
tlsConfig, err = ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
ServerName: v.option.ServerName,
|
||||
}, v.option.Fingerprint)
|
||||
},
|
||||
Fingerprint: v.option.Fingerprint,
|
||||
Certificate: v.option.Certificate,
|
||||
PrivateKey: v.option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ type VmessOption struct {
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
Certificate string `proxy:"certificate,omitempty"`
|
||||
PrivateKey string `proxy:"private-key,omitempty"`
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
@@ -123,13 +125,16 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
|
||||
if v.option.TLS {
|
||||
wsOpts.TLS = true
|
||||
tlsConfig := &tls.Config{
|
||||
wsOpts.TLSConfig, err = ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
|
||||
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
||||
},
|
||||
Fingerprint: v.option.Fingerprint,
|
||||
Certificate: v.option.Certificate,
|
||||
PrivateKey: v.option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -178,6 +183,8 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
Certificate: v.option.Certificate,
|
||||
PrivateKey: v.option.PrivateKey,
|
||||
NextProtos: []string{"h2"},
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
@@ -208,6 +215,8 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
Certificate: v.option.Certificate,
|
||||
PrivateKey: v.option.PrivateKey,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
@@ -501,10 +510,15 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
}
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||
tlsConfig, err = ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
ServerName: v.option.ServerName,
|
||||
}, v.option.Fingerprint)
|
||||
},
|
||||
Fingerprint: v.option.Fingerprint,
|
||||
Certificate: v.option.Certificate,
|
||||
PrivateKey: v.option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -92,21 +92,21 @@ type AmneziaWGOption struct {
|
||||
JMax int `proxy:"jmax,omitempty"`
|
||||
S1 int `proxy:"s1,omitempty"`
|
||||
S2 int `proxy:"s2,omitempty"`
|
||||
H1 uint32 `proxy:"h1,omitempty"`
|
||||
H2 uint32 `proxy:"h2,omitempty"`
|
||||
H3 uint32 `proxy:"h3,omitempty"`
|
||||
H4 uint32 `proxy:"h4,omitempty"`
|
||||
|
||||
// AmneziaWG v1.5
|
||||
I1 string `proxy:"i1,omitempty"`
|
||||
I2 string `proxy:"i2,omitempty"`
|
||||
I3 string `proxy:"i3,omitempty"`
|
||||
I4 string `proxy:"i4,omitempty"`
|
||||
I5 string `proxy:"i5,omitempty"`
|
||||
J1 string `proxy:"j1,omitempty"`
|
||||
J2 string `proxy:"j2,omitempty"`
|
||||
J3 string `proxy:"j3,omitempty"`
|
||||
Itime int64 `proxy:"itime,omitempty"`
|
||||
S3 int `proxy:"s3,omitempty"` // AmneziaWG v1.5 and v2
|
||||
S4 int `proxy:"s4,omitempty"` // AmneziaWG v1.5 and v2
|
||||
H1 string `proxy:"h1,omitempty"` // In AmneziaWG v1.x, it was uint32, but our WeaklyTypedInput can handle this situation
|
||||
H2 string `proxy:"h2,omitempty"` // In AmneziaWG v1.x, it was uint32, but our WeaklyTypedInput can handle this situation
|
||||
H3 string `proxy:"h3,omitempty"` // In AmneziaWG v1.x, it was uint32, but our WeaklyTypedInput can handle this situation
|
||||
H4 string `proxy:"h4,omitempty"` // In AmneziaWG v1.x, it was uint32, but our WeaklyTypedInput can handle this situation
|
||||
I1 string `proxy:"i1,omitempty"` // AmneziaWG v1.5 and v2
|
||||
I2 string `proxy:"i2,omitempty"` // AmneziaWG v1.5 and v2
|
||||
I3 string `proxy:"i3,omitempty"` // AmneziaWG v1.5 and v2
|
||||
I4 string `proxy:"i4,omitempty"` // AmneziaWG v1.5 and v2
|
||||
I5 string `proxy:"i5,omitempty"` // AmneziaWG v1.5 and v2
|
||||
J1 string `proxy:"j1,omitempty"` // AmneziaWG v1.5 only (removed in v2)
|
||||
J2 string `proxy:"j2,omitempty"` // AmneziaWG v1.5 only (removed in v2)
|
||||
J3 string `proxy:"j3,omitempty"` // AmneziaWG v1.5 only (removed in v2)
|
||||
Itime int64 `proxy:"itime,omitempty"` // AmneziaWG v1.5 only (removed in v2)
|
||||
}
|
||||
|
||||
type wgSingErrorHandler struct {
|
||||
@@ -412,17 +412,23 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er
|
||||
if w.option.AmneziaWGOption.S2 != 0 {
|
||||
ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H1 != 0 {
|
||||
ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n"
|
||||
if w.option.AmneziaWGOption.S3 != 0 {
|
||||
ipcConf += "s3=" + strconv.Itoa(w.option.AmneziaWGOption.S3) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H2 != 0 {
|
||||
ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n"
|
||||
if w.option.AmneziaWGOption.S4 != 0 {
|
||||
ipcConf += "s4=" + strconv.Itoa(w.option.AmneziaWGOption.S4) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H3 != 0 {
|
||||
ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n"
|
||||
if w.option.AmneziaWGOption.H1 != "" {
|
||||
ipcConf += "h1=" + w.option.AmneziaWGOption.H1 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H4 != 0 {
|
||||
ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n"
|
||||
if w.option.AmneziaWGOption.H2 != "" {
|
||||
ipcConf += "h2=" + w.option.AmneziaWGOption.H2 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H3 != "" {
|
||||
ipcConf += "h3=" + w.option.AmneziaWGOption.H3 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H4 != "" {
|
||||
ipcConf += "h4=" + w.option.AmneziaWGOption.H4 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.I1 != "" {
|
||||
ipcConf += "i1=" + w.option.AmneziaWGOption.I1 + "\n"
|
||||
|
||||
@@ -331,13 +331,20 @@ func (cp *CompatibleProvider) Close() error {
|
||||
}
|
||||
|
||||
func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) {
|
||||
var excludeTypeArray []string
|
||||
if excludeType != "" {
|
||||
excludeTypeArray = strings.Split(excludeType, "|")
|
||||
}
|
||||
|
||||
var excludeFilterRegs []*regexp2.Regexp
|
||||
if excludeFilter != "" {
|
||||
for _, excludeFilter := range strings.Split(excludeFilter, "`") {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
var excludeTypeArray []string
|
||||
if excludeType != "" {
|
||||
excludeTypeArray = strings.Split(excludeType, "|")
|
||||
excludeFilterRegs = append(excludeFilterRegs, excludeFilterReg)
|
||||
}
|
||||
}
|
||||
|
||||
var filterRegs []*regexp2.Regexp
|
||||
@@ -367,8 +374,9 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
proxies := []C.Proxy{}
|
||||
proxiesSet := map[string]struct{}{}
|
||||
for _, filterReg := range filterRegs {
|
||||
LOOP1:
|
||||
for idx, mapping := range schema.Proxies {
|
||||
if nil != excludeTypeArray && len(excludeTypeArray) > 0 {
|
||||
if len(excludeTypeArray) > 0 {
|
||||
mType, ok := mapping["type"]
|
||||
if !ok {
|
||||
continue
|
||||
@@ -377,18 +385,11 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
flag := false
|
||||
for i := range excludeTypeArray {
|
||||
if strings.EqualFold(pType, excludeTypeArray[i]) {
|
||||
flag = true
|
||||
break
|
||||
for _, excludeType := range excludeTypeArray {
|
||||
if strings.EqualFold(pType, excludeType) {
|
||||
continue LOOP1
|
||||
}
|
||||
|
||||
}
|
||||
if flag {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
mName, ok := mapping["name"]
|
||||
if !ok {
|
||||
@@ -398,9 +399,11 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(excludeFilter) > 0 {
|
||||
if len(excludeFilterRegs) > 0 {
|
||||
for _, excludeFilterReg := range excludeFilterRegs {
|
||||
if mat, _ := excludeFilterReg.MatchString(name); mat {
|
||||
continue
|
||||
continue LOOP1
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(filter) > 0 {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
"github.com/metacubex/mihomo/common/net/deadline"
|
||||
|
||||
@@ -26,11 +25,20 @@ type ReadWaitOptions = network.ReadWaitOptions
|
||||
|
||||
var NewReadWaitOptions = network.NewReadWaitOptions
|
||||
|
||||
type ReaderWithUpstream = network.ReaderWithUpstream
|
||||
type WithUpstreamReader = network.WithUpstreamReader
|
||||
type WriterWithUpstream = network.WriterWithUpstream
|
||||
type WithUpstreamWriter = network.WithUpstreamWriter
|
||||
type WithUpstream = common.WithUpstream
|
||||
|
||||
var UnwrapReader = network.UnwrapReader
|
||||
var UnwrapWriter = network.UnwrapWriter
|
||||
|
||||
func NewDeadlineConn(conn net.Conn) ExtendedConn {
|
||||
if deadline.IsPipe(conn) || deadline.IsPipe(network.UnwrapReader(conn)) {
|
||||
if deadline.IsPipe(conn) || deadline.IsPipe(UnwrapReader(conn)) {
|
||||
return NewExtendedConn(conn) // pipe always have correctly deadline implement
|
||||
}
|
||||
if deadline.IsConn(conn) || deadline.IsConn(network.UnwrapReader(conn)) {
|
||||
if deadline.IsConn(conn) || deadline.IsConn(UnwrapReader(conn)) {
|
||||
return NewExtendedConn(conn) // was a *deadline.Conn
|
||||
}
|
||||
return deadline.NewConn(conn)
|
||||
@@ -47,9 +55,37 @@ type CountFunc = network.CountFunc
|
||||
|
||||
var Pipe = deadline.Pipe
|
||||
|
||||
// Relay copies between left and right bidirectionally.
|
||||
func Relay(leftConn, rightConn net.Conn) {
|
||||
defer runtime.KeepAlive(leftConn)
|
||||
defer runtime.KeepAlive(rightConn)
|
||||
_ = bufio.CopyConn(context.TODO(), leftConn, rightConn)
|
||||
func closeWrite(writer io.Closer) error {
|
||||
if c, ok := common.Cast[network.WriteCloser](writer); ok {
|
||||
return c.CloseWrite()
|
||||
}
|
||||
return writer.Close()
|
||||
}
|
||||
|
||||
// Relay copies between left and right bidirectionally.
|
||||
// like [bufio.CopyConn] but remove unneeded [context.Context] handle and the cost of [task.Group]
|
||||
func Relay(leftConn, rightConn net.Conn) {
|
||||
defer func() {
|
||||
_ = leftConn.Close()
|
||||
_ = rightConn.Close()
|
||||
}()
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
_, err := bufio.Copy(leftConn, rightConn)
|
||||
if err == nil {
|
||||
_ = closeWrite(leftConn)
|
||||
} else {
|
||||
_ = leftConn.Close()
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
_, err := bufio.Copy(rightConn, leftConn)
|
||||
if err == nil {
|
||||
_ = closeWrite(rightConn)
|
||||
} else {
|
||||
_ = rightConn.Close()
|
||||
}
|
||||
<-ch
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package queue
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Queue is a simple concurrent safe queue
|
||||
@@ -24,33 +22,32 @@ func (q *Queue[T]) Put(items ...T) {
|
||||
}
|
||||
|
||||
// Pop returns the head of items.
|
||||
func (q *Queue[T]) Pop() T {
|
||||
func (q *Queue[T]) Pop() (head T) {
|
||||
if len(q.items) == 0 {
|
||||
return lo.Empty[T]()
|
||||
return
|
||||
}
|
||||
|
||||
q.lock.Lock()
|
||||
head := q.items[0]
|
||||
head = q.items[0]
|
||||
q.items = q.items[1:]
|
||||
q.lock.Unlock()
|
||||
return head
|
||||
}
|
||||
|
||||
// Last returns the last of item.
|
||||
func (q *Queue[T]) Last() T {
|
||||
func (q *Queue[T]) Last() (last T) {
|
||||
if len(q.items) == 0 {
|
||||
return lo.Empty[T]()
|
||||
return
|
||||
}
|
||||
|
||||
q.lock.RLock()
|
||||
last := q.items[len(q.items)-1]
|
||||
last = q.items[len(q.items)-1]
|
||||
q.lock.RUnlock()
|
||||
return last
|
||||
}
|
||||
|
||||
// Copy get the copy of queue.
|
||||
func (q *Queue[T]) Copy() []T {
|
||||
items := []T{}
|
||||
func (q *Queue[T]) Copy() (items []T) {
|
||||
q.lock.RLock()
|
||||
items = append(items, q.items...)
|
||||
q.lock.RUnlock()
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestQueuePut tests the Put method of Queue
|
||||
func TestQueuePut(t *testing.T) {
|
||||
// Initialize a new queue
|
||||
q := New[int](10)
|
||||
|
||||
// Test putting a single item
|
||||
q.Put(1)
|
||||
assert.Equal(t, int64(1), q.Len(), "Queue length should be 1 after putting one item")
|
||||
|
||||
// Test putting multiple items
|
||||
q.Put(2, 3, 4)
|
||||
assert.Equal(t, int64(4), q.Len(), "Queue length should be 4 after putting three more items")
|
||||
|
||||
// Test putting zero items (should not change queue)
|
||||
q.Put()
|
||||
assert.Equal(t, int64(4), q.Len(), "Queue length should remain unchanged when putting zero items")
|
||||
}
|
||||
|
||||
// TestQueuePop tests the Pop method of Queue
|
||||
func TestQueuePop(t *testing.T) {
|
||||
// Initialize a new queue with items
|
||||
q := New[int](10)
|
||||
q.Put(1, 2, 3)
|
||||
|
||||
// Test popping items in FIFO order
|
||||
item := q.Pop()
|
||||
assert.Equal(t, 1, item, "First item popped should be 1")
|
||||
assert.Equal(t, int64(2), q.Len(), "Queue length should be 2 after popping one item")
|
||||
|
||||
item = q.Pop()
|
||||
assert.Equal(t, 2, item, "Second item popped should be 2")
|
||||
assert.Equal(t, int64(1), q.Len(), "Queue length should be 1 after popping two items")
|
||||
|
||||
item = q.Pop()
|
||||
assert.Equal(t, 3, item, "Third item popped should be 3")
|
||||
assert.Equal(t, int64(0), q.Len(), "Queue length should be 0 after popping all items")
|
||||
}
|
||||
|
||||
// TestQueuePopEmpty tests the Pop method on an empty queue
|
||||
func TestQueuePopEmpty(t *testing.T) {
|
||||
// Initialize a new empty queue
|
||||
q := New[int](0)
|
||||
|
||||
// Test popping from an empty queue
|
||||
item := q.Pop()
|
||||
assert.Equal(t, 0, item, "Popping from an empty queue should return the zero value")
|
||||
assert.Equal(t, int64(0), q.Len(), "Queue length should remain 0 after popping from an empty queue")
|
||||
}
|
||||
|
||||
// TestQueueLast tests the Last method of Queue
|
||||
func TestQueueLast(t *testing.T) {
|
||||
// Initialize a new queue with items
|
||||
q := New[int](10)
|
||||
q.Put(1, 2, 3)
|
||||
|
||||
// Test getting the last item
|
||||
item := q.Last()
|
||||
assert.Equal(t, 3, item, "Last item should be 3")
|
||||
assert.Equal(t, int64(3), q.Len(), "Queue length should remain unchanged after calling Last")
|
||||
|
||||
// Test Last on an empty queue
|
||||
emptyQ := New[int](0)
|
||||
emptyItem := emptyQ.Last()
|
||||
assert.Equal(t, 0, emptyItem, "Last on an empty queue should return the zero value")
|
||||
}
|
||||
|
||||
// TestQueueCopy tests the Copy method of Queue
|
||||
func TestQueueCopy(t *testing.T) {
|
||||
// Initialize a new queue with items
|
||||
q := New[int](10)
|
||||
q.Put(1, 2, 3)
|
||||
|
||||
// Test copying the queue
|
||||
copy := q.Copy()
|
||||
assert.Equal(t, 3, len(copy), "Copy should have the same number of items as the original queue")
|
||||
assert.Equal(t, 1, copy[0], "First item in copy should be 1")
|
||||
assert.Equal(t, 2, copy[1], "Second item in copy should be 2")
|
||||
assert.Equal(t, 3, copy[2], "Third item in copy should be 3")
|
||||
|
||||
// Verify that modifying the copy doesn't affect the original queue
|
||||
copy[0] = 99
|
||||
assert.Equal(t, 1, q.Pop(), "Original queue should not be affected by modifying the copy")
|
||||
}
|
||||
|
||||
// TestQueueLen tests the Len method of Queue
|
||||
func TestQueueLen(t *testing.T) {
|
||||
// Initialize a new empty queue
|
||||
q := New[int](10)
|
||||
assert.Equal(t, int64(0), q.Len(), "New queue should have length 0")
|
||||
|
||||
// Add items and check length
|
||||
q.Put(1, 2)
|
||||
assert.Equal(t, int64(2), q.Len(), "Queue length should be 2 after putting two items")
|
||||
|
||||
// Remove an item and check length
|
||||
q.Pop()
|
||||
assert.Equal(t, int64(1), q.Len(), "Queue length should be 1 after popping one item")
|
||||
}
|
||||
|
||||
// TestQueueNew tests the New constructor
|
||||
func TestQueueNew(t *testing.T) {
|
||||
// Test creating a new queue with different hints
|
||||
q1 := New[int](0)
|
||||
assert.NotNil(t, q1, "New queue should not be nil")
|
||||
assert.Equal(t, int64(0), q1.Len(), "New queue should have length 0")
|
||||
|
||||
q2 := New[int](10)
|
||||
assert.NotNil(t, q2, "New queue should not be nil")
|
||||
assert.Equal(t, int64(0), q2.Len(), "New queue should have length 0")
|
||||
|
||||
// Test with a different type
|
||||
q3 := New[string](5)
|
||||
assert.NotNil(t, q3, "New queue should not be nil")
|
||||
assert.Equal(t, int64(0), q3.Len(), "New queue should have length 0")
|
||||
}
|
||||
|
||||
// TestQueueConcurrency tests the concurrency safety of Queue
|
||||
func TestQueueConcurrency(t *testing.T) {
|
||||
// Initialize a new queue
|
||||
q := New[int](100)
|
||||
|
||||
// Number of goroutines and operations
|
||||
goroutines := 10
|
||||
operations := 100
|
||||
|
||||
// Wait group to synchronize goroutines
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(goroutines * 2) // For both producers and consumers
|
||||
|
||||
// Start producer goroutines
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < operations; j++ {
|
||||
q.Put(id*operations + j)
|
||||
// Small sleep to increase chance of race conditions
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Start consumer goroutines
|
||||
consumed := make(chan int, goroutines*operations)
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < operations; j++ {
|
||||
// Try to pop an item, but don't block if queue is empty
|
||||
// Use a mutex to avoid race condition between Len() check and Pop()
|
||||
q.lock.Lock()
|
||||
if len(q.items) > 0 {
|
||||
item := q.items[0]
|
||||
q.items = q.items[1:]
|
||||
q.lock.Unlock()
|
||||
consumed <- item
|
||||
} else {
|
||||
q.lock.Unlock()
|
||||
}
|
||||
// Small sleep to increase chance of race conditions
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for all goroutines to finish
|
||||
wg.Wait()
|
||||
// Close the consumed channel
|
||||
close(consumed)
|
||||
|
||||
// Count the number of consumed items
|
||||
consumedCount := 0
|
||||
for range consumed {
|
||||
consumedCount++
|
||||
}
|
||||
|
||||
// Check that the queue is in a consistent state
|
||||
totalItems := goroutines * operations
|
||||
remaining := int(q.Len())
|
||||
assert.Equal(t, totalItems, consumedCount+remaining, "Total items should equal consumed items plus remaining items")
|
||||
}
|
||||
|
||||
// TestQueueWithDifferentTypes tests the Queue with different types
|
||||
func TestQueueWithDifferentTypes(t *testing.T) {
|
||||
// Test with string type
|
||||
qString := New[string](5)
|
||||
qString.Put("hello", "world")
|
||||
assert.Equal(t, int64(2), qString.Len(), "Queue length should be 2")
|
||||
assert.Equal(t, "hello", qString.Pop(), "First item should be 'hello'")
|
||||
assert.Equal(t, "world", qString.Pop(), "Second item should be 'world'")
|
||||
|
||||
// Test with struct type
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
qStruct := New[Person](5)
|
||||
qStruct.Put(Person{Name: "Alice", Age: 30}, Person{Name: "Bob", Age: 25})
|
||||
assert.Equal(t, int64(2), qStruct.Len(), "Queue length should be 2")
|
||||
|
||||
firstPerson := qStruct.Pop()
|
||||
assert.Equal(t, "Alice", firstPerson.Name, "First person's name should be 'Alice'")
|
||||
secondPerson := qStruct.Pop()
|
||||
assert.Equal(t, "Bob", secondPerson.Name, "Second person's name should be 'Bob'")
|
||||
}
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/common/once"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
)
|
||||
|
||||
var globalCertPool *x509.CertPool
|
||||
@@ -65,70 +67,61 @@ func ResetCertificate() {
|
||||
initializeCertPool()
|
||||
}
|
||||
|
||||
func getCertPool() *x509.CertPool {
|
||||
if globalCertPool == nil {
|
||||
func GetCertPool() *x509.CertPool {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
if globalCertPool != nil {
|
||||
return globalCertPool
|
||||
}
|
||||
if globalCertPool == nil {
|
||||
initializeCertPool()
|
||||
}
|
||||
return globalCertPool
|
||||
}
|
||||
|
||||
func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error) {
|
||||
var certificate []byte
|
||||
var err error
|
||||
if len(customCA) > 0 {
|
||||
path := C.Path.Resolve(customCA)
|
||||
if !C.Path.IsSafePath(path) {
|
||||
return nil, C.Path.ErrNotSafePath(path)
|
||||
}
|
||||
certificate, err = os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load ca error: %w", err)
|
||||
}
|
||||
} else if customCAString != "" {
|
||||
certificate = []byte(customCAString)
|
||||
}
|
||||
if len(certificate) > 0 {
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(certificate) {
|
||||
return nil, fmt.Errorf("failed to parse certificate:\n\n %s", certificate)
|
||||
}
|
||||
return certPool, nil
|
||||
} else {
|
||||
return getCertPool(), nil
|
||||
}
|
||||
type Option struct {
|
||||
TLSConfig *tls.Config
|
||||
Fingerprint string
|
||||
ZeroTrust bool
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
}
|
||||
|
||||
// GetTLSConfig specified fingerprint, customCA and customCAString
|
||||
func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (_ *tls.Config, err error) {
|
||||
func GetTLSConfig(opt Option) (tlsConfig *tls.Config, err error) {
|
||||
tlsConfig = opt.TLSConfig
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
tlsConfig.RootCAs, err = GetCertPool(customCA, customCAString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
tlsConfig.Time = ntp.Now
|
||||
|
||||
if opt.ZeroTrust {
|
||||
tlsConfig.RootCAs = zeroTrustCertPool()
|
||||
} else {
|
||||
tlsConfig.RootCAs = GetCertPool()
|
||||
}
|
||||
|
||||
if len(fingerprint) > 0 {
|
||||
tlsConfig.VerifyPeerCertificate, err = NewFingerprintVerifier(fingerprint)
|
||||
if len(opt.Fingerprint) > 0 {
|
||||
tlsConfig.VerifyPeerCertificate, err = NewFingerprintVerifier(opt.Fingerprint, tlsConfig.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
if len(opt.Certificate) > 0 || len(opt.PrivateKey) > 0 {
|
||||
var cert tls.Certificate
|
||||
cert, err = LoadTLSKeyPair(opt.Certificate, opt.PrivateKey, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
// GetSpecifiedFingerprintTLSConfig specified fingerprint
|
||||
func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string) (*tls.Config, error) {
|
||||
return GetTLSConfig(tlsConfig, fingerprint, "", "")
|
||||
}
|
||||
|
||||
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
|
||||
tlsConfig, _ = GetTLSConfig(tlsConfig, "", "", "")
|
||||
return tlsConfig
|
||||
}
|
||||
var zeroTrustCertPool = once.OnceValue(func() *x509.CertPool {
|
||||
if len(_CaCertificates) != 0 { // always using embed cert first
|
||||
zeroTrustCertPool := x509.NewCertPool()
|
||||
if zeroTrustCertPool.AppendCertsFromPEM(_CaCertificates) {
|
||||
return zeroTrustCertPool
|
||||
}
|
||||
}
|
||||
return nil // fallback to system pool
|
||||
})
|
||||
|
||||
@@ -7,10 +7,11 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewFingerprintVerifier returns a function that verifies whether a certificate's SHA-256 fingerprint matches the given one.
|
||||
func NewFingerprintVerifier(fingerprint string) (func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error, error) {
|
||||
func NewFingerprintVerifier(fingerprint string, time func() time.Time) (func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error, error) {
|
||||
switch fingerprint {
|
||||
case "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random", "randomized": // WTF???
|
||||
return nil, fmt.Errorf("`fingerprint` is used for TLS certificate pinning. If you need to specify the browser fingerprint, use `client-fingerprint`")
|
||||
@@ -27,9 +28,40 @@ func NewFingerprintVerifier(fingerprint string) (func(rawCerts [][]byte, verifie
|
||||
|
||||
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
// ssl pining
|
||||
for _, rawCert := range rawCerts {
|
||||
for i, rawCert := range rawCerts {
|
||||
hash := sha256.Sum256(rawCert)
|
||||
if bytes.Equal(fpByte, hash[:]) {
|
||||
if i > 0 {
|
||||
|
||||
// When the fingerprint matches a non-leaf certificate,
|
||||
// the certificate chain validity is verified using the certificate as the trusted root certificate.
|
||||
//
|
||||
// Currently, we do not verify that the SNI matches the certificate's DNS name,
|
||||
// but we do verify the validity of the child certificate,
|
||||
// including the issuance time and whether the child certificate was issued by the parent certificate.
|
||||
|
||||
certs := make([]*x509.Certificate, i+1) // stop at i
|
||||
for j := range certs {
|
||||
cert, err := x509.ParseCertificate(rawCerts[j])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certs[j] = cert
|
||||
}
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: x509.NewCertPool(),
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
if time != nil {
|
||||
opts.CurrentTime = time()
|
||||
}
|
||||
opts.Roots.AddCert(certs[i])
|
||||
for _, cert := range certs[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
_, err := certs[0].Verify(opts)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
package ca
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFingerprintVerifierLeaf(t *testing.T) {
|
||||
leafFingerprint := CalculateFingerprint(leafPEM.Bytes)
|
||||
verifier, err := NewFingerprintVerifier(leafFingerprint, func() time.Time {
|
||||
return time.Unix(1677615892, 0)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = verifier([][]byte{leafPEM.Bytes, intermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = verifier([][]byte{leafPEM.Bytes, smimeIntermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = verifier([][]byte{leafPEM.Bytes, intermediatePEM.Bytes, smimeRootPEM.Bytes}, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = verifier([][]byte{leafWithInvalidHashPEM.Bytes, intermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = verifier([][]byte{smimeLeafPEM.Bytes, intermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = verifier([][]byte{smimeLeafPEM.Bytes, intermediatePEM.Bytes, smimeRootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestFingerprintVerifierIntermediate(t *testing.T) {
|
||||
intermediateFingerprint := CalculateFingerprint(intermediatePEM.Bytes)
|
||||
verifier, err := NewFingerprintVerifier(intermediateFingerprint, func() time.Time {
|
||||
return time.Unix(1677615892, 0)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = verifier([][]byte{leafPEM.Bytes, intermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = verifier([][]byte{leafPEM.Bytes, smimeIntermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = verifier([][]byte{leafPEM.Bytes, intermediatePEM.Bytes, smimeRootPEM.Bytes}, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = verifier([][]byte{leafWithInvalidHashPEM.Bytes, intermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = verifier([][]byte{smimeLeafPEM.Bytes, intermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = verifier([][]byte{smimeLeafPEM.Bytes, intermediatePEM.Bytes, smimeRootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestFingerprintVerifierRoot(t *testing.T) {
|
||||
rootFingerprint := CalculateFingerprint(rootPEM.Bytes)
|
||||
verifier, err := NewFingerprintVerifier(rootFingerprint, func() time.Time {
|
||||
return time.Unix(1677615892, 0)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = verifier([][]byte{leafPEM.Bytes, intermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = verifier([][]byte{leafPEM.Bytes, smimeIntermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = verifier([][]byte{leafPEM.Bytes, intermediatePEM.Bytes, smimeRootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = verifier([][]byte{leafWithInvalidHashPEM.Bytes, intermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = verifier([][]byte{smimeLeafPEM.Bytes, intermediatePEM.Bytes, rootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = verifier([][]byte{smimeLeafPEM.Bytes, intermediatePEM.Bytes, smimeRootPEM.Bytes}, nil)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
var rootPEM, _ = pem.Decode([]byte(gtsRoot))
|
||||
var intermediatePEM, _ = pem.Decode([]byte(gtsIntermediate))
|
||||
var leafPEM, _ = pem.Decode([]byte(googleLeaf))
|
||||
var leafWithInvalidHashPEM, _ = pem.Decode([]byte(googleLeafWithInvalidHash))
|
||||
var smimeRootPEM, _ = pem.Decode([]byte(smimeRoot))
|
||||
var smimeIntermediatePEM, _ = pem.Decode([]byte(smimeIntermediate))
|
||||
var smimeLeafPEM, _ = pem.Decode([]byte(smimeLeaf))
|
||||
|
||||
const gtsIntermediate = `-----BEGIN CERTIFICATE-----
|
||||
MIIFljCCA36gAwIBAgINAgO8U1lrNMcY9QFQZjANBgkqhkiG9w0BAQsFADBHMQsw
|
||||
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
|
||||
MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjAwODEzMDAwMDQyWhcNMjcwOTMwMDAw
|
||||
MDQyWjBGMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
|
||||
Y2VzIExMQzETMBEGA1UEAxMKR1RTIENBIDFDMzCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBAPWI3+dijB43+DdCkH9sh9D7ZYIl/ejLa6T/belaI+KZ9hzp
|
||||
kgOZE3wJCor6QtZeViSqejOEH9Hpabu5dOxXTGZok3c3VVP+ORBNtzS7XyV3NzsX
|
||||
lOo85Z3VvMO0Q+sup0fvsEQRY9i0QYXdQTBIkxu/t/bgRQIh4JZCF8/ZK2VWNAcm
|
||||
BA2o/X3KLu/qSHw3TT8An4Pf73WELnlXXPxXbhqW//yMmqaZviXZf5YsBvcRKgKA
|
||||
gOtjGDxQSYflispfGStZloEAoPtR28p3CwvJlk/vcEnHXG0g/Zm0tOLKLnf9LdwL
|
||||
tmsTDIwZKxeWmLnwi/agJ7u2441Rj72ux5uxiZ0CAwEAAaOCAYAwggF8MA4GA1Ud
|
||||
DwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0T
|
||||
AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUinR/r4XN7pXNPZzQ4kYU83E1HScwHwYD
|
||||
VR0jBBgwFoAU5K8rJnEaK0gnhS9SZizv8IkTcT4waAYIKwYBBQUHAQEEXDBaMCYG
|
||||
CCsGAQUFBzABhhpodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHNyMTAwBggrBgEFBQcw
|
||||
AoYkaHR0cDovL3BraS5nb29nL3JlcG8vY2VydHMvZ3RzcjEuZGVyMDQGA1UdHwQt
|
||||
MCswKaAnoCWGI2h0dHA6Ly9jcmwucGtpLmdvb2cvZ3RzcjEvZ3RzcjEuY3JsMFcG
|
||||
A1UdIARQME4wOAYKKwYBBAHWeQIFAzAqMCgGCCsGAQUFBwIBFhxodHRwczovL3Br
|
||||
aS5nb29nL3JlcG9zaXRvcnkvMAgGBmeBDAECATAIBgZngQwBAgIwDQYJKoZIhvcN
|
||||
AQELBQADggIBAIl9rCBcDDy+mqhXlRu0rvqrpXJxtDaV/d9AEQNMwkYUuxQkq/BQ
|
||||
cSLbrcRuf8/xam/IgxvYzolfh2yHuKkMo5uhYpSTld9brmYZCwKWnvy15xBpPnrL
|
||||
RklfRuFBsdeYTWU0AIAaP0+fbH9JAIFTQaSSIYKCGvGjRFsqUBITTcFTNvNCCK9U
|
||||
+o53UxtkOCcXCb1YyRt8OS1b887U7ZfbFAO/CVMkH8IMBHmYJvJh8VNS/UKMG2Yr
|
||||
PxWhu//2m+OBmgEGcYk1KCTd4b3rGS3hSMs9WYNRtHTGnXzGsYZbr8w0xNPM1IER
|
||||
lQCh9BIiAfq0g3GvjLeMcySsN1PCAJA/Ef5c7TaUEDu9Ka7ixzpiO2xj2YC/WXGs
|
||||
Yye5TBeg2vZzFb8q3o/zpWwygTMD0IZRcZk0upONXbVRWPeyk+gB9lm+cZv9TSjO
|
||||
z23HFtz30dZGm6fKa+l3D/2gthsjgx0QGtkJAITgRNOidSOzNIb2ILCkXhAd4FJG
|
||||
AJ2xDx8hcFH1mt0G/FX0Kw4zd8NLQsLxdxP8c4CU6x+7Nz/OAipmsHMdMqUybDKw
|
||||
juDEI/9bfU1lcKwrmz3O2+BtjjKAvpafkmO8l7tdufThcV4q5O8DIrGKZTqPwJNl
|
||||
1IXNDw9bg1kWRxYtnCQ6yICmJhSFm/Y3m6xv+cXDBlHz4n/FsRC6UfTd
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
const gtsRoot = `-----BEGIN CERTIFICATE-----
|
||||
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
|
||||
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
|
||||
MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
|
||||
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
|
||||
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
|
||||
A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
|
||||
27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
|
||||
Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
|
||||
TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
|
||||
qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
|
||||
szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
|
||||
Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
|
||||
MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
|
||||
wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
|
||||
aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
|
||||
VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
|
||||
AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
|
||||
FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
|
||||
C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
|
||||
QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
|
||||
h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
|
||||
7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
|
||||
ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
|
||||
MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
|
||||
Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
|
||||
6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
|
||||
0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
|
||||
2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
|
||||
bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
const googleLeaf = `-----BEGIN CERTIFICATE-----
|
||||
MIIFUjCCBDqgAwIBAgIQERmRWTzVoz0SMeozw2RM3DANBgkqhkiG9w0BAQsFADBG
|
||||
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
|
||||
QzETMBEGA1UEAxMKR1RTIENBIDFDMzAeFw0yMzAxMDIwODE5MTlaFw0yMzAzMjcw
|
||||
ODE5MThaMBkxFzAVBgNVBAMTDnd3dy5nb29nbGUuY29tMIIBIjANBgkqhkiG9w0B
|
||||
AQEFAAOCAQ8AMIIBCgKCAQEAq30odrKMT54TJikMKL8S+lwoCMT5geP0u9pWjk6a
|
||||
wdB6i3kO+UE4ijCAmhbcZKeKaLnGJ38weZNwB1ayabCYyX7hDiC/nRcZU49LX5+o
|
||||
55kDVaNn14YKkg2kCeX25HDxSwaOsNAIXKPTqiQL5LPvc4Twhl8HY51hhNWQrTEr
|
||||
N775eYbixEULvyVLq5BLbCOpPo8n0/MTjQ32ku1jQq3GIYMJC/Rf2VW5doF6t9zs
|
||||
KleflAN8OdKp0ME9OHg0T1P3yyb67T7n0SpisHbeG06AmQcKJF9g/9VPJtRf4l1Q
|
||||
WRPDC+6JUqzXCxAGmIRGZ7TNMxPMBW/7DRX6w8oLKVNb0wIDAQABo4ICZzCCAmMw
|
||||
DgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQC
|
||||
MAAwHQYDVR0OBBYEFBnboj3lf9+Xat4oEgo6ZtIMr8ZuMB8GA1UdIwQYMBaAFIp0
|
||||
f6+Fze6VzT2c0OJGFPNxNR0nMGoGCCsGAQUFBwEBBF4wXDAnBggrBgEFBQcwAYYb
|
||||
aHR0cDovL29jc3AucGtpLmdvb2cvZ3RzMWMzMDEGCCsGAQUFBzAChiVodHRwOi8v
|
||||
cGtpLmdvb2cvcmVwby9jZXJ0cy9ndHMxYzMuZGVyMBkGA1UdEQQSMBCCDnd3dy5n
|
||||
b29nbGUuY29tMCEGA1UdIAQaMBgwCAYGZ4EMAQIBMAwGCisGAQQB1nkCBQMwPAYD
|
||||
VR0fBDUwMzAxoC+gLYYraHR0cDovL2NybHMucGtpLmdvb2cvZ3RzMWMzL1FPdkow
|
||||
TjFzVDJBLmNybDCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB2AHoyjFTYty22IOo4
|
||||
4FIe6YQWcDIThU070ivBOlejUutSAAABhXHHOiUAAAQDAEcwRQIgBUkikUIXdo+S
|
||||
3T8PP0/cvokhUlumRE3GRWGL4WRMLpcCIQDY+bwK384mZxyXGZ5lwNRTAPNzT8Fx
|
||||
1+//nbaGK3BQMAB2AOg+0No+9QY1MudXKLyJa8kD08vREWvs62nhd31tBr1uAAAB
|
||||
hXHHOfQAAAQDAEcwRQIgLoVydNfMFKV9IoZR+M0UuJ2zOqbxIRum7Sn9RMPOBGMC
|
||||
IQD1/BgzCSDTvYvco6kpB6ifKSbg5gcb5KTnYxQYwRW14TANBgkqhkiG9w0BAQsF
|
||||
AAOCAQEA2bQQu30e3OFu0bmvQHmcqYvXBu6tF6e5b5b+hj4O+Rn7BXTTmaYX3M6p
|
||||
MsfRH4YVJJMB/dc3PROR2VtnKFC6gAZX+RKM6nXnZhIlOdmQnonS1ecOL19PliUd
|
||||
VXbwKjXqAO0Ljd9y9oXaXnyPyHmUJNI5YXAcxE+XXiOZhcZuMYyWmoEKJQ/XlSga
|
||||
zWfTn1IcKhA3IC7A1n/5bkkWD1Xi1mdWFQ6DQDMp//667zz7pKOgFMlB93aPDjvI
|
||||
c78zEqNswn6xGKXpWF5xVwdFcsx9HKhJ6UAi2bQ/KQ1yb7LPUOR6wXXWrG1cLnNP
|
||||
i8eNLnKL9PXQ+5SwJFCzfEhcIZuhzg==
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
// googleLeafWithInvalidHash is the same as googleLeaf, but the signature
|
||||
// algorithm in the certificate contains a nonsense OID.
|
||||
const googleLeafWithInvalidHash = `-----BEGIN CERTIFICATE-----
|
||||
MIIFUjCCBDqgAwIBAgIQERmRWTzVoz0SMeozw2RM3DANBgkqhkiG9w0BAQ4FADBG
|
||||
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
|
||||
QzETMBEGA1UEAxMKR1RTIENBIDFDMzAeFw0yMzAxMDIwODE5MTlaFw0yMzAzMjcw
|
||||
ODE5MThaMBkxFzAVBgNVBAMTDnd3dy5nb29nbGUuY29tMIIBIjANBgkqhkiG9w0B
|
||||
AQEFAAOCAQ8AMIIBCgKCAQEAq30odrKMT54TJikMKL8S+lwoCMT5geP0u9pWjk6a
|
||||
wdB6i3kO+UE4ijCAmhbcZKeKaLnGJ38weZNwB1ayabCYyX7hDiC/nRcZU49LX5+o
|
||||
55kDVaNn14YKkg2kCeX25HDxSwaOsNAIXKPTqiQL5LPvc4Twhl8HY51hhNWQrTEr
|
||||
N775eYbixEULvyVLq5BLbCOpPo8n0/MTjQ32ku1jQq3GIYMJC/Rf2VW5doF6t9zs
|
||||
KleflAN8OdKp0ME9OHg0T1P3yyb67T7n0SpisHbeG06AmQcKJF9g/9VPJtRf4l1Q
|
||||
WRPDC+6JUqzXCxAGmIRGZ7TNMxPMBW/7DRX6w8oLKVNb0wIDAQABo4ICZzCCAmMw
|
||||
DgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQC
|
||||
MAAwHQYDVR0OBBYEFBnboj3lf9+Xat4oEgo6ZtIMr8ZuMB8GA1UdIwQYMBaAFIp0
|
||||
f6+Fze6VzT2c0OJGFPNxNR0nMGoGCCsGAQUFBwEBBF4wXDAnBggrBgEFBQcwAYYb
|
||||
aHR0cDovL29jc3AucGtpLmdvb2cvZ3RzMWMzMDEGCCsGAQUFBzAChiVodHRwOi8v
|
||||
cGtpLmdvb2cvcmVwby9jZXJ0cy9ndHMxYzMuZGVyMBkGA1UdEQQSMBCCDnd3dy5n
|
||||
b29nbGUuY29tMCEGA1UdIAQaMBgwCAYGZ4EMAQIBMAwGCisGAQQB1nkCBQMwPAYD
|
||||
VR0fBDUwMzAxoC+gLYYraHR0cDovL2NybHMucGtpLmdvb2cvZ3RzMWMzL1FPdkow
|
||||
TjFzVDJBLmNybDCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB2AHoyjFTYty22IOo4
|
||||
4FIe6YQWcDIThU070ivBOlejUutSAAABhXHHOiUAAAQDAEcwRQIgBUkikUIXdo+S
|
||||
3T8PP0/cvokhUlumRE3GRWGL4WRMLpcCIQDY+bwK384mZxyXGZ5lwNRTAPNzT8Fx
|
||||
1+//nbaGK3BQMAB2AOg+0No+9QY1MudXKLyJa8kD08vREWvs62nhd31tBr1uAAAB
|
||||
hXHHOfQAAAQDAEcwRQIgLoVydNfMFKV9IoZR+M0UuJ2zOqbxIRum7Sn9RMPOBGMC
|
||||
IQD1/BgzCSDTvYvco6kpB6ifKSbg5gcb5KTnYxQYwRW14TANBgkqhkiG9w0BAQ4F
|
||||
AAOCAQEA2bQQu30e3OFu0bmvQHmcqYvXBu6tF6e5b5b+hj4O+Rn7BXTTmaYX3M6p
|
||||
MsfRH4YVJJMB/dc3PROR2VtnKFC6gAZX+RKM6nXnZhIlOdmQnonS1ecOL19PliUd
|
||||
VXbwKjXqAO0Ljd9y9oXaXnyPyHmUJNI5YXAcxE+XXiOZhcZuMYyWmoEKJQ/XlSga
|
||||
zWfTn1IcKhA3IC7A1n/5bkkWD1Xi1mdWFQ6DQDMp//667zz7pKOgFMlB93aPDjvI
|
||||
c78zEqNswn6xGKXpWF5xVwdFcsx9HKhJ6UAi2bQ/KQ1yb7LPUOR6wXXWrG1cLnNP
|
||||
i8eNLnKL9PXQ+5SwJFCzfEhcIZuhzg==
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
const smimeLeaf = `-----BEGIN CERTIFICATE-----
|
||||
MIIIPDCCBiSgAwIBAgIQaMDxFS0pOMxZZeOBxoTJtjANBgkqhkiG9w0BAQsFADCB
|
||||
nTELMAkGA1UEBhMCRVMxFDASBgNVBAoMC0laRU5QRSBTLkEuMTowOAYDVQQLDDFB
|
||||
WlogWml1cnRhZ2lyaSBwdWJsaWtvYSAtIENlcnRpZmljYWRvIHB1YmxpY28gU0NB
|
||||
MTwwOgYDVQQDDDNFQUVrbyBIZXJyaSBBZG1pbmlzdHJhemlvZW4gQ0EgLSBDQSBB
|
||||
QVBQIFZhc2NhcyAoMikwHhcNMTcwNzEyMDg1MzIxWhcNMjEwNzEyMDg1MzIxWjCC
|
||||
AQwxDzANBgNVBAoMBklaRU5QRTE4MDYGA1UECwwvWml1cnRhZ2lyaSBrb3Jwb3Jh
|
||||
dGlib2EtQ2VydGlmaWNhZG8gY29ycG9yYXRpdm8xQzBBBgNVBAsMOkNvbmRpY2lv
|
||||
bmVzIGRlIHVzbyBlbiB3d3cuaXplbnBlLmNvbSBub2xhIGVyYWJpbGkgamFraXRl
|
||||
a28xFzAVBgNVBC4TDi1kbmkgOTk5OTk5ODlaMSQwIgYDVQQDDBtDT1JQT1JBVElW
|
||||
TyBGSUNUSUNJTyBBQ1RJVk8xFDASBgNVBCoMC0NPUlBPUkFUSVZPMREwDwYDVQQE
|
||||
DAhGSUNUSUNJTzESMBAGA1UEBRMJOTk5OTk5ODlaMIIBIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAQ8AMIIBCgKCAQEAwVOMwUDfBtsH0XuxYnb+v/L774jMH8valX7RPH8cl2Lb
|
||||
SiqSo0RchW2RGA2d1yuYHlpChC9jGmt0X/g66/E/+q2hUJlfJtqVDJFwtFYV4u2S
|
||||
yzA3J36V4PRkPQrKxAsbzZriFXAF10XgiHQz9aVeMMJ9GBhmh9+DK8Tm4cMF6i8l
|
||||
+AuC35KdngPF1x0ealTYrYZplpEJFO7CiW42aLi6vQkDR2R7nmZA4AT69teqBWsK
|
||||
0DZ93/f0G/3+vnWwNTBF0lB6dIXoaz8OMSyHLqGnmmAtMrzbjAr/O/WWgbB/BqhR
|
||||
qjJQ7Ui16cuDldXaWQ/rkMzsxmsAox0UF+zdQNvXUQIDAQABo4IDBDCCAwAwgccG
|
||||
A1UdEgSBvzCBvIYVaHR0cDovL3d3dy5pemVucGUuY29tgQ9pbmZvQGl6ZW5wZS5j
|
||||
b22kgZEwgY4xRzBFBgNVBAoMPklaRU5QRSBTLkEuIC0gQ0lGIEEwMTMzNzI2MC1S
|
||||
TWVyYy5WaXRvcmlhLUdhc3RlaXogVDEwNTUgRjYyIFM4MUMwQQYDVQQJDDpBdmRh
|
||||
IGRlbCBNZWRpdGVycmFuZW8gRXRvcmJpZGVhIDE0IC0gMDEwMTAgVml0b3JpYS1H
|
||||
YXN0ZWl6MB4GA1UdEQQXMBWBE2ZpY3RpY2lvQGl6ZW5wZS5ldXMwDgYDVR0PAQH/
|
||||
BAQDAgXgMCkGA1UdJQQiMCAGCCsGAQUFBwMCBggrBgEFBQcDBAYKKwYBBAGCNxQC
|
||||
AjAdBgNVHQ4EFgQUyeoOD4cgcljKY0JvrNuX2waFQLAwHwYDVR0jBBgwFoAUwKlK
|
||||
90clh/+8taaJzoLSRqiJ66MwggEnBgNVHSAEggEeMIIBGjCCARYGCisGAQQB8zkB
|
||||
AQEwggEGMDMGCCsGAQUFBwIBFidodHRwOi8vd3d3Lml6ZW5wZS5jb20vcnBhc2Nh
|
||||
Y29ycG9yYXRpdm8wgc4GCCsGAQUFBwICMIHBGoG+Wml1cnRhZ2lyaWEgRXVza2Fs
|
||||
IEF1dG9ub21pYSBFcmtpZGVnb2tvIHNla3RvcmUgcHVibGlrb2tvIGVyYWt1bmRl
|
||||
ZW4gYmFybmUtc2FyZWV0YW4gYmFrYXJyaWsgZXJhYmlsIGRhaXRla2UuIFVzbyBy
|
||||
ZXN0cmluZ2lkbyBhbCBhbWJpdG8gZGUgcmVkZXMgaW50ZXJuYXMgZGUgRW50aWRh
|
||||
ZGVzIGRlbCBTZWN0b3IgUHVibGljbyBWYXNjbzAyBggrBgEFBQcBAQQmMCQwIgYI
|
||||
KwYBBQUHMAGGFmh0dHA6Ly9vY3NwLml6ZW5wZS5jb20wOgYDVR0fBDMwMTAvoC2g
|
||||
K4YpaHR0cDovL2NybC5pemVucGUuY29tL2NnaS1iaW4vY3JsaW50ZXJuYTIwDQYJ
|
||||
KoZIhvcNAQELBQADggIBAIy5PQ+UZlCRq6ig43vpHwlwuD9daAYeejV0Q+ZbgWAE
|
||||
GtO0kT/ytw95ZEJMNiMw3fYfPRlh27ThqiT0VDXZJDlzmn7JZd6QFcdXkCsiuv4+
|
||||
ZoXAg/QwnA3SGUUO9aVaXyuOIIuvOfb9MzoGp9xk23SMV3eiLAaLMLqwB5DTfBdt
|
||||
BGI7L1MnGJBv8RfP/TL67aJ5bgq2ri4S8vGHtXSjcZ0+rCEOLJtmDNMnTZxancg3
|
||||
/H5edeNd+n6Z48LO+JHRxQufbC4mVNxVLMIP9EkGUejlq4E4w6zb5NwCQczJbSWL
|
||||
i31rk2orsNsDlyaLGsWZp3JSNX6RmodU4KAUPor4jUJuUhrrm3Spb73gKlV/gcIw
|
||||
bCE7mML1Kss3x1ySaXsis6SZtLpGWKkW2iguPWPs0ydV6RPhmsCxieMwPPIJ87vS
|
||||
5IejfgyBae7RSuAIHyNFy4uI5xwvwUFf6OZ7az8qtW7ImFOgng3Ds+W9k1S2CNTx
|
||||
d0cnKTfA6IpjGo8EeHcxnIXT8NPImWaRj0qqonvYady7ci6U4m3lkNSdXNn1afgw
|
||||
mYust+gxVtOZs1gk2MUCgJ1V1X+g7r/Cg7viIn6TLkLrpS1kS1hvMqkl9M+7XqPo
|
||||
Qd95nJKOkusQpy99X4dF/lfbYAQnnjnqh3DLD2gvYObXFaAYFaiBKTiMTV2X72F+
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
const smimeIntermediate = `-----BEGIN CERTIFICATE-----
|
||||
MIIHNzCCBSGgAwIBAgIQJMXIqlZvjuhMvqcFXOFkpDALBgkqhkiG9w0BAQswODEL
|
||||
MAkGA1UEBhMCRVMxFDASBgNVBAoMC0laRU5QRSBTLkEuMRMwEQYDVQQDDApJemVu
|
||||
cGUuY29tMB4XDTEwMTAyMDA4MjMzM1oXDTM3MTIxMjIzMDAwMFowgZ0xCzAJBgNV
|
||||
BAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjE6MDgGA1UECwwxQVpaIFppdXJ0
|
||||
YWdpcmkgcHVibGlrb2EgLSBDZXJ0aWZpY2FkbyBwdWJsaWNvIFNDQTE8MDoGA1UE
|
||||
AwwzRUFFa28gSGVycmkgQWRtaW5pc3RyYXppb2VuIENBIC0gQ0EgQUFQUCBWYXNj
|
||||
YXMgKDIpMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoIM7nEdI0N1h
|
||||
rR5T4xuV/usKDoMIasaiKvfLhbwxaNtTt+a7W/6wV5bv3svQFIy3sUXjjdzV1nG2
|
||||
To2wo/YSPQiOt8exWvOapvL21ogiof+kelWnXFjWaKJI/vThHYLgIYEMj/y4HdtU
|
||||
ojI646rZwqsb4YGAopwgmkDfUh5jOhV2IcYE3TgJAYWVkj6jku9PLaIsHiarAHjD
|
||||
PY8dig8a4SRv0gm5Yk7FXLmW1d14oxQBDeHZ7zOEXfpafxdEDO2SNaRJjpkh8XRr
|
||||
PGqkg2y1Q3gT6b4537jz+StyDIJ3omylmlJsGCwqT7p8mEqjGJ5kC5I2VnjXKuNn
|
||||
soShc72khWZVUJiJo5SGuAkNE2ZXqltBVm5Jv6QweQKsX6bkcMc4IZok4a+hx8FM
|
||||
8IBpGf/I94pU6HzGXqCyc1d46drJgDY9mXa+6YDAJFl3xeXOOW2iGCfwXqhiCrKL
|
||||
MYvyMZzqF3QH5q4nb3ZnehYvraeMFXJXDn+Utqp8vd2r7ShfQJz01KtM4hgKdgSg
|
||||
jtW+shkVVN5ng/fPN85ovfAH2BHXFfHmQn4zKsYnLitpwYM/7S1HxlT61cdQ7Nnk
|
||||
3LZTYEgAoOmEmdheklT40WAYakksXGM5VrzG7x9S7s1Tm+Vb5LSThdHC8bxxwyTb
|
||||
KsDRDNJ84N9fPDO6qHnzaL2upQ43PycCAwEAAaOCAdkwggHVMIHHBgNVHREEgb8w
|
||||
gbyGFWh0dHA6Ly93d3cuaXplbnBlLmNvbYEPaW5mb0BpemVucGUuY29tpIGRMIGO
|
||||
MUcwRQYDVQQKDD5JWkVOUEUgUy5BLiAtIENJRiBBMDEzMzcyNjAtUk1lcmMuVml0
|
||||
b3JpYS1HYXN0ZWl6IFQxMDU1IEY2MiBTODFDMEEGA1UECQw6QXZkYSBkZWwgTWVk
|
||||
aXRlcnJhbmVvIEV0b3JiaWRlYSAxNCAtIDAxMDEwIFZpdG9yaWEtR2FzdGVpejAP
|
||||
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUwKlK90cl
|
||||
h/+8taaJzoLSRqiJ66MwHwYDVR0jBBgwFoAUHRxlDqjyJXu0kc/ksbHmvVV0bAUw
|
||||
OgYDVR0gBDMwMTAvBgRVHSAAMCcwJQYIKwYBBQUHAgEWGWh0dHA6Ly93d3cuaXpl
|
||||
bnBlLmNvbS9jcHMwNwYIKwYBBQUHAQEEKzApMCcGCCsGAQUFBzABhhtodHRwOi8v
|
||||
b2NzcC5pemVucGUuY29tOjgwOTQwMwYDVR0fBCwwKjAooCagJIYiaHR0cDovL2Ny
|
||||
bC5pemVucGUuY29tL2NnaS1iaW4vYXJsMjALBgkqhkiG9w0BAQsDggIBAMbjc3HM
|
||||
3DG9ubWPkzsF0QsktukpujbTTcGk4h20G7SPRy1DiiTxrRzdAMWGjZioOP3/fKCS
|
||||
M539qH0M+gsySNie+iKlbSZJUyE635T1tKw+G7bDUapjlH1xyv55NC5I6wCXGC6E
|
||||
3TEP5B/E7dZD0s9E4lS511ubVZivFgOzMYo1DO96diny/N/V1enaTCpRl1qH1OyL
|
||||
xUYTijV4ph2gL6exwuG7pxfRcVNHYlrRaXWfTz3F6NBKyULxrI3P/y6JAtN1GqT4
|
||||
VF/+vMygx22n0DufGepBwTQz6/rr1ulSZ+eMnuJiTXgh/BzQnkUsXTb8mHII25iR
|
||||
0oYF2qAsk6ecWbLiDpkHKIDHmML21MZE13MS8NSvTHoqJO4LyAmDe6SaeNHtrPlK
|
||||
b6mzE1BN2ug+ZaX8wLA5IMPFaf0jKhb/Cxu8INsxjt00brsErCc9ip1VNaH0M4bi
|
||||
1tGxfiew2436FaeyUxW7Pl6G5GgkNbuUc7QIoRy06DdU/U38BxW3uyJMY60zwHvS
|
||||
FlKAn0OvYp4niKhAJwaBVN3kowmJuOU5Rid+TUnfyxbJ9cttSgzaF3hP/N4zgMEM
|
||||
5tikXUskeckt8LUK96EH0QyssavAMECUEb/xrupyRdYWwjQGvNLq6T5+fViDGyOw
|
||||
k+lzD44wofy8paAy9uC9Owae0zMEzhcsyRm7
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
const smimeRoot = `-----BEGIN CERTIFICATE-----
|
||||
MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
|
||||
MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
|
||||
ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
|
||||
VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
|
||||
b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
|
||||
scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
|
||||
xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
|
||||
LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
|
||||
uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
|
||||
yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
|
||||
JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
|
||||
rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
|
||||
BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
|
||||
hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
|
||||
QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
|
||||
HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
|
||||
Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
|
||||
QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
|
||||
BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
|
||||
MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
|
||||
AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
|
||||
A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
|
||||
laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
|
||||
awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
|
||||
JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
|
||||
LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
|
||||
VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
|
||||
LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
|
||||
UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
|
||||
QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
|
||||
naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
|
||||
QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
|
||||
-----END CERTIFICATE-----`
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Path interface {
|
||||
@@ -56,6 +58,33 @@ func LoadTLSKeyPair(certificate, privateKey string, path Path) (tls.Certificate,
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func LoadCertificates(certificate string, path Path) (*x509.CertPool, error) {
|
||||
pool := x509.NewCertPool()
|
||||
if pool.AppendCertsFromPEM([]byte(certificate)) {
|
||||
return pool, nil
|
||||
}
|
||||
painTextErr := fmt.Errorf("invalid certificate: %s", certificate)
|
||||
if path == nil {
|
||||
return nil, painTextErr
|
||||
}
|
||||
|
||||
certificate = path.Resolve(certificate)
|
||||
var loadErr error
|
||||
if !path.IsSafePath(certificate) {
|
||||
loadErr = path.ErrNotSafePath(certificate)
|
||||
} else {
|
||||
certPEMBlock, err := os.ReadFile(certificate)
|
||||
if pool.AppendCertsFromPEM(certPEMBlock) {
|
||||
return pool, nil
|
||||
}
|
||||
loadErr = err
|
||||
}
|
||||
if loadErr != nil {
|
||||
return nil, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
type KeyPairType string
|
||||
|
||||
const (
|
||||
@@ -85,7 +114,11 @@ func NewRandomTLSKeyPair(keyPairType KeyPairType) (certificate string, privateKe
|
||||
return
|
||||
}
|
||||
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
NotBefore: time.Now().Add(-time.Hour * 24 * 365),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365),
|
||||
}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, key.Public(), key)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -21,10 +21,10 @@ func bindControl(ifaceIdx int) controlFn {
|
||||
var innerErr error
|
||||
err = c.Control(func(fd uintptr) {
|
||||
switch network {
|
||||
case "tcp4", "udp4":
|
||||
innerErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, ifaceIdx)
|
||||
case "tcp6", "udp6":
|
||||
case "tcp6", "udp6", "ip6":
|
||||
innerErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, ifaceIdx)
|
||||
default:
|
||||
innerErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, ifaceIdx)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/keepalive"
|
||||
@@ -177,6 +178,34 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
}
|
||||
|
||||
func ICMPControl(destination netip.Addr) func(network, address string, conn syscall.RawConn) error {
|
||||
return func(network, address string, conn syscall.RawConn) error {
|
||||
if DefaultSocketHook != nil {
|
||||
return DefaultSocketHook(network, address, conn)
|
||||
}
|
||||
dialer := &net.Dialer{}
|
||||
interfaceName := DefaultInterface.Load()
|
||||
if interfaceName == "" {
|
||||
if finder := DefaultInterfaceFinder.Load(); finder != nil {
|
||||
interfaceName = finder.FindInterfaceName(destination)
|
||||
}
|
||||
}
|
||||
if interfaceName != "" {
|
||||
if err := bindIfaceToDialer(interfaceName, dialer, network, destination); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
routingMark := int(DefaultRoutingMark.Load())
|
||||
if routingMark != 0 {
|
||||
bindMarkToDialer(routingMark, dialer, network, destination)
|
||||
}
|
||||
if dialer.ControlContext != nil {
|
||||
return dialer.ControlContext(context.TODO(), network, address, conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
|
||||
return serialDialContext(ctx, network, ips, port, opt)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -28,11 +27,11 @@ func SetUA(UA string) {
|
||||
ua = UA
|
||||
}
|
||||
|
||||
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
|
||||
return HttpRequestWithProxy(ctx, url, method, header, body, "")
|
||||
}
|
||||
|
||||
func HttpRequestWithProxy(ctx context.Context, url, method string, header map[string][]string, body io.Reader, specialProxy string) (*http.Response, error) {
|
||||
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader, options ...Option) (*http.Response, error) {
|
||||
opt := option{}
|
||||
for _, o := range options {
|
||||
o(&opt)
|
||||
}
|
||||
method = strings.ToUpper(method)
|
||||
urlRes, err := URL.Parse(url)
|
||||
if err != nil {
|
||||
@@ -40,6 +39,10 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, urlRes.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range header {
|
||||
for _, v := range v {
|
||||
req.Header.Add(k, v)
|
||||
@@ -50,10 +53,6 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
|
||||
req.Header.Set("User-Agent", UA())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user := urlRes.User; user != nil {
|
||||
password, _ := user.Password()
|
||||
req.SetBasicAuth(user.Username(), password)
|
||||
@@ -61,6 +60,11 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
tlsConfig, err := ca.GetTLSConfig(opt.caOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
// from http.DefaultTransport
|
||||
DisableKeepAlives: runtime.GOOS == "android",
|
||||
@@ -69,15 +73,34 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
if conn, err := inner.HandleTcp(inner.GetTunnel(), address, specialProxy); err == nil {
|
||||
if conn, err := inner.HandleTcp(inner.GetTunnel(), address, opt.specialProxy); err == nil {
|
||||
return conn, nil
|
||||
} else {
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
}
|
||||
},
|
||||
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
|
||||
client := http.Client{Transport: transport}
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
type Option func(opt *option)
|
||||
|
||||
type option struct {
|
||||
specialProxy string
|
||||
caOption ca.Option
|
||||
}
|
||||
|
||||
func WithSpecialProxy(name string) Option {
|
||||
return func(opt *option) {
|
||||
opt.specialProxy = name
|
||||
}
|
||||
}
|
||||
|
||||
func WithCAOption(caOption ca.Option) Option {
|
||||
return func(opt *option) {
|
||||
opt.caOption = caOption
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +183,6 @@ func (f *Fetcher[V]) startPullLoop(forceUpdate bool) (err error) {
|
||||
if f.vehicle.Type() == types.File {
|
||||
f.watcher, err = fswatch.NewWatcher(fswatch.Options{
|
||||
Path: []string{f.vehicle.Path()},
|
||||
Direct: true,
|
||||
Callback: f.updateCallback,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -135,7 +135,7 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []b
|
||||
setIfNoneMatch = true
|
||||
}
|
||||
}
|
||||
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, h.url, http.MethodGet, header, nil, h.proxy)
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, h.url, http.MethodGet, header, nil, mihomoHttp.WithSpecialProxy(h.proxy))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
utls "github.com/metacubex/utls"
|
||||
)
|
||||
|
||||
type ClientAuthType = utls.ClientAuthType
|
||||
|
||||
const (
|
||||
NoClientCert = utls.NoClientCert
|
||||
RequestClientCert = utls.RequestClientCert
|
||||
RequireAnyClientCert = utls.RequireAnyClientCert
|
||||
VerifyClientCertIfGiven = utls.VerifyClientCertIfGiven
|
||||
RequireAndVerifyClientCert = utls.RequireAndVerifyClientCert
|
||||
)
|
||||
|
||||
func ClientAuthTypeFromString(s string) ClientAuthType {
|
||||
switch s {
|
||||
case "request":
|
||||
return RequestClientCert
|
||||
case "require-any":
|
||||
return RequireAnyClientCert
|
||||
case "verify-if-given":
|
||||
return VerifyClientCertIfGiven
|
||||
case "require-and-verify":
|
||||
return RequireAndVerifyClientCert
|
||||
default:
|
||||
return NoClientCert
|
||||
}
|
||||
}
|
||||
|
||||
func ClientAuthTypeToString(t ClientAuthType) string {
|
||||
switch t {
|
||||
case RequestClientCert:
|
||||
return "request"
|
||||
case RequireAnyClientCert:
|
||||
return "require-any"
|
||||
case VerifyClientCertIfGiven:
|
||||
return "verify-if-given"
|
||||
case RequireAndVerifyClientCert:
|
||||
return "require-and-verify"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -185,6 +185,7 @@ func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChain
|
||||
opts := x509.VerifyOptions{
|
||||
DNSName: c.serverName,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
CurrentTime: ntp.Now(),
|
||||
}
|
||||
for _, cert := range certs[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
|
||||
@@ -135,6 +135,8 @@ func UConfig(config *tls.Config) *utls.Config {
|
||||
RootCAs: config.RootCAs,
|
||||
NextProtos: config.NextProtos,
|
||||
ServerName: config.ServerName,
|
||||
ClientAuth: utls.ClientAuthType(config.ClientAuth),
|
||||
ClientCAs: config.ClientCAs,
|
||||
InsecureSkipVerify: config.InsecureSkipVerify,
|
||||
CipherSuites: config.CipherSuites,
|
||||
MinVersion: config.MinVersion,
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
@@ -171,7 +172,7 @@ func (u *CoreUpdater) Update(currentExePath string, channel string, force bool)
|
||||
func (u *CoreUpdater) getLatestVersion(versionURL string) (version string, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, nil, nil)
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, nil, nil, mihomoHttp.WithCAOption(ca.Option{ZeroTrust: true}))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -194,7 +195,7 @@ func (u *CoreUpdater) getLatestVersion(versionURL string) (version string, err e
|
||||
func (u *CoreUpdater) download(updateDir, packagePath, packageURL string) (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, nil, nil)
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, nil, nil, mihomoHttp.WithCAOption(ca.Option{ZeroTrust: true}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("http request failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
@@ -308,7 +310,16 @@ func moveDir(src string, dst string) error {
|
||||
}
|
||||
|
||||
for _, dirEntry := range dirEntryList {
|
||||
err = os.Rename(filepath.Join(src, dirEntry.Name()), filepath.Join(dst, dirEntry.Name()))
|
||||
srcPath := filepath.Join(src, dirEntry.Name())
|
||||
dstPath := filepath.Join(dst, dirEntry.Name())
|
||||
err = os.Rename(srcPath, dstPath)
|
||||
if err != nil {
|
||||
// Fallback for invalid cross-device link (errno:18).
|
||||
if errors.Is(err, syscall.Errno(18)) {
|
||||
err = copyAll(srcPath, dstPath)
|
||||
_ = os.RemoveAll(srcPath)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -316,6 +327,50 @@ func moveDir(src string, dst string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyAll copy the src path and any children it contains to dst
|
||||
// modify from [os.CopyFS]
|
||||
func copyAll(src, dst string) error {
|
||||
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fpath, err := filepath.Rel(src, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newPath := filepath.Join(dst, fpath)
|
||||
|
||||
switch info.Mode().Type() {
|
||||
case os.ModeDir:
|
||||
return os.MkdirAll(newPath, info.Mode().Perm())
|
||||
case os.ModeSymlink:
|
||||
target, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Symlink(target, newPath)
|
||||
case 0:
|
||||
r, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
w, err := os.OpenFile(newPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, info.Mode().Perm())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(w, r); err != nil {
|
||||
w.Close()
|
||||
return &os.PathError{Op: "Copy", Path: newPath, Err: err}
|
||||
}
|
||||
return w.Close()
|
||||
default:
|
||||
return &os.PathError{Op: "CopyFS", Path: path, Err: os.ErrInvalid}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func inDest(fpath, dest string) bool {
|
||||
if rel, err := filepath.Rel(dest, fpath); err == nil {
|
||||
if filepath.IsLocal(rel) {
|
||||
|
||||
@@ -117,7 +117,6 @@ type Cors struct {
|
||||
|
||||
// Experimental config
|
||||
type Experimental struct {
|
||||
Fingerprints []string
|
||||
QUICGoDisableGSO bool
|
||||
QUICGoDisableECN bool
|
||||
IP4PEnable bool
|
||||
@@ -175,6 +174,8 @@ type Profile struct {
|
||||
type TLS struct {
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
ClientAuthType string
|
||||
ClientAuthCert string
|
||||
EchKey string
|
||||
CustomTrustCert []string
|
||||
}
|
||||
@@ -292,6 +293,7 @@ type RawTun struct {
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
DisableICMPForwarding bool `yaml:"disable-icmp-forwarding" json:"disable-icmp-forwarding,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
|
||||
Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
||||
@@ -368,6 +370,8 @@ type RawSniffingConfig struct {
|
||||
type RawTLS struct {
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
ClientAuthType string `yaml:"client-auth-type" json:"client-auth-type"`
|
||||
ClientAuthCert string `yaml:"client-auth-cert" json:"client-auth-cert"`
|
||||
EchKey string `yaml:"ech-key" json:"ech-key"`
|
||||
CustomTrustCert []string `yaml:"custom-certifactes" json:"custom-certifactes"`
|
||||
}
|
||||
@@ -790,7 +794,6 @@ func parseController(cfg *RawConfig) (*Controller, error) {
|
||||
|
||||
func parseExperimental(cfg *RawConfig) (*Experimental, error) {
|
||||
return &Experimental{
|
||||
Fingerprints: cfg.Experimental.Fingerprints,
|
||||
QUICGoDisableGSO: cfg.Experimental.QUICGoDisableGSO,
|
||||
QUICGoDisableECN: cfg.Experimental.QUICGoDisableECN,
|
||||
IP4PEnable: cfg.Experimental.IP4PEnable,
|
||||
@@ -828,6 +831,8 @@ func parseTLS(cfg *RawConfig) (*TLS, error) {
|
||||
return &TLS{
|
||||
Certificate: cfg.TLS.Certificate,
|
||||
PrivateKey: cfg.TLS.PrivateKey,
|
||||
ClientAuthType: cfg.TLS.ClientAuthType,
|
||||
ClientAuthCert: cfg.TLS.ClientAuthCert,
|
||||
EchKey: cfg.TLS.EchKey,
|
||||
CustomTrustCert: cfg.TLS.CustomTrustCert,
|
||||
}, nil
|
||||
@@ -1552,6 +1557,7 @@ func parseTun(rawTun RawTun, general *General) error {
|
||||
ExcludePackage: rawTun.ExcludePackage,
|
||||
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
||||
UDPTimeout: rawTun.UDPTimeout,
|
||||
DisableICMPForwarding: rawTun.DisableICMPForwarding,
|
||||
FileDescriptor: rawTun.FileDescriptor,
|
||||
|
||||
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
||||
|
||||
@@ -16,35 +16,23 @@ import (
|
||||
)
|
||||
|
||||
type client struct {
|
||||
*D.Client
|
||||
port string
|
||||
host string
|
||||
dialer *dnsDialer
|
||||
addr string
|
||||
schema string
|
||||
skipCertVerify bool
|
||||
}
|
||||
|
||||
var _ dnsClient = (*client)(nil)
|
||||
|
||||
// Address implements dnsClient
|
||||
func (c *client) Address() string {
|
||||
if len(c.addr) != 0 {
|
||||
return c.addr
|
||||
}
|
||||
schema := "udp"
|
||||
if strings.HasPrefix(c.Client.Net, "tcp") {
|
||||
schema = "tcp"
|
||||
if strings.HasSuffix(c.Client.Net, "tls") {
|
||||
schema = "tls"
|
||||
}
|
||||
}
|
||||
|
||||
c.addr = fmt.Sprintf("%s://%s", schema, net.JoinHostPort(c.host, c.port))
|
||||
return c.addr
|
||||
return fmt.Sprintf("%s://%s", c.schema, net.JoinHostPort(c.host, c.port))
|
||||
}
|
||||
|
||||
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||
network := "udp"
|
||||
if strings.HasPrefix(c.Client.Net, "tcp") {
|
||||
if c.schema != "udp" {
|
||||
network = "tcp"
|
||||
}
|
||||
|
||||
@@ -53,9 +41,24 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
}()
|
||||
defer conn.Close()
|
||||
|
||||
if c.schema == "tls" {
|
||||
tlsConfig, err := ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: c.host,
|
||||
InsecureSkipVerify: c.skipCertVerify,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn = tlsConn
|
||||
}
|
||||
|
||||
// miekg/dns ExchangeContext doesn't respond to context cancel.
|
||||
// this is a workaround
|
||||
@@ -65,34 +68,30 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
}
|
||||
ch := make(chan result, 1)
|
||||
go func() {
|
||||
if strings.HasSuffix(c.Client.Net, "tls") {
|
||||
conn = tls.Client(conn, ca.GetGlobalTLSConfig(c.Client.TLSConfig))
|
||||
dClient := &D.Client{
|
||||
UDPSize: 4096,
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
dConn := &D.Conn{
|
||||
Conn: conn,
|
||||
UDPSize: c.Client.UDPSize,
|
||||
TsigSecret: c.Client.TsigSecret,
|
||||
TsigProvider: c.Client.TsigProvider,
|
||||
UDPSize: dClient.UDPSize,
|
||||
}
|
||||
|
||||
msg, _, err := c.Client.ExchangeWithConn(m, dConn)
|
||||
msg, _, err := dClient.ExchangeWithConn(m, dConn)
|
||||
|
||||
// Resolvers MUST resend queries over TCP if they receive a truncated UDP response (with TC=1 set)!
|
||||
if msg != nil && msg.Truncated && network == "udp" {
|
||||
tcpClient := *c.Client // copy a client
|
||||
tcpClient.Net = "tcp"
|
||||
network = "tcp"
|
||||
log.Debugln("[DNS] Truncated reply from %s:%s for %s over UDP, retrying over TCP", c.host, c.port, m.Question[0].String())
|
||||
dConn.Conn, err = c.dialer.DialContext(ctx, network, addr)
|
||||
var tcpConn net.Conn
|
||||
tcpConn, err = c.dialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
ch <- result{msg, err}
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
}()
|
||||
msg, _, err = tcpClient.ExchangeWithConn(m, dConn)
|
||||
defer tcpConn.Close()
|
||||
dConn.Conn = tcpConn
|
||||
msg, _, err = dClient.ExchangeWithConn(m, dConn)
|
||||
}
|
||||
|
||||
ch <- result{msg, err}
|
||||
@@ -111,20 +110,19 @@ func (c *client) ResetConnection() {}
|
||||
func newClient(addr string, resolver *Resolver, netType string, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) *client {
|
||||
host, port, _ := net.SplitHostPort(addr)
|
||||
c := &client{
|
||||
Client: &D.Client{
|
||||
Net: netType,
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: host,
|
||||
},
|
||||
UDPSize: 4096,
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
port: port,
|
||||
host: host,
|
||||
dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
|
||||
schema: "udp",
|
||||
}
|
||||
if strings.HasPrefix(netType, "tcp") {
|
||||
c.schema = "tcp"
|
||||
if strings.HasSuffix(netType, "tls") {
|
||||
c.schema = "tls"
|
||||
}
|
||||
}
|
||||
if params["skip-cert-verify"] == "true" {
|
||||
c.TLSConfig.InsecureSkipVerify = true
|
||||
c.skipCertVerify = true
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -397,12 +397,16 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
tlsConfig := ca.GetGlobalTLSConfig(
|
||||
&tls.Config{
|
||||
tlsConfig, err := ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: doh.skipCertVerify,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
SessionTicketsDisabled: false,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var nextProtos []string
|
||||
for _, v := range doh.httpVersions {
|
||||
nextProtos = append(nextProtos, string(v))
|
||||
|
||||
@@ -331,15 +331,19 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn *quic.Conn, er
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig := ca.GetGlobalTLSConfig(
|
||||
&tls.Config{
|
||||
tlsConfig, err := ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: doq.skipCertVerify,
|
||||
NextProtos: []string{
|
||||
NextProtoDQ,
|
||||
},
|
||||
SessionTicketsDisabled: false,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transport := quic.Transport{Conn: udp}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
|
||||
@@ -48,6 +48,9 @@ ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS
|
||||
tls:
|
||||
certificate: string # 证书 PEM 格式,或者 证书的路径
|
||||
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
|
||||
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
|
||||
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
@@ -142,6 +145,7 @@ tun:
|
||||
# gso-max-size: 65536 # 通用分段卸载包的最大大小
|
||||
auto-redirect: false # 自动配置 iptables 以重定向 TCP 连接。仅支持 Linux。带有 auto-redirect 的 auto-route 现在可以在路由器上按预期工作,无需干预。
|
||||
# strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问
|
||||
# disable-icmp-forwarding: true # 禁用 ICMP 转发,防止某些情况下的 ICMP 环回问题,ping 将不会显示真实的延迟
|
||||
route-address-set: # 将指定规则集中的目标 IP CIDR 规则添加到防火墙, 不匹配的流量将绕过路由, 仅支持 Linux,且需要 nftables,`auto-route` 和 `auto-redirect` 已启用。
|
||||
- ruleset-1
|
||||
- ruleset-2
|
||||
@@ -348,7 +352,10 @@ proxies: # socks5
|
||||
# username: username
|
||||
# password: password
|
||||
# tls: true
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# skip-cert-verify: true
|
||||
# udp: true
|
||||
# ip-version: ipv6
|
||||
@@ -363,7 +370,10 @@ proxies: # socks5
|
||||
# tls: true # https
|
||||
# skip-cert-verify: true
|
||||
# sni: custom.com
|
||||
# fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# ip-version: dual
|
||||
|
||||
# Snell
|
||||
@@ -431,9 +441,10 @@ proxies: # socks5
|
||||
plugin-opts:
|
||||
mode: websocket # no QUIC now
|
||||
# tls: true # wss
|
||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 配置指纹将实现 SSL Pining 效果
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# ech-opts:
|
||||
# enable: true # 必须手动开启
|
||||
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||
@@ -471,9 +482,10 @@ proxies: # socks5
|
||||
plugin-opts:
|
||||
mode: websocket
|
||||
# tls: true # wss
|
||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 配置指纹将实现 SSL Pining 效果
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
@@ -533,7 +545,10 @@ proxies: # socks5
|
||||
cipher: auto
|
||||
# udp: true
|
||||
# tls: true
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# client-fingerprint: chrome # Available: "chrome","firefox","safari","ios","random", currently only support TLS transport in TCP/GRPC/WS/HTTP for VLESS/Vmess and trojan.
|
||||
# skip-cert-verify: true
|
||||
# servername: example.com # priority over wss host
|
||||
@@ -560,7 +575,10 @@ proxies: # socks5
|
||||
cipher: auto
|
||||
network: h2
|
||||
tls: true
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
h2-opts:
|
||||
host:
|
||||
- http.example.com
|
||||
@@ -595,7 +613,10 @@ proxies: # socks5
|
||||
cipher: auto
|
||||
network: grpc
|
||||
tls: true
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
servername: example.com
|
||||
# skip-cert-verify: true
|
||||
grpc-opts:
|
||||
@@ -610,9 +631,11 @@ proxies: # socks5
|
||||
uuid: uuid
|
||||
network: tcp
|
||||
servername: example.com # AKA SNI
|
||||
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||
# ech-opts:
|
||||
# enable: true # 必须手动开启
|
||||
@@ -629,7 +652,10 @@ proxies: # socks5
|
||||
udp: true
|
||||
flow: xtls-rprx-vision
|
||||
client-fingerprint: chrome
|
||||
# fingerprint: xxxx
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# skip-cert-verify: true
|
||||
|
||||
- name: "vless-encryption"
|
||||
@@ -640,10 +666,16 @@ proxies: # socks5
|
||||
network: tcp
|
||||
# -------------------------
|
||||
# vless encryption客户端配置:
|
||||
# (native/xorpub 的 XTLS 可以 Splice。只使用 1-RTT 模式 / 若服务端发的 ticket 中秒数不为零则 0-RTT 复用)
|
||||
# (native/xorpub 的 XTLS Vision 可以 Splice。只使用 1-RTT 模式 / 若服务端发的 ticket 中秒数不为零则 0-RTT 复用)
|
||||
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
|
||||
#
|
||||
# Padding 是可选的参数,仅作用于 1-RTT 以消除握手的长度特征,双端默认值均为 "100-111-1111.75-0-111.50-0-3333":
|
||||
# 在 1-RTT client/server hello 后以 100% 的概率粘上随机 111 到 1111 字节的 padding
|
||||
# 以 75% 的概率等待随机 0 到 111 毫秒("probability-from-to")
|
||||
# 再次以 50% 的概率发送随机 0 到 3333 字节的 padding(若为 0 则不 Write())
|
||||
# 服务端、客户端可以设置不同的 padding 参数,按 len、gap 的顺序无限串联,第一个 padding 需概率 100%、至少 35 字节
|
||||
# -------------------------
|
||||
encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(X25519 Password).(ML-KEM-768 Client)..."
|
||||
encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(padding len).(padding gap).(X25519 Password).(ML-KEM-768 Client)..."
|
||||
tls: false #可以不开启tls
|
||||
udp: true
|
||||
|
||||
@@ -693,7 +725,10 @@ proxies: # socks5
|
||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||
servername: example.com # priority over wss host
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
ws-opts:
|
||||
path: "/"
|
||||
headers:
|
||||
@@ -708,7 +743,10 @@ proxies: # socks5
|
||||
port: 443
|
||||
password: yourpsk
|
||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# udp: true
|
||||
# sni: example.com # aka server name
|
||||
# alpn:
|
||||
@@ -732,7 +770,10 @@ proxies: # socks5
|
||||
network: grpc
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
udp: true
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
@@ -745,7 +786,10 @@ proxies: # socks5
|
||||
network: ws
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
udp: true
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
@@ -764,7 +808,10 @@ proxies: # socks5
|
||||
# udp: true
|
||||
# sni: example.com # aka server name
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
|
||||
#hysteria
|
||||
- name: "hysteria"
|
||||
@@ -787,10 +834,11 @@ proxies: # socks5
|
||||
# skip-cert-verify: false
|
||||
# recv-window-conn: 12582912
|
||||
# recv-window: 52428800
|
||||
# ca: "./my.ca"
|
||||
# ca-str: "xyz"
|
||||
# disable-mtu-discovery: false
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# fast-open: true # 支持 TCP 快速打开,默认为 false
|
||||
|
||||
#hysteria2
|
||||
@@ -812,11 +860,12 @@ proxies: # socks5
|
||||
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||
# skip-cert-verify: false
|
||||
# fingerprint: xxxx
|
||||
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 下面两项如果填写则开启 mTLS(需要同时填写)
|
||||
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# alpn:
|
||||
# - h3
|
||||
# ca: "./my.ca"
|
||||
# ca-str: "xyz"
|
||||
###quic-go特殊配置项,不要随意修改除非你知道你在干什么###
|
||||
# initial-stream-receive-window: 8388608
|
||||
# max-stream-receive-window: 8388608
|
||||
@@ -857,20 +906,25 @@ proxies: # socks5
|
||||
# jmax: 501
|
||||
# s1: 30
|
||||
# s2: 40
|
||||
# h1: 123456
|
||||
# h2: 67543
|
||||
# h4: 32345
|
||||
# h3: 123123
|
||||
# # AmneziaWG v1.5
|
||||
# i1: <b 0xf6ab3267fa><c><b 0xf6ab><t><r 10><wt 10>
|
||||
# i2: <b 0xf6ab3267fa><r 100>
|
||||
# i3: ""
|
||||
# i4: ""
|
||||
# i5: ""
|
||||
# j1: <b 0xffffffff><c><b 0xf6ab><t><r 10>
|
||||
# j2: <c><b 0xf6ab><t><wt 1000>
|
||||
# j3: <t><b 0xf6ab><c><r 10>
|
||||
# itime: 60
|
||||
# s3: 50 # AmneziaWG v1.5 and v2
|
||||
# s4: 5 # AmneziaWG v1.5 and v2
|
||||
# h1: 123456 # AmneziaWG v1.0 and v1.5
|
||||
# h2: 67543 # AmneziaWG v1.0 and v1.5
|
||||
# h3: 123123 # AmneziaWG v1.0 and v1.5
|
||||
# h4: 32345 # AmneziaWG v1.0 and v1.5
|
||||
# h1: 123456-123500 # AmneziaWG v2.0 only
|
||||
# h2: 67543-67550 # AmneziaWG v2.0 only
|
||||
# h3: 123123-123200 # AmneziaWG v2.0 only
|
||||
# h4: 32345-32350 # AmneziaWG v2.0 only
|
||||
# i1: <b 0xf6ab3267fa><c><b 0xf6ab><t><r 10><wt 10> # AmneziaWG v1.5 and v2
|
||||
# i2: <b 0xf6ab3267fa><r 100> # AmneziaWG v1.5 and v2
|
||||
# i3: "" # AmneziaWG v1.5 and v2
|
||||
# i4: "" # AmneziaWG v1.5 and v2
|
||||
# i5: "" # AmneziaWG v1.5 and v2
|
||||
# j1: <b 0xffffffff><c><b 0xf6ab><t><r 10> # AmneziaWG v1.5 only (removed in v2)
|
||||
# j2: <c><b 0xf6ab><t><wt 1000> # AmneziaWG v1.5 only (removed in v2)
|
||||
# j3: <t><b 0xf6ab><c><r 10> # AmneziaWG v1.5 only (removed in v2)
|
||||
# itime: 60 # AmneziaWG v1.5 only (removed in v2)
|
||||
|
||||
# tuic
|
||||
- name: tuic
|
||||
@@ -1190,8 +1244,11 @@ listeners:
|
||||
# - username: aaa
|
||||
# password: aaa
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
|
||||
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
|
||||
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
@@ -1210,8 +1267,11 @@ listeners:
|
||||
# - username: aaa
|
||||
# password: aaa
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
|
||||
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
|
||||
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
@@ -1231,8 +1291,11 @@ listeners:
|
||||
# - username: aaa
|
||||
# password: aaa
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
|
||||
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
|
||||
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
@@ -1287,8 +1350,11 @@ listeners:
|
||||
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
|
||||
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
|
||||
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
@@ -1326,8 +1392,11 @@ listeners:
|
||||
# users: # tuicV5 填写(可以同时填写 token)
|
||||
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
|
||||
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
|
||||
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
|
||||
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
@@ -1365,13 +1434,23 @@ listeners:
|
||||
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
||||
# -------------------------
|
||||
# vless encryption服务端配置:
|
||||
# (原生外观 / 只 XOR 公钥 / 全随机数。只允许 1-RTT 模式 / 同时允许 1-RTT 模式与 600 秒复用的 0-RTT 模式)
|
||||
# (原生外观 / 只 XOR 公钥 / 全随机数。1-RTT 每次下发随机 300 到 600 秒的 ticket 以便 0-RTT 复用 / 只允许 1-RTT)
|
||||
# 填写 "600s" 会每次随机取 50% 到 100%,即相当于填写 "300-600s"
|
||||
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
|
||||
#
|
||||
# Padding 是可选的参数,仅作用于 1-RTT 以消除握手的长度特征,双端默认值均为 "100-111-1111.75-0-111.50-0-3333":
|
||||
# 在 1-RTT client/server hello 后以 100% 的概率粘上随机 111 到 1111 字节的 padding
|
||||
# 以 75% 的概率等待随机 0 到 111 毫秒("probability-from-to")
|
||||
# 再次以 50% 的概率发送随机 0 到 3333 字节的 padding(若为 0 则不 Write())
|
||||
# 服务端、客户端可以设置不同的 padding 参数,按 len、gap 的顺序无限串联,第一个 padding 需概率 100%、至少 35 字节
|
||||
# -------------------------
|
||||
# decryption: "mlkem768x25519plus.native/xorpub/random.1rtt/600s.(X25519 PrivateKey).(ML-KEM-768 Seed)..."
|
||||
# decryption: "mlkem768x25519plus.native/xorpub/random.600s(300-600s)/0s.(padding len).(padding gap).(X25519 PrivateKey).(ML-KEM-768 Seed)..."
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
|
||||
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
|
||||
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
@@ -1407,8 +1486,11 @@ listeners:
|
||||
username1: password1
|
||||
username2: password2
|
||||
# "certificate" and "private-key" are required
|
||||
certificate: ./server.crt
|
||||
certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
|
||||
private-key: ./server.key
|
||||
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
|
||||
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
|
||||
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
@@ -1430,8 +1512,11 @@ listeners:
|
||||
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
certificate: ./server.crt
|
||||
private-key: ./server.key
|
||||
certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
|
||||
private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
|
||||
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
|
||||
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
@@ -1472,8 +1557,11 @@ listeners:
|
||||
users:
|
||||
00000000-0000-0000-0000-000000000000: PASSWORD_0
|
||||
00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
|
||||
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
|
||||
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
|
||||
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
@@ -1537,6 +1625,7 @@ listeners:
|
||||
# - com.android.chrome
|
||||
# exclude-package: # 排除被路由的 Android 应用包名
|
||||
# - com.android.captiveportallogin
|
||||
# disable-icmp-forwarding: true # 禁用 ICMP 转发,防止某些情况下的 ICMP 环回问题,ping 将不会显示真实的延迟
|
||||
# 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理
|
||||
# shadowsocks,vmess 入口配置(传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理)
|
||||
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
|
||||
|
||||
@@ -6,7 +6,7 @@ require (
|
||||
github.com/bahlo/generic-list-go v0.2.0
|
||||
github.com/coreos/go-iptables v0.8.0
|
||||
github.com/dlclark/regexp2 v1.11.5
|
||||
github.com/enfein/mieru/v3 v3.19.1
|
||||
github.com/enfein/mieru/v3 v3.20.0
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gobwas/ws v1.4.0
|
||||
@@ -14,8 +14,8 @@ require (
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
||||
github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a
|
||||
github.com/metacubex/bart v0.20.5
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250902133113-a7f637c14281
|
||||
github.com/metacubex/bart v0.24.0
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b
|
||||
github.com/metacubex/blake3 v0.1.0
|
||||
github.com/metacubex/chacha v0.1.5
|
||||
@@ -24,18 +24,18 @@ require (
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295
|
||||
github.com/metacubex/randv2 v0.2.0
|
||||
github.com/metacubex/restls-client-go v0.1.7
|
||||
github.com/metacubex/sing v0.5.5
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb
|
||||
github.com/metacubex/sing v0.5.6
|
||||
github.com/metacubex/sing-mux v0.3.4
|
||||
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
github.com/metacubex/sing-tun v0.4.7
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0
|
||||
github.com/metacubex/sing-tun v0.4.8
|
||||
github.com/metacubex/sing-vmess v0.2.4
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142
|
||||
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0
|
||||
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
|
||||
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
@@ -46,7 +46,7 @@ require (
|
||||
github.com/samber/lo v1.51.0
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 // lastest version compatible with golang1.20
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.11.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7
|
||||
@@ -82,15 +82,15 @@ require (
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/ascon v0.1.0 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
|
||||
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
|
||||
@@ -25,8 +25,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.19.1 h1:19b9kgFC7oJXX9RLEO5Pi1gO6yek5cWlpK7IJVUoE8I=
|
||||
github.com/enfein/mieru/v3 v3.19.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/enfein/mieru/v3 v3.20.0 h1:1ob7pCIVSH5FYFAfYvim8isLW1vBOS4cFOUF9exJS38=
|
||||
github.com/enfein/mieru/v3 v3.20.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||
@@ -68,8 +68,6 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
|
||||
@@ -90,12 +88,12 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a h1:c1QSGpacSeQdBdWcEKZKGuWLcqIG2wxHEygAcXuDwS4=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250902133113-a7f637c14281 h1:09EM0sOLb2kfL0KETGhHujsBLB5iy5U/2yHRHsxf/pI=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250902133113-a7f637c14281/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
|
||||
github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM=
|
||||
github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc=
|
||||
github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM=
|
||||
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||
github.com/metacubex/bart v0.24.0 h1:EyNiPeVOlg0joSHTzi5oentI0j5M89utUq/5dd76pWM=
|
||||
github.com/metacubex/bart v0.24.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b h1:j7dadXD8I2KTmMt8jg1JcaP1ANL3JEObJPdANKcSYPY=
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b/go.mod h1:+WmP0VJZDkDszvpa83HzfUp6QzARl/IKkMorH4+nODw=
|
||||
github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cqY=
|
||||
@@ -106,8 +104,8 @@ github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQux
|
||||
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
|
||||
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 h1:8JVlYuE8uSJAvmyCd4TjvDxs57xjb0WxEoaWafK5+qs=
|
||||
@@ -117,32 +115,34 @@ github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFq
|
||||
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
|
||||
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
|
||||
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing v0.5.5 h1:m5U8iHvRAUxlme3FZlE/LPIGHjU8oMCUzXWGbQQAC1E=
|
||||
github.com/metacubex/sing v0.5.5/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac h1:wDH/Jh/yqWbzPktqJP+Y1cUG8hchcrzKzUxJiSpnaQs=
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb h1:U/m3h8lp/j7i8zFgfvScLdZa1/Y8dd74oO7iZaQq80s=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
|
||||
github.com/metacubex/sing v0.5.6 h1:mEPDCadsCj3DB8gn+t/EtposlYuALEkExa/LUguw6/c=
|
||||
github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0=
|
||||
github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231 h1:dGvo7UahC/gYBQNBoictr14baJzBjAKUAorP63QFFtg=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6 h1:ZR1kYT0f0Vi64iQSS09OdhFfppiNkh7kjgRdMm0SB98=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||
github.com/metacubex/sing-tun v0.4.7 h1:ZDY/W+1c7PeWWKeKRyUo18fySF/TWjB0i5ui81Ar778=
|
||||
github.com/metacubex/sing-tun v0.4.7/go.mod h1:xHecZRwBnKWe6zG9amAK9cXf91lF6blgjBqm+VvOrmU=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 h1:WZepq4TOZa6WewB8tGAZrrL+bL2R2ivoBzuEgAeolWc=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||
github.com/metacubex/sing-tun v0.4.8 h1:3PyiUKWXYi37yHptXskzL1723O3OUdyt0Aej4XHVikM=
|
||||
github.com/metacubex/sing-tun v0.4.8/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I=
|
||||
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 h1:yN3mQ4cT9sPUciw/rO0Isc/8QlO86DB6g9SEMRgQ8Cw=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142 h1:csEbKOzRAxJXffOeZnnS3/kA/F55JiTbKv5jcYqCXms=
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142/go.mod h1:67I3skhEY4Sya8f1YxELwWPoeQdXqZCrWNYLvq8gn2U=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 h1:Ui+/2s5Qz0lSnDUBmEL12M5Oi/PzvFxGTNohm8ZcsmE=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2 h1:5OGzQvoE5yuOe8AsZsFwhf32ZxKmKN9G+k06AVd+6jY=
|
||||
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2/go.mod h1:GN/CB3TRwQ9LYquYpIFynDkvMTYmkjwI7+mkUIoHj88=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
|
||||
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
|
||||
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49/go.mod h1:MBeEa9IVBphH7vc3LNtW6ZujVXFizotPo3OEiHQ+TNU=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
@@ -196,8 +196,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tiendc/go-deepcopy v1.6.1 h1:uVRTItFeNHkMcLueHS7OCsxgxT9P8MzGB/taUa2Y4Tk=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
|
||||
@@ -231,6 +231,8 @@ func updateNTP(c *config.NTP) {
|
||||
c.DialerProxy,
|
||||
c.WriteToSystem,
|
||||
)
|
||||
} else {
|
||||
ntp.ReCreateNTPService("", 0, "", false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@ func applyRoute(cfg *config.Config) {
|
||||
Secret: cfg.Controller.Secret,
|
||||
Certificate: cfg.TLS.Certificate,
|
||||
PrivateKey: cfg.TLS.PrivateKey,
|
||||
ClientAuthType: cfg.TLS.ClientAuthType,
|
||||
ClientAuthCert: cfg.TLS.ClientAuthCert,
|
||||
EchKey: cfg.TLS.EchKey,
|
||||
DohServer: cfg.Controller.ExternalDohServer,
|
||||
IsDebug: cfg.General.LogLevel == log.DEBUG,
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@@ -63,6 +64,8 @@ type Config struct {
|
||||
Secret string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
ClientAuthType string
|
||||
ClientAuthCert string
|
||||
EchKey string
|
||||
DohServer string
|
||||
IsDebug bool
|
||||
@@ -201,9 +204,23 @@ func startTLS(cfg *Config) {
|
||||
}
|
||||
|
||||
log.Infoln("RESTful API tls listening at: %s", l.Addr().String())
|
||||
tlsConfig := &tlsC.Config{}
|
||||
tlsConfig := &tlsC.Config{Time: ntp.Now}
|
||||
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
|
||||
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(cfg.ClientAuthType)
|
||||
if len(cfg.ClientAuthCert) > 0 {
|
||||
if tlsConfig.ClientAuth == tlsC.NoClientCert {
|
||||
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
|
||||
pool, err := ca.LoadCertificates(cfg.ClientAuthCert, C.Path)
|
||||
if err != nil {
|
||||
log.Errorln("External controller tls listen error: %s", err)
|
||||
return
|
||||
}
|
||||
tlsConfig.ClientCAs = pool
|
||||
}
|
||||
|
||||
if cfg.EchKey != "" {
|
||||
err = ech.LoadECHKey(cfg.EchKey, tlsConfig, C.Path)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
"github.com/metacubex/mihomo/transport/anytls/padding"
|
||||
"github.com/metacubex/mihomo/transport/anytls/session"
|
||||
|
||||
@@ -42,7 +43,7 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig := &tlsC.Config{}
|
||||
tlsConfig := &tlsC.Config{Time: ntp.Now}
|
||||
if config.Certificate != "" && config.PrivateKey != "" {
|
||||
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
|
||||
if err != nil {
|
||||
@@ -57,6 +58,19 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
}
|
||||
}
|
||||
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
|
||||
if len(config.ClientAuthCert) > 0 {
|
||||
if tlsConfig.ClientAuth == tlsC.NoClientCert {
|
||||
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
|
||||
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ClientCAs = pool
|
||||
}
|
||||
|
||||
sl = &Listener{
|
||||
config: config,
|
||||
|
||||
@@ -10,6 +10,8 @@ type AnyTLSServer struct {
|
||||
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
ClientAuthType string `yaml:"client-auth-type" json:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `yaml:"client-auth-cert" json:"client-auth-cert,omitempty"`
|
||||
EchKey string `yaml:"ech-key" json:"ech-key"`
|
||||
PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"`
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ type AuthServer struct {
|
||||
AuthStore auth.AuthStore
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
ClientAuthType string
|
||||
ClientAuthCert string
|
||||
EchKey string
|
||||
RealityConfig reality.Config
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ type Hysteria2Server struct {
|
||||
ObfsPassword string `yaml:"obfs-password" json:"obfs-password,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
ClientAuthType string `yaml:"client-auth-type" json:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `yaml:"client-auth-cert" json:"client-auth-cert,omitempty"`
|
||||
EchKey string `yaml:"ech-key" json:"ech-key,omitempty"`
|
||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
|
||||
@@ -20,6 +20,8 @@ type TrojanServer struct {
|
||||
GrpcServiceName string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
ClientAuthType string
|
||||
ClientAuthCert string
|
||||
EchKey string
|
||||
RealityConfig reality.Config
|
||||
MuxOption sing.MuxOption
|
||||
|
||||
@@ -13,6 +13,8 @@ type TuicServer struct {
|
||||
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
ClientAuthType string `yaml:"client-auth-type" json:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `yaml:"client-auth-cert" json:"client-auth-cert,omitempty"`
|
||||
EchKey string `yaml:"ech-key" json:"ech-key"`
|
||||
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
|
||||
@@ -48,6 +48,7 @@ type Tun struct {
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
DisableICMPForwarding bool `yaml:"disable-icmp-forwarding" json:"disable-icmp-forwarding,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
|
||||
Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
||||
@@ -186,6 +187,9 @@ func (t *Tun) Equal(other Tun) bool {
|
||||
if t.UDPTimeout != other.UDPTimeout {
|
||||
return false
|
||||
}
|
||||
if t.DisableICMPForwarding != other.DisableICMPForwarding {
|
||||
return false
|
||||
}
|
||||
if t.FileDescriptor != other.FileDescriptor {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ type VlessServer struct {
|
||||
GrpcServiceName string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
ClientAuthType string
|
||||
ClientAuthCert string
|
||||
EchKey string
|
||||
RealityConfig reality.Config
|
||||
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
||||
|
||||
@@ -21,6 +21,8 @@ type VmessServer struct {
|
||||
GrpcServiceName string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
ClientAuthType string
|
||||
ClientAuthCert string
|
||||
EchKey string
|
||||
RealityConfig reality.Config
|
||||
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
authStore "github.com/metacubex/mihomo/listener/auth"
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/reality"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
@@ -65,7 +66,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig := &tlsC.Config{}
|
||||
tlsConfig := &tlsC.Config{Time: ntp.Now}
|
||||
var realityBuilder *reality.Builder
|
||||
|
||||
if config.Certificate != "" && config.PrivateKey != "" {
|
||||
@@ -82,10 +83,26 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
||||
}
|
||||
}
|
||||
}
|
||||
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
|
||||
if len(config.ClientAuthCert) > 0 {
|
||||
if tlsConfig.ClientAuth == tlsC.NoClientCert {
|
||||
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
|
||||
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ClientCAs = pool
|
||||
}
|
||||
if config.RealityConfig.PrivateKey != "" {
|
||||
if tlsConfig.Certificates != nil {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
if tlsConfig.ClientAuth != tlsC.NoClientCert {
|
||||
return nil, errors.New("client-auth is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -14,6 +14,8 @@ type AnyTLSOption struct {
|
||||
Users map[string]string `inbound:"users,omitempty"`
|
||||
Certificate string `inbound:"certificate"`
|
||||
PrivateKey string `inbound:"private-key"`
|
||||
ClientAuthType string `inbound:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
|
||||
EchKey string `inbound:"ech-key,omitempty"`
|
||||
PaddingScheme string `inbound:"padding-scheme,omitempty"`
|
||||
}
|
||||
@@ -43,6 +45,8 @@ func NewAnyTLS(options *AnyTLSOption) (*AnyTLS, error) {
|
||||
Users: options.Users,
|
||||
Certificate: options.Certificate,
|
||||
PrivateKey: options.PrivateKey,
|
||||
ClientAuthType: options.ClientAuthType,
|
||||
ClientAuthCert: options.ClientAuthCert,
|
||||
EchKey: options.EchKey,
|
||||
PaddingScheme: options.PaddingScheme,
|
||||
},
|
||||
|
||||
@@ -70,4 +70,25 @@ func TestInboundAnyTLS_TLS(t *testing.T) {
|
||||
}
|
||||
testInboundAnyTLS(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
t.Run("mTLS", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
testInboundAnyTLS(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
t.Run("mTLS+ECH", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
inboundOptions.EchKey = echKeyPem
|
||||
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||
Enable: true,
|
||||
Config: echConfigBase64,
|
||||
}
|
||||
testInboundAnyTLS(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -37,9 +37,10 @@ var httpData = make([]byte, 2*pool.RelayBufferSize)
|
||||
var remoteAddr = netip.MustParseAddr("1.2.3.4")
|
||||
var userUUID = utils.NewUUIDV4().String()
|
||||
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
|
||||
var tlsAuthCertificate, tlsAuthPrivateKey, _, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
|
||||
var tlsConfigCert, _ = tls.X509KeyPair([]byte(tlsCertificate), []byte(tlsPrivateKey))
|
||||
var tlsConfig = &tls.Config{Certificates: []tls.Certificate{tlsConfigCert}, NextProtos: []string{"h2", "http/1.1"}}
|
||||
var tlsClientConfig, _ = ca.GetTLSConfig(nil, tlsFingerprint, "", "")
|
||||
var tlsClientConfig, _ = ca.GetTLSConfig(ca.Option{Fingerprint: tlsFingerprint})
|
||||
var realityPrivateKey, realityPublickey string
|
||||
var realityDest = "itunes.apple.com"
|
||||
var realityShortid = "10f897e26c4b9478"
|
||||
|
||||
@@ -16,6 +16,8 @@ type HTTPOption struct {
|
||||
Users AuthUsers `inbound:"users,omitempty"`
|
||||
Certificate string `inbound:"certificate,omitempty"`
|
||||
PrivateKey string `inbound:"private-key,omitempty"`
|
||||
ClientAuthType string `inbound:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
|
||||
EchKey string `inbound:"ech-key,omitempty"`
|
||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||
}
|
||||
@@ -65,6 +67,8 @@ func (h *HTTP) Listen(tunnel C.Tunnel) error {
|
||||
AuthStore: h.config.Users.GetAuthStore(),
|
||||
Certificate: h.config.Certificate,
|
||||
PrivateKey: h.config.PrivateKey,
|
||||
ClientAuthType: h.config.ClientAuthType,
|
||||
ClientAuthCert: h.config.ClientAuthCert,
|
||||
EchKey: h.config.EchKey,
|
||||
RealityConfig: h.config.RealityConfig.Build(),
|
||||
},
|
||||
|
||||
@@ -16,6 +16,8 @@ type Hysteria2Option struct {
|
||||
ObfsPassword string `inbound:"obfs-password,omitempty"`
|
||||
Certificate string `inbound:"certificate"`
|
||||
PrivateKey string `inbound:"private-key"`
|
||||
ClientAuthType string `inbound:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
|
||||
EchKey string `inbound:"ech-key,omitempty"`
|
||||
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
|
||||
ALPN []string `inbound:"alpn,omitempty"`
|
||||
@@ -61,6 +63,8 @@ func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) {
|
||||
ObfsPassword: options.ObfsPassword,
|
||||
Certificate: options.Certificate,
|
||||
PrivateKey: options.PrivateKey,
|
||||
ClientAuthType: options.ClientAuthType,
|
||||
ClientAuthCert: options.ClientAuthCert,
|
||||
EchKey: options.EchKey,
|
||||
MaxIdleTime: options.MaxIdleTime,
|
||||
ALPN: options.ALPN,
|
||||
|
||||
@@ -51,14 +51,7 @@ func testInboundHysteria2(t *testing.T, inboundOptions inbound.Hysteria2Option,
|
||||
tunnel.DoTest(t, out)
|
||||
}
|
||||
|
||||
func TestInboundHysteria2_TLS(t *testing.T) {
|
||||
inboundOptions := inbound.Hysteria2Option{
|
||||
Certificate: tlsCertificate,
|
||||
PrivateKey: tlsPrivateKey,
|
||||
}
|
||||
outboundOptions := outbound.Hysteria2Option{
|
||||
Fingerprint: tlsFingerprint,
|
||||
}
|
||||
func testInboundHysteria2TLS(t *testing.T, inboundOptions inbound.Hysteria2Option, outboundOptions outbound.Hysteria2Option) {
|
||||
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||
t.Run("ECH", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
@@ -70,6 +63,38 @@ func TestInboundHysteria2_TLS(t *testing.T) {
|
||||
}
|
||||
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
t.Run("mTLS", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
t.Run("mTLS+ECH", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
inboundOptions.EchKey = echKeyPem
|
||||
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||
Enable: true,
|
||||
Config: echConfigBase64,
|
||||
}
|
||||
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInboundHysteria2_TLS(t *testing.T) {
|
||||
inboundOptions := inbound.Hysteria2Option{
|
||||
Certificate: tlsCertificate,
|
||||
PrivateKey: tlsPrivateKey,
|
||||
}
|
||||
outboundOptions := outbound.Hysteria2Option{
|
||||
Fingerprint: tlsFingerprint,
|
||||
}
|
||||
testInboundHysteria2TLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundHysteria2_Salamander(t *testing.T) {
|
||||
@@ -84,17 +109,7 @@ func TestInboundHysteria2_Salamander(t *testing.T) {
|
||||
Obfs: "salamander",
|
||||
ObfsPassword: userUUID,
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundHysteria2TLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundHysteria2_Brutal(t *testing.T) {
|
||||
@@ -109,15 +124,5 @@ func TestInboundHysteria2_Brutal(t *testing.T) {
|
||||
Up: "30 Mbps",
|
||||
Down: "200 Mbps",
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundHysteria2TLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ type MixedOption struct {
|
||||
UDP bool `inbound:"udp,omitempty"`
|
||||
Certificate string `inbound:"certificate,omitempty"`
|
||||
PrivateKey string `inbound:"private-key,omitempty"`
|
||||
ClientAuthType string `inbound:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
|
||||
EchKey string `inbound:"ech-key,omitempty"`
|
||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||
}
|
||||
@@ -70,6 +72,8 @@ func (m *Mixed) Listen(tunnel C.Tunnel) error {
|
||||
AuthStore: m.config.Users.GetAuthStore(),
|
||||
Certificate: m.config.Certificate,
|
||||
PrivateKey: m.config.PrivateKey,
|
||||
ClientAuthType: m.config.ClientAuthType,
|
||||
ClientAuthCert: m.config.ClientAuthCert,
|
||||
EchKey: m.config.EchKey,
|
||||
RealityConfig: m.config.RealityConfig.Build(),
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var singMuxProtocolList = []string{"smux"} // don't test "h2mux" and "yamux" because it has some confused bugs
|
||||
var singMuxProtocolList = []string{"smux", "yamux"} // don't test "h2mux" because it has some confused bugs
|
||||
|
||||
// notCloseProxyAdapter is a proxy adapter that does not close the underlying outbound.ProxyAdapter.
|
||||
// The outbound.SingMux will close the underlying outbound.ProxyAdapter when it is closed, but we don't want to close it.
|
||||
|
||||
@@ -17,6 +17,8 @@ type SocksOption struct {
|
||||
UDP bool `inbound:"udp,omitempty"`
|
||||
Certificate string `inbound:"certificate,omitempty"`
|
||||
PrivateKey string `inbound:"private-key,omitempty"`
|
||||
ClientAuthType string `inbound:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
|
||||
EchKey string `inbound:"ech-key,omitempty"`
|
||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||
}
|
||||
@@ -90,6 +92,8 @@ func (s *Socks) Listen(tunnel C.Tunnel) error {
|
||||
AuthStore: s.config.Users.GetAuthStore(),
|
||||
Certificate: s.config.Certificate,
|
||||
PrivateKey: s.config.PrivateKey,
|
||||
ClientAuthType: s.config.ClientAuthType,
|
||||
ClientAuthCert: s.config.ClientAuthCert,
|
||||
EchKey: s.config.EchKey,
|
||||
RealityConfig: s.config.RealityConfig.Build(),
|
||||
},
|
||||
|
||||
@@ -16,6 +16,8 @@ type TrojanOption struct {
|
||||
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
||||
Certificate string `inbound:"certificate,omitempty"`
|
||||
PrivateKey string `inbound:"private-key,omitempty"`
|
||||
ClientAuthType string `inbound:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
|
||||
EchKey string `inbound:"ech-key,omitempty"`
|
||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
||||
@@ -68,6 +70,8 @@ func NewTrojan(options *TrojanOption) (*Trojan, error) {
|
||||
GrpcServiceName: options.GrpcServiceName,
|
||||
Certificate: options.Certificate,
|
||||
PrivateKey: options.PrivateKey,
|
||||
ClientAuthType: options.ClientAuthType,
|
||||
ClientAuthCert: options.ClientAuthCert,
|
||||
EchKey: options.EchKey,
|
||||
RealityConfig: options.RealityConfig.Build(),
|
||||
MuxOption: options.MuxOption.Build(),
|
||||
|
||||
@@ -52,17 +52,13 @@ func testInboundTrojan(t *testing.T, inboundOptions inbound.TrojanOption, outbou
|
||||
|
||||
tunnel.DoTest(t, out)
|
||||
|
||||
if outboundOptions.Network == "grpc" { // don't test sing-mux over grpc
|
||||
return
|
||||
}
|
||||
testSingMux(t, tunnel, out)
|
||||
}
|
||||
|
||||
func TestInboundTrojan_TLS(t *testing.T) {
|
||||
inboundOptions := inbound.TrojanOption{
|
||||
Certificate: tlsCertificate,
|
||||
PrivateKey: tlsPrivateKey,
|
||||
}
|
||||
outboundOptions := outbound.TrojanOption{
|
||||
Fingerprint: tlsFingerprint,
|
||||
}
|
||||
func testInboundTrojanTLS(t *testing.T, inboundOptions inbound.TrojanOption, outboundOptions outbound.TrojanOption) {
|
||||
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||
t.Run("ECH", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
@@ -74,6 +70,38 @@ func TestInboundTrojan_TLS(t *testing.T) {
|
||||
}
|
||||
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
t.Run("mTLS", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
t.Run("mTLS+ECH", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
inboundOptions.EchKey = echKeyPem
|
||||
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||
Enable: true,
|
||||
Config: echConfigBase64,
|
||||
}
|
||||
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInboundTrojan_TLS(t *testing.T) {
|
||||
inboundOptions := inbound.TrojanOption{
|
||||
Certificate: tlsCertificate,
|
||||
PrivateKey: tlsPrivateKey,
|
||||
}
|
||||
outboundOptions := outbound.TrojanOption{
|
||||
Fingerprint: tlsFingerprint,
|
||||
}
|
||||
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundTrojan_Wss1(t *testing.T) {
|
||||
@@ -89,17 +117,7 @@ func TestInboundTrojan_Wss1(t *testing.T) {
|
||||
Path: "/ws",
|
||||
},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundTrojan_Wss2(t *testing.T) {
|
||||
@@ -116,17 +134,7 @@ func TestInboundTrojan_Wss2(t *testing.T) {
|
||||
Path: "/ws",
|
||||
},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundTrojan_Grpc1(t *testing.T) {
|
||||
@@ -140,17 +148,7 @@ func TestInboundTrojan_Grpc1(t *testing.T) {
|
||||
Network: "grpc",
|
||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundTrojan_Grpc2(t *testing.T) {
|
||||
@@ -165,17 +163,7 @@ func TestInboundTrojan_Grpc2(t *testing.T) {
|
||||
Network: "grpc",
|
||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundTrojan_Reality(t *testing.T) {
|
||||
@@ -239,17 +227,7 @@ func TestInboundTrojan_TLS_TrojanSS(t *testing.T) {
|
||||
Password: "password",
|
||||
},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundTrojan_Wss_TrojanSS(t *testing.T) {
|
||||
@@ -275,15 +253,5 @@ func TestInboundTrojan_Wss_TrojanSS(t *testing.T) {
|
||||
Path: "/ws",
|
||||
},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ type TuicOption struct {
|
||||
Users map[string]string `inbound:"users,omitempty"`
|
||||
Certificate string `inbound:"certificate"`
|
||||
PrivateKey string `inbound:"private-key"`
|
||||
ClientAuthType string `inbound:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
|
||||
EchKey string `inbound:"ech-key,omitempty"`
|
||||
CongestionController string `inbound:"congestion-controller,omitempty"`
|
||||
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
|
||||
@@ -51,6 +53,8 @@ func NewTuic(options *TuicOption) (*Tuic, error) {
|
||||
Users: options.Users,
|
||||
Certificate: options.Certificate,
|
||||
PrivateKey: options.PrivateKey,
|
||||
ClientAuthType: options.ClientAuthType,
|
||||
ClientAuthCert: options.ClientAuthCert,
|
||||
EchKey: options.EchKey,
|
||||
CongestionController: options.CongestionController,
|
||||
MaxIdleTime: options.MaxIdleTime,
|
||||
|
||||
@@ -99,4 +99,25 @@ func TestInboundTuic_TLS(t *testing.T) {
|
||||
}
|
||||
testInboundTuic(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
t.Run("mTLS", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
testInboundTuic(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
t.Run("mTLS+ECH", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
inboundOptions.EchKey = echKeyPem
|
||||
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||
Enable: true,
|
||||
Config: echConfigBase64,
|
||||
}
|
||||
testInboundTuic(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ type TunOption struct {
|
||||
ExcludePackage []string `inbound:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat bool `inbound:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `inbound:"udp-timeout,omitempty"`
|
||||
DisableICMPForwarding bool `inbound:"disable-icmp-forwarding,omitempty"`
|
||||
FileDescriptor int `inbound:"file-descriptor,omitempty"`
|
||||
|
||||
Inet4RouteAddress []netip.Prefix `inbound:"inet4-route-address,omitempty"`
|
||||
@@ -122,6 +123,7 @@ func NewTun(options *TunOption) (*Tun, error) {
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
EndpointIndependentNat: options.EndpointIndependentNat,
|
||||
UDPTimeout: options.UDPTimeout,
|
||||
DisableICMPForwarding: options.DisableICMPForwarding,
|
||||
FileDescriptor: options.FileDescriptor,
|
||||
|
||||
Inet4RouteAddress: options.Inet4RouteAddress,
|
||||
|
||||
@@ -17,6 +17,8 @@ type VlessOption struct {
|
||||
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
||||
Certificate string `inbound:"certificate,omitempty"`
|
||||
PrivateKey string `inbound:"private-key,omitempty"`
|
||||
ClientAuthType string `inbound:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
|
||||
EchKey string `inbound:"ech-key,omitempty"`
|
||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
||||
@@ -64,6 +66,8 @@ func NewVless(options *VlessOption) (*Vless, error) {
|
||||
GrpcServiceName: options.GrpcServiceName,
|
||||
Certificate: options.Certificate,
|
||||
PrivateKey: options.PrivateKey,
|
||||
ClientAuthType: options.ClientAuthType,
|
||||
ClientAuthCert: options.ClientAuthCert,
|
||||
EchKey: options.EchKey,
|
||||
RealityConfig: options.RealityConfig.Build(),
|
||||
MuxOption: options.MuxOption.Build(),
|
||||
|
||||
@@ -53,9 +53,75 @@ func testInboundVless(t *testing.T, inboundOptions inbound.VlessOption, outbound
|
||||
|
||||
tunnel.DoTest(t, out)
|
||||
|
||||
if outboundOptions.Network == "grpc" { // don't test sing-mux over grpc
|
||||
return
|
||||
}
|
||||
testSingMux(t, tunnel, out)
|
||||
}
|
||||
|
||||
func testInboundVlessTLS(t *testing.T, inboundOptions inbound.VlessOption, outboundOptions outbound.VlessOption, testVision bool) {
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
if testVision {
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
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)
|
||||
if testVision {
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("mTLS", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
if testVision {
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("mTLS+ECH", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
inboundOptions.EchKey = echKeyPem
|
||||
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||
Enable: true,
|
||||
Config: echConfigBase64,
|
||||
}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
if testVision {
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInboundVless_TLS(t *testing.T) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Certificate: tlsCertificate,
|
||||
@@ -65,27 +131,7 @@ func TestInboundVless_TLS(t *testing.T) {
|
||||
TLS: true,
|
||||
Fingerprint: tlsFingerprint,
|
||||
}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
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)
|
||||
})
|
||||
})
|
||||
testInboundVlessTLS(t, inboundOptions, outboundOptions, true)
|
||||
}
|
||||
|
||||
func TestInboundVless_Encryption(t *testing.T) {
|
||||
@@ -99,6 +145,15 @@ func TestInboundVless_Encryption(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
paddings := []struct {
|
||||
name string
|
||||
data string
|
||||
}{
|
||||
{"unconfigured-padding", ""},
|
||||
{"default-padding", "100-111-1111.75-0-111.50-0-3333."},
|
||||
{"old-padding", "100-100-1000."}, // Xray-core v25.8.29
|
||||
{"custom-padding", "100-1234-7890.33-0-1111.66-0-6666.55-111-777."},
|
||||
}
|
||||
var modes = []string{
|
||||
"native",
|
||||
"xorpub",
|
||||
@@ -107,12 +162,18 @@ func TestInboundVless_Encryption(t *testing.T) {
|
||||
for i := range modes {
|
||||
mode := modes[i]
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
for i := range paddings {
|
||||
padding := paddings[i].data
|
||||
t.Run(paddings[i].name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Decryption: "mlkem768x25519plus." + mode + ".600s." + privateKeyBase64 + "." + seedBase64,
|
||||
Decryption: "mlkem768x25519plus." + mode + ".600s." + padding + privateKeyBase64 + "." + seedBase64,
|
||||
}
|
||||
outboundOptions := outbound.VlessOption{
|
||||
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + passwordBase64 + "." + clientBase64,
|
||||
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + padding + passwordBase64 + "." + clientBase64,
|
||||
}
|
||||
t.Run("raw", func(t *testing.T) {
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
@@ -120,6 +181,36 @@ func TestInboundVless_Encryption(t *testing.T) {
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
})
|
||||
t.Run("ws", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
inboundOptions.WsPath = "/ws"
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Network = "ws"
|
||||
outboundOptions.WSOpts = outbound.WSOptions{Path: "/ws"}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
})
|
||||
t.Run("grpc", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
inboundOptions.GrpcServiceName = "GunService"
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Network = "grpc"
|
||||
outboundOptions.GrpcOpts = outbound.GrpcOptions{GrpcServiceName: "GunService"}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,31 +224,9 @@ func TestInboundVless_Wss1(t *testing.T) {
|
||||
TLS: true,
|
||||
Fingerprint: tlsFingerprint,
|
||||
Network: "ws",
|
||||
WSOpts: outbound.WSOptions{
|
||||
Path: "/ws",
|
||||
},
|
||||
WSOpts: outbound.WSOptions{Path: "/ws"},
|
||||
}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
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)
|
||||
})
|
||||
})
|
||||
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
|
||||
}
|
||||
|
||||
func TestInboundVless_Wss2(t *testing.T) {
|
||||
@@ -171,31 +240,9 @@ func TestInboundVless_Wss2(t *testing.T) {
|
||||
TLS: true,
|
||||
Fingerprint: tlsFingerprint,
|
||||
Network: "ws",
|
||||
WSOpts: outbound.WSOptions{
|
||||
Path: "/ws",
|
||||
},
|
||||
WSOpts: outbound.WSOptions{Path: "/ws"},
|
||||
}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
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)
|
||||
})
|
||||
})
|
||||
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
|
||||
}
|
||||
|
||||
func TestInboundVless_Grpc1(t *testing.T) {
|
||||
@@ -210,17 +257,7 @@ func TestInboundVless_Grpc1(t *testing.T) {
|
||||
Network: "grpc",
|
||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
|
||||
}
|
||||
|
||||
func TestInboundVless_Grpc2(t *testing.T) {
|
||||
@@ -236,17 +273,7 @@ func TestInboundVless_Grpc2(t *testing.T) {
|
||||
Network: "grpc",
|
||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
|
||||
}
|
||||
|
||||
func TestInboundVless_Reality(t *testing.T) {
|
||||
|
||||
@@ -16,6 +16,8 @@ type VmessOption struct {
|
||||
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
||||
Certificate string `inbound:"certificate,omitempty"`
|
||||
PrivateKey string `inbound:"private-key,omitempty"`
|
||||
ClientAuthType string `inbound:"client-auth-type,omitempty"`
|
||||
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
|
||||
EchKey string `inbound:"ech-key,omitempty"`
|
||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
||||
@@ -62,6 +64,8 @@ func NewVmess(options *VmessOption) (*Vmess, error) {
|
||||
GrpcServiceName: options.GrpcServiceName,
|
||||
Certificate: options.Certificate,
|
||||
PrivateKey: options.PrivateKey,
|
||||
ClientAuthType: options.ClientAuthType,
|
||||
ClientAuthCert: options.ClientAuthCert,
|
||||
EchKey: options.EchKey,
|
||||
RealityConfig: options.RealityConfig.Build(),
|
||||
MuxOption: options.MuxOption.Build(),
|
||||
|
||||
@@ -54,6 +54,9 @@ func testInboundVMess(t *testing.T, inboundOptions inbound.VmessOption, outbound
|
||||
|
||||
tunnel.DoTest(t, out)
|
||||
|
||||
if outboundOptions.Network == "grpc" { // don't test sing-mux over grpc
|
||||
return
|
||||
}
|
||||
testSingMux(t, tunnel, out)
|
||||
}
|
||||
|
||||
@@ -63,15 +66,7 @@ func TestInboundVMess_Basic(t *testing.T) {
|
||||
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundVMess_TLS(t *testing.T) {
|
||||
inboundOptions := inbound.VmessOption{
|
||||
Certificate: tlsCertificate,
|
||||
PrivateKey: tlsPrivateKey,
|
||||
}
|
||||
outboundOptions := outbound.VmessOption{
|
||||
TLS: true,
|
||||
Fingerprint: tlsFingerprint,
|
||||
}
|
||||
func testInboundVMessTLS(t *testing.T, inboundOptions inbound.VmessOption, outboundOptions outbound.VmessOption) {
|
||||
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||
t.Run("ECH", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
@@ -83,6 +78,39 @@ func TestInboundVMess_TLS(t *testing.T) {
|
||||
}
|
||||
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
t.Run("mTLS", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
t.Run("mTLS+ECH", func(t *testing.T) {
|
||||
inboundOptions := inboundOptions
|
||||
outboundOptions := outboundOptions
|
||||
inboundOptions.ClientAuthCert = tlsAuthCertificate
|
||||
outboundOptions.Certificate = tlsAuthCertificate
|
||||
outboundOptions.PrivateKey = tlsAuthPrivateKey
|
||||
inboundOptions.EchKey = echKeyPem
|
||||
outboundOptions.ECHOpts = outbound.ECHOptions{
|
||||
Enable: true,
|
||||
Config: echConfigBase64,
|
||||
}
|
||||
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInboundVMess_TLS(t *testing.T) {
|
||||
inboundOptions := inbound.VmessOption{
|
||||
Certificate: tlsCertificate,
|
||||
PrivateKey: tlsPrivateKey,
|
||||
}
|
||||
outboundOptions := outbound.VmessOption{
|
||||
TLS: true,
|
||||
Fingerprint: tlsFingerprint,
|
||||
}
|
||||
testInboundVMessTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundVMess_Ws(t *testing.T) {
|
||||
@@ -169,17 +197,7 @@ func TestInboundVMess_Wss1(t *testing.T) {
|
||||
Path: "/ws",
|
||||
},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundVMessTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundVMess_Wss2(t *testing.T) {
|
||||
@@ -197,17 +215,7 @@ func TestInboundVMess_Wss2(t *testing.T) {
|
||||
Path: "/ws",
|
||||
},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundVMessTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundVMess_Grpc1(t *testing.T) {
|
||||
@@ -222,17 +230,7 @@ func TestInboundVMess_Grpc1(t *testing.T) {
|
||||
Network: "grpc",
|
||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundVMessTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundVMess_Grpc2(t *testing.T) {
|
||||
@@ -248,17 +246,7 @@ func TestInboundVMess_Grpc2(t *testing.T) {
|
||||
Network: "grpc",
|
||||
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||
}
|
||||
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)
|
||||
})
|
||||
testInboundVMessTLS(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundVMess_Reality(t *testing.T) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/metacubex/mihomo/listener/http"
|
||||
"github.com/metacubex/mihomo/listener/reality"
|
||||
"github.com/metacubex/mihomo/listener/socks"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
"github.com/metacubex/mihomo/transport/socks4"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
)
|
||||
@@ -61,7 +62,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig := &tlsC.Config{}
|
||||
tlsConfig := &tlsC.Config{Time: ntp.Now}
|
||||
var realityBuilder *reality.Builder
|
||||
|
||||
if config.Certificate != "" && config.PrivateKey != "" {
|
||||
@@ -78,10 +79,26 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
||||
}
|
||||
}
|
||||
}
|
||||
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
|
||||
if len(config.ClientAuthCert) > 0 {
|
||||
if tlsConfig.ClientAuth == tlsC.NoClientCert {
|
||||
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
|
||||
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ClientCAs = pool
|
||||
}
|
||||
if config.RealityConfig.PrivateKey != "" {
|
||||
if tlsConfig.Certificates != nil {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
if tlsConfig.ClientAuth != tlsC.NoClientCert {
|
||||
return nil, errors.New("client-auth is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
@@ -103,7 +104,7 @@ func (h *ListenerHandler) ParseSpecialFqdn(ctx context.Context, conn net.Conn, m
|
||||
case mux.Destination.Fqdn:
|
||||
return h.muxService.NewConnection(ctx, conn, UpstreamMetadata(metadata))
|
||||
case vmess.MuxDestination.Fqdn:
|
||||
return vmess.HandleMuxConnection(ctx, conn, h)
|
||||
return vmess.HandleMuxConnection(ctx, conn, metadata, h)
|
||||
case uot.MagicAddress:
|
||||
request, err := uot.ReadRequest(conn)
|
||||
if err != nil {
|
||||
@@ -150,6 +151,8 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
|
||||
conn = packetaddr.NewConn(bufio.NewNetPacketConn(conn), M.Socksaddr{})
|
||||
}
|
||||
|
||||
connID := utils.NewUUIDV4().String() // make a new SNAT key
|
||||
|
||||
defer func() { _ = conn.Close() }()
|
||||
mutex := sync.Mutex{}
|
||||
writer := bufio.NewNetPacketWriter(conn) // a new interface to set nil in defer
|
||||
@@ -192,6 +195,7 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
|
||||
lAddr: conn.LocalAddr(),
|
||||
buff: buff,
|
||||
}
|
||||
cPacket.rAddr = N.NewCustomAddr(h.Type.String(), connID, cPacket.rAddr) // for tunnel's handleUDPConn
|
||||
if lAddr := getInAddr(ctx); lAddr != nil {
|
||||
cPacket.lAddr = lAddr
|
||||
}
|
||||
@@ -210,13 +214,11 @@ func (h *ListenerHandler) NewPacket(ctx context.Context, key netip.AddrPort, buf
|
||||
cPacket := &packet{
|
||||
writer: &writer,
|
||||
mutex: &mutex,
|
||||
rAddr: metadata.Source.UDPAddr(),
|
||||
rAddr: metadata.Source.UDPAddr(), // TODO: using key argument to make a SNAT key
|
||||
buff: buffer,
|
||||
}
|
||||
if conn, ok := common.Cast[localAddr](writer); ok {
|
||||
cPacket.rAddr = conn.LocalAddr()
|
||||
} else {
|
||||
cPacket.rAddr = metadata.Source.UDPAddr() // tun does not have real inAddr
|
||||
if conn, ok := common.Cast[localAddr](writer); ok { // tun does not have real inAddr
|
||||
cPacket.lAddr = conn.LocalAddr()
|
||||
}
|
||||
h.handlePacket(ctx, cPacket, metadata.Source, metadata.Destination)
|
||||
}
|
||||
|
||||
@@ -12,11 +12,14 @@ import (
|
||||
func (h *ListenerHandler) HandleSocket(target socks5.Addr, conn net.Conn, _additions ...inbound.Addition) {
|
||||
conn, metadata := inbound.NewSocket(target, conn, h.Type, h.Additions...)
|
||||
if h.IsSpecialFqdn(metadata.Host) {
|
||||
_ = h.ParseSpecialFqdn(
|
||||
err := h.ParseSpecialFqdn(
|
||||
WithAdditions(context.Background(), _additions...),
|
||||
conn,
|
||||
ConvertMetadata(metadata),
|
||||
)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
}
|
||||
} else {
|
||||
inbound.ApplyAdditions(metadata, _additions...)
|
||||
h.Tunnel.HandleTCPConn(conn, metadata)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
|
||||
"github.com/metacubex/sing-quic/hysteria2"
|
||||
|
||||
@@ -61,9 +62,23 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig := &tlsC.Config{
|
||||
Time: ntp.Now,
|
||||
MinVersion: tlsC.VersionTLS13,
|
||||
}
|
||||
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
|
||||
if len(config.ClientAuthCert) > 0 {
|
||||
if tlsConfig.ClientAuth == tlsC.NoClientCert {
|
||||
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
|
||||
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ClientCAs = pool
|
||||
}
|
||||
|
||||
if config.EchKey != "" {
|
||||
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
type ListenerHandler struct {
|
||||
*sing.ListenerHandler
|
||||
DnsAdds []netip.AddrPort
|
||||
DisableICMPForwarding bool
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) ShouldHijackDns(targetAddr netip.AddrPort) bool {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package sing_tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
tun "github.com/metacubex/sing-tun"
|
||||
"github.com/metacubex/sing-tun/ping"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
N "github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
func (h *ListenerHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
switch network {
|
||||
case N.NetworkICMP: // our fork only send those type to PrepareConnection now
|
||||
if h.DisableICMPForwarding || resolver.IsFakeIP(destination.Addr) { // skip fakeip and if ICMP handling is disabled
|
||||
log.Infoln("[ICMP] %s %s --> %s using fake ping echo", network, source, destination)
|
||||
return nil, nil
|
||||
}
|
||||
log.Infoln("[ICMP] %s %s --> %s using DIRECT", network, source, destination)
|
||||
directRouteDestination, err := ping.ConnectDestination(context.TODO(), log.SingLogger, dialer.ICMPControl(destination.Addr), destination.Addr, routeContext, timeout)
|
||||
if err != nil {
|
||||
log.Warnln("[ICMP] failed to connect to %s", destination)
|
||||
return nil, err
|
||||
}
|
||||
log.Debugln("[ICMP] success connect to %s", destination)
|
||||
return directRouteDestination, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
@@ -174,11 +175,11 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
if tunMTU == 0 {
|
||||
tunMTU = 9000
|
||||
}
|
||||
var udpTimeout int64
|
||||
var udpTimeout time.Duration
|
||||
if options.UDPTimeout != 0 {
|
||||
udpTimeout = options.UDPTimeout
|
||||
udpTimeout = time.Second * time.Duration(options.UDPTimeout)
|
||||
} else {
|
||||
udpTimeout = int64(sing.UDPTimeout.Seconds())
|
||||
udpTimeout = sing.UDPTimeout
|
||||
}
|
||||
tableIndex := options.IPRoute2TableIndex
|
||||
if tableIndex == 0 {
|
||||
@@ -268,6 +269,7 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
handler := &ListenerHandler{
|
||||
ListenerHandler: h,
|
||||
DnsAdds: dnsAdds,
|
||||
DisableICMPForwarding: options.DisableICMPForwarding,
|
||||
}
|
||||
l = &Listener{
|
||||
closed: false,
|
||||
|
||||
@@ -5,9 +5,7 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
@@ -17,48 +15,20 @@ import (
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/reality"
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
"github.com/metacubex/sing-vmess/vless"
|
||||
"github.com/metacubex/sing/common"
|
||||
"github.com/metacubex/sing/common/metadata"
|
||||
"github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
func init() {
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := network.CastReader[*reality.Conn](conn) // *utls.Conn
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn).Elem(), unsafe.Pointer(tlsConn)
|
||||
})
|
||||
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := network.CastReader[*tlsC.UConn](conn) // *utls.UConn
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), unsafe.Pointer(tlsConn.Conn)
|
||||
})
|
||||
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := network.CastReader[*encryption.CommonConn](conn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, tlsConn.Conn, reflect.TypeOf(tlsConn).Elem(), unsafe.Pointer(tlsConn)
|
||||
})
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
closed bool
|
||||
config LC.VlessServer
|
||||
listeners []net.Listener
|
||||
service *vless.Service[string]
|
||||
service *Service[string]
|
||||
decryption *encryption.ServerInstance
|
||||
}
|
||||
|
||||
@@ -79,7 +49,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := vless.NewService[string](log.SingLogger, h)
|
||||
service := NewService[string](h)
|
||||
service.UpdateUsers(
|
||||
common.Map(config.Users, func(it LC.VlessUser) string {
|
||||
return it.Username
|
||||
@@ -106,7 +76,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}()
|
||||
}
|
||||
|
||||
tlsConfig := &tlsC.Config{}
|
||||
tlsConfig := &tlsC.Config{Time: ntp.Now}
|
||||
var realityBuilder *reality.Builder
|
||||
var httpServer http.Server
|
||||
|
||||
@@ -124,10 +94,26 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
}
|
||||
}
|
||||
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
|
||||
if len(config.ClientAuthCert) > 0 {
|
||||
if tlsConfig.ClientAuth == tlsC.NoClientCert {
|
||||
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
|
||||
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ClientCAs = pool
|
||||
}
|
||||
if config.RealityConfig.PrivateKey != "" {
|
||||
if tlsConfig.Certificates != nil {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
if tlsConfig.ClientAuth != tlsC.NoClientCert {
|
||||
return nil, errors.New("client-auth is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -230,7 +216,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
|
||||
ctx := sing.WithAdditions(context.TODO(), additions...)
|
||||
if l.decryption != nil {
|
||||
var err error
|
||||
conn, err = l.decryption.Handshake(conn)
|
||||
conn, err = l.decryption.Handshake(conn, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
package sing_vless
|
||||
|
||||
// copy and modify from https://github.com/SagerNet/sing-vmess/tree/3c1cf255413250b09a57e4ecdf1def1fa505e3cc/vless
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/metacubex/mihomo/transport/vless"
|
||||
"github.com/metacubex/mihomo/transport/vless/vision"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/sing-vmess"
|
||||
"github.com/metacubex/sing/common/auth"
|
||||
"github.com/metacubex/sing/common/buf"
|
||||
"github.com/metacubex/sing/common/bufio"
|
||||
E "github.com/metacubex/sing/common/exceptions"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
N "github.com/metacubex/sing/common/network"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type Service[T comparable] struct {
|
||||
userMap map[[16]byte]T
|
||||
userFlow map[T]string
|
||||
handler Handler
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
N.TCPConnectionHandler
|
||||
N.UDPConnectionHandler
|
||||
E.Handler
|
||||
}
|
||||
|
||||
func NewService[T comparable](handler Handler) *Service[T] {
|
||||
return &Service[T]{
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service[T]) UpdateUsers(userList []T, userUUIDList []string, userFlowList []string) {
|
||||
userMap := make(map[[16]byte]T)
|
||||
userFlowMap := make(map[T]string)
|
||||
for i, userName := range userList {
|
||||
userID, err := uuid.FromString(userUUIDList[i])
|
||||
if err != nil {
|
||||
userID = uuid.NewV5(uuid.Nil, userUUIDList[i])
|
||||
}
|
||||
userMap[userID] = userName
|
||||
userFlowMap[userName] = userFlowList[i]
|
||||
}
|
||||
s.userMap = userMap
|
||||
s.userFlow = userFlowMap
|
||||
}
|
||||
|
||||
var _ N.TCPConnectionHandler = (*Service[int])(nil)
|
||||
|
||||
func (s *Service[T]) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
var version uint8
|
||||
err := binary.Read(conn, binary.BigEndian, &version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if version != vless.Version {
|
||||
return E.New("unknown version: ", version)
|
||||
}
|
||||
|
||||
var requestUUID [16]byte
|
||||
_, err = io.ReadFull(conn, requestUUID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var addonsLen uint8
|
||||
err = binary.Read(conn, binary.BigEndian, &addonsLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var addons vless.Addons
|
||||
if addonsLen > 0 {
|
||||
addonsBytes := make([]byte, addonsLen)
|
||||
_, err = io.ReadFull(conn, addonsBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = proto.Unmarshal(addonsBytes, &addons)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var command byte
|
||||
err = binary.Read(conn, binary.BigEndian, &command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var destination M.Socksaddr
|
||||
if command != vless.CommandMux {
|
||||
destination, err = vmess.AddressSerializer.ReadAddrPort(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
user, loaded := s.userMap[requestUUID]
|
||||
if !loaded {
|
||||
return E.New("unknown UUID: ", uuid.FromBytesOrNil(requestUUID[:]))
|
||||
}
|
||||
ctx = auth.ContextWithUser(ctx, user)
|
||||
metadata.Destination = destination
|
||||
|
||||
userFlow := s.userFlow[user]
|
||||
requestFlow := addons.Flow
|
||||
if requestFlow != userFlow && requestFlow != "" {
|
||||
return E.New("flow mismatch: expected ", flowName(userFlow), ", but got ", flowName(requestFlow))
|
||||
}
|
||||
|
||||
responseConn := &serverConn{ExtendedConn: bufio.NewExtendedConn(conn)}
|
||||
switch requestFlow {
|
||||
case vless.XRV:
|
||||
conn, err = vision.NewConn(responseConn, conn, requestUUID)
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize vision")
|
||||
}
|
||||
case "":
|
||||
conn = responseConn
|
||||
default:
|
||||
return E.New("unknown flow: ", requestFlow)
|
||||
}
|
||||
switch command {
|
||||
case vless.CommandTCP:
|
||||
return s.handler.NewConnection(ctx, conn, metadata)
|
||||
case vless.CommandUDP:
|
||||
if requestFlow == vless.XRV {
|
||||
return E.New(vless.XRV, " flow does not support UDP")
|
||||
}
|
||||
return s.handler.NewPacketConnection(ctx, &serverPacketConn{ExtendedConn: bufio.NewExtendedConn(conn), destination: destination}, metadata)
|
||||
case vless.CommandMux:
|
||||
return vmess.HandleMuxConnection(ctx, conn, metadata, s.handler)
|
||||
default:
|
||||
return E.New("unknown command: ", command)
|
||||
}
|
||||
}
|
||||
|
||||
func flowName(value string) string {
|
||||
if value == "" {
|
||||
return "none"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
type serverConn struct {
|
||||
N.ExtendedConn
|
||||
responseWritten bool
|
||||
}
|
||||
|
||||
func (c *serverConn) Write(b []byte) (n int, err error) {
|
||||
if !c.responseWritten {
|
||||
buffer := buf.NewSize(2 + len(b))
|
||||
buffer.WriteByte(vless.Version)
|
||||
buffer.WriteByte(0)
|
||||
buffer.Write(b)
|
||||
_, err = c.ExtendedConn.Write(buffer.Bytes())
|
||||
buffer.Release()
|
||||
if err == nil {
|
||||
n = len(b)
|
||||
}
|
||||
c.responseWritten = true
|
||||
return
|
||||
}
|
||||
return c.ExtendedConn.Write(b)
|
||||
}
|
||||
|
||||
func (c *serverConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
if !c.responseWritten {
|
||||
header := buffer.ExtendHeader(2)
|
||||
header[0] = vless.Version
|
||||
header[1] = 0
|
||||
c.responseWritten = true
|
||||
}
|
||||
return c.ExtendedConn.WriteBuffer(buffer)
|
||||
}
|
||||
|
||||
func (c *serverConn) FrontHeadroom() int {
|
||||
if c.responseWritten {
|
||||
return 0
|
||||
}
|
||||
return 2
|
||||
}
|
||||
|
||||
func (c *serverConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *serverConn) WriterReplaceable() bool {
|
||||
return c.responseWritten
|
||||
}
|
||||
|
||||
func (c *serverConn) NeedAdditionalReadDeadline() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *serverConn) Upstream() any {
|
||||
return c.ExtendedConn
|
||||
}
|
||||
|
||||
type serverPacketConn struct {
|
||||
N.ExtendedConn
|
||||
destination M.Socksaddr
|
||||
readWaitOptions N.ReadWaitOptions
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {
|
||||
c.readWaitOptions = options
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) WaitReadPacket() (buffer *buf.Buffer, destination M.Socksaddr, err error) {
|
||||
var packetLen uint16
|
||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &packetLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buffer = c.readWaitOptions.NewPacketBuffer()
|
||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(packetLen))
|
||||
if err != nil {
|
||||
buffer.Release()
|
||||
return
|
||||
}
|
||||
c.readWaitOptions.PostReturn(buffer)
|
||||
|
||||
destination = c.destination
|
||||
return
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
var packetLen uint16
|
||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &packetLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(p) < int(packetLen) {
|
||||
err = io.ErrShortBuffer
|
||||
return
|
||||
}
|
||||
n, err = io.ReadFull(c.ExtendedConn, p[:packetLen])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.destination.IsFqdn() {
|
||||
addr = c.destination
|
||||
} else {
|
||||
addr = c.destination.UDPAddr()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(p)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return c.ExtendedConn.Write(p)
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||
var packetLen uint16
|
||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &packetLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(packetLen))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
destination = c.destination
|
||||
return
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
packetLen := buffer.Len()
|
||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(packetLen))
|
||||
return c.ExtendedConn.WriteBuffer(buffer)
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) FrontHeadroom() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) NeedAdditionalReadDeadline() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) Upstream() any {
|
||||
return c.ExtendedConn
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
|
||||
sl = &Listener{false, config, nil, service}
|
||||
|
||||
tlsConfig := &tlsC.Config{}
|
||||
tlsConfig := &tlsC.Config{Time: ntp.Now}
|
||||
var realityBuilder *reality.Builder
|
||||
var httpServer http.Server
|
||||
|
||||
@@ -94,10 +94,26 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
}
|
||||
}
|
||||
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
|
||||
if len(config.ClientAuthCert) > 0 {
|
||||
if tlsConfig.ClientAuth == tlsC.NoClientCert {
|
||||
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
|
||||
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ClientCAs = pool
|
||||
}
|
||||
if config.RealityConfig.PrivateKey != "" {
|
||||
if tlsConfig.Certificates != nil {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
if tlsConfig.ClientAuth != tlsC.NoClientCert {
|
||||
return nil, errors.New("client-auth is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
authStore "github.com/metacubex/mihomo/listener/auth"
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/reality"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
"github.com/metacubex/mihomo/transport/socks4"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
)
|
||||
@@ -60,7 +61,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig := &tlsC.Config{}
|
||||
tlsConfig := &tlsC.Config{Time: ntp.Now}
|
||||
var realityBuilder *reality.Builder
|
||||
|
||||
if config.Certificate != "" && config.PrivateKey != "" {
|
||||
@@ -77,10 +78,26 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
||||
}
|
||||
}
|
||||
}
|
||||
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
|
||||
if len(config.ClientAuthCert) > 0 {
|
||||
if tlsConfig.ClientAuth == tlsC.NoClientCert {
|
||||
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
|
||||
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ClientCAs = pool
|
||||
}
|
||||
if config.RealityConfig.PrivateKey != "" {
|
||||
if tlsConfig.Certificates != nil {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
if tlsConfig.ClientAuth != tlsC.NoClientCert {
|
||||
return nil, errors.New("client-auth is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/reality"
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/shadowsocks/core"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
@@ -70,7 +71,7 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
sl = &Listener{false, config, nil, keys, pickCipher, h}
|
||||
|
||||
tlsConfig := &tlsC.Config{}
|
||||
tlsConfig := &tlsC.Config{Time: ntp.Now}
|
||||
var realityBuilder *reality.Builder
|
||||
var httpServer http.Server
|
||||
|
||||
@@ -88,10 +89,26 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
}
|
||||
}
|
||||
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
|
||||
if len(config.ClientAuthCert) > 0 {
|
||||
if tlsConfig.ClientAuth == tlsC.NoClientCert {
|
||||
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
|
||||
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ClientCAs = pool
|
||||
}
|
||||
if config.RealityConfig.PrivateKey != "" {
|
||||
if tlsConfig.Certificates != nil {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
if tlsConfig.ClientAuth != tlsC.NoClientCert {
|
||||
return nil, errors.New("client-auth is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
"github.com/metacubex/mihomo/transport/tuic"
|
||||
|
||||
@@ -53,9 +54,23 @@ func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) (
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig := &tlsC.Config{
|
||||
Time: ntp.Now,
|
||||
MinVersion: tlsC.VersionTLS13,
|
||||
}
|
||||
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
|
||||
if len(config.ClientAuthCert) > 0 {
|
||||
if tlsConfig.ClientAuth == tlsC.NoClientCert {
|
||||
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
|
||||
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ClientCAs = pool
|
||||
}
|
||||
|
||||
if config.EchKey != "" {
|
||||
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||
|
||||
@@ -62,7 +62,19 @@ func main() {
|
||||
// Defensive programming: panic when code mistakenly calls net.DefaultResolver
|
||||
net.DefaultResolver.PreferGo = true
|
||||
net.DefaultResolver.Dial = func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
panic("should never be called")
|
||||
//panic("should never be called")
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n := runtime.Stack(buf, true)
|
||||
if n < len(buf) {
|
||||
buf = buf[:n]
|
||||
break
|
||||
}
|
||||
buf = make([]byte, 2*len(buf))
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "panic: should never be called\n\n%s", buf) // always print all goroutine stack
|
||||
os.Exit(2)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {}))
|
||||
|
||||
@@ -3,6 +3,7 @@ package ntp
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
@@ -13,8 +14,8 @@ import (
|
||||
"github.com/metacubex/sing/common/ntp"
|
||||
)
|
||||
|
||||
var offset time.Duration
|
||||
var service *Service
|
||||
var globalSrv atomic.Pointer[Service]
|
||||
var globalMu sync.Mutex
|
||||
|
||||
type Service struct {
|
||||
server M.Socksaddr
|
||||
@@ -22,17 +23,21 @@ type Service struct {
|
||||
ticker *time.Ticker
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
mu sync.Mutex
|
||||
offset atomic.Int64 // [time.Duration]
|
||||
syncSystemTime bool
|
||||
running bool
|
||||
}
|
||||
|
||||
func ReCreateNTPService(server string, interval time.Duration, dialerProxy string, syncSystemTime bool) {
|
||||
if service != nil {
|
||||
globalMu.Lock()
|
||||
defer globalMu.Unlock()
|
||||
if service := globalSrv.Swap(nil); service != nil {
|
||||
service.Stop()
|
||||
}
|
||||
if server == "" || interval <= 0 {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
service = &Service{
|
||||
service := &Service{
|
||||
server: M.ParseSocksaddr(server),
|
||||
dialer: proxydialer.NewByNameSingDialer(dialerProxy, dialer.NewDialer()),
|
||||
ticker: time.NewTicker(interval * time.Minute),
|
||||
@@ -41,57 +46,41 @@ func ReCreateNTPService(server string, interval time.Duration, dialerProxy strin
|
||||
syncSystemTime: syncSystemTime,
|
||||
}
|
||||
service.Start()
|
||||
globalSrv.Store(service)
|
||||
}
|
||||
|
||||
func (srv *Service) Start() {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
log.Infoln("NTP service start, sync system time is %t", srv.syncSystemTime)
|
||||
err := srv.update()
|
||||
if err != nil {
|
||||
log.Errorln("Initialize NTP time failed: %s", err)
|
||||
return
|
||||
}
|
||||
service.running = true
|
||||
go srv.loopUpdate()
|
||||
}
|
||||
|
||||
func (srv *Service) Stop() {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
if service.running {
|
||||
srv.ticker.Stop()
|
||||
log.Infoln("NTP service stop")
|
||||
srv.cancel()
|
||||
service.running = false
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Service) Running() bool {
|
||||
if srv == nil {
|
||||
return false
|
||||
}
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
return srv.running
|
||||
func (srv *Service) Offset() time.Duration {
|
||||
return time.Duration(srv.offset.Load())
|
||||
}
|
||||
|
||||
func (srv *Service) update() error {
|
||||
var response *ntp.Response
|
||||
var err error
|
||||
for i := 0; i < 3; i++ {
|
||||
if response, err = ntp.Exchange(context.Background(), srv.dialer, srv.server); err == nil {
|
||||
break
|
||||
response, err = ntp.Exchange(srv.ctx, srv.dialer, srv.server)
|
||||
if err != nil {
|
||||
if srv.ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
if i == 2 {
|
||||
return err
|
||||
continue
|
||||
}
|
||||
}
|
||||
offset = response.ClockOffset
|
||||
offset := response.ClockOffset
|
||||
if offset > time.Duration(0) {
|
||||
log.Infoln("System clock is ahead of NTP time by %s", offset)
|
||||
} else if offset < time.Duration(0) {
|
||||
log.Infoln("System clock is behind NTP time by %s", -offset)
|
||||
}
|
||||
srv.offset.Store(int64(offset))
|
||||
if srv.syncSystemTime {
|
||||
timeNow := response.Time
|
||||
syncErr := setSystemTime(timeNow)
|
||||
@@ -103,26 +92,32 @@ func (srv *Service) update() error {
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (srv *Service) loopUpdate() {
|
||||
defer srv.offset.Store(0)
|
||||
defer srv.ticker.Stop()
|
||||
for {
|
||||
err := srv.update()
|
||||
if err != nil {
|
||||
log.Warnln("Sync time failed: %s", err)
|
||||
}
|
||||
select {
|
||||
case <-srv.ctx.Done():
|
||||
return
|
||||
case <-srv.ticker.C:
|
||||
}
|
||||
err := srv.update()
|
||||
if err != nil {
|
||||
log.Warnln("Sync time failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Now() time.Time {
|
||||
now := time.Now()
|
||||
if service.Running() && offset.Abs() > 0 {
|
||||
if service := globalSrv.Load(); service != nil {
|
||||
if offset := service.Offset(); offset.Abs() > 0 {
|
||||
now = now.Add(offset)
|
||||
}
|
||||
}
|
||||
return now
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ type Option struct {
|
||||
ECHConfig *ech.Config
|
||||
SkipCertVerify bool
|
||||
Fingerprint string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
Mux bool
|
||||
}
|
||||
|
||||
@@ -57,15 +59,19 @@ func NewGostWebsocket(ctx context.Context, conn net.Conn, option *Option) (net.C
|
||||
Headers: header,
|
||||
}
|
||||
|
||||
var err error
|
||||
if option.TLS {
|
||||
config.TLS = true
|
||||
tlsConfig := &tls.Config{
|
||||
config.TLSConfig, err = ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: option.Host,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
var err error
|
||||
config.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||
},
|
||||
Fingerprint: option.Fingerprint,
|
||||
Certificate: option.Certificate,
|
||||
PrivateKey: option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -75,7 +81,6 @@ func NewGostWebsocket(ctx context.Context, conn net.Conn, option *Option) (net.C
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
conn, err = vmess.StreamWebsocketConn(ctx, conn, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -145,7 +145,7 @@ func makeClientHelloMsg(data []byte, server string) []byte {
|
||||
buf.Write([]byte{0x03, 0x03})
|
||||
|
||||
// random with timestamp, sid len, sid
|
||||
binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix()))
|
||||
binary.Write(buf, binary.BigEndian, uint32(ntp.Now().Unix()))
|
||||
buf.Write(random)
|
||||
buf.WriteByte(32)
|
||||
buf.Write(sessionID)
|
||||
|
||||
@@ -26,6 +26,8 @@ type ShadowTLSOption struct {
|
||||
Password string
|
||||
Host string
|
||||
Fingerprint string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
ClientFingerprint string
|
||||
SkipCertVerify bool
|
||||
Version int
|
||||
@@ -33,22 +35,25 @@ type ShadowTLSOption struct {
|
||||
}
|
||||
|
||||
func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (net.Conn, error) {
|
||||
tlsConfig := &tls.Config{
|
||||
tlsConfig, err := ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
NextProtos: option.ALPN,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: option.Host,
|
||||
}
|
||||
if option.Version == 1 {
|
||||
tlsConfig.MaxVersion = tls.VersionTLS12 // ShadowTLS v1 only support TLS 1.2
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||
},
|
||||
Fingerprint: option.Fingerprint,
|
||||
Certificate: option.Certificate,
|
||||
PrivateKey: option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if option.Version == 1 {
|
||||
tlsConfig.MaxVersion = tls.VersionTLS12 // ShadowTLS v1 only support TLS 1.2
|
||||
}
|
||||
|
||||
tlsHandshake := uTLSHandshakeFunc(tlsConfig, option.ClientFingerprint, option.Version)
|
||||
client, err := shadowtls.NewClient(shadowtls.ClientConfig{
|
||||
Version: option.Version,
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
"github.com/metacubex/mihomo/transport/ssr/tools"
|
||||
|
||||
"github.com/metacubex/randv2"
|
||||
@@ -182,7 +182,7 @@ func packData(buf *bytes.Buffer, data []byte) {
|
||||
}
|
||||
|
||||
func (t *tls12Ticket) packAuthData(buf *bytes.Buffer) {
|
||||
binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix()))
|
||||
binary.Write(buf, binary.BigEndian, uint32(ntp.Now().Unix()))
|
||||
tools.AppendRandBytes(buf, 18)
|
||||
buf.Write(t.hmacSHA1(buf.Bytes()[buf.Len()-22:])[:10])
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
"github.com/metacubex/mihomo/transport/shadowsocks/core"
|
||||
|
||||
"github.com/metacubex/randv2"
|
||||
@@ -49,7 +49,7 @@ func (a *authData) next() *authData {
|
||||
}
|
||||
|
||||
func (a *authData) putAuthData(buf *bytes.Buffer) {
|
||||
binary.Write(buf, binary.LittleEndian, uint32(time.Now().Unix()))
|
||||
binary.Write(buf, binary.LittleEndian, uint32(ntp.Now().Unix()))
|
||||
buf.Write(a.clientID[:])
|
||||
binary.Write(buf, binary.LittleEndian, a.connectionID)
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func (a *authData) putAuthData(buf *bytes.Buffer) {
|
||||
func (a *authData) putEncryptedData(b *bytes.Buffer, userKey []byte, paddings [2]int, salt string) error {
|
||||
encrypt := pool.Get(16)
|
||||
defer pool.Put(encrypt)
|
||||
binary.LittleEndian.PutUint32(encrypt, uint32(time.Now().Unix()))
|
||||
binary.LittleEndian.PutUint32(encrypt, uint32(ntp.Now().Unix()))
|
||||
copy(encrypt[4:], a.clientID[:])
|
||||
binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID)
|
||||
binary.LittleEndian.PutUint16(encrypt[12:], uint16(paddings[0]))
|
||||
|
||||
@@ -87,7 +87,11 @@ func (s *serverHandler) handle() {
|
||||
_ = s.handleMessage()
|
||||
}()
|
||||
|
||||
<-s.quicConn.HandshakeComplete()
|
||||
select {
|
||||
case <-s.quicConn.HandshakeComplete(): // this chan maybe not closed if handshake never complete
|
||||
case <-time.After(s.quicConn.Config().HandshakeIdleTimeout): // HandshakeIdleTimeout in real conn.Config() never be zero
|
||||
}
|
||||
|
||||
time.AfterFunc(s.AuthenticationTimeout, func() {
|
||||
if s.v4Handler != nil {
|
||||
if s.v4Handler.AuthOk() {
|
||||
|
||||
@@ -21,6 +21,8 @@ type Option struct {
|
||||
ECHConfig *ech.Config
|
||||
SkipCertVerify bool
|
||||
Fingerprint string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
Mux bool
|
||||
V2rayHttpUpgrade bool
|
||||
V2rayHttpUpgradeFastOpen bool
|
||||
@@ -43,15 +45,19 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn,
|
||||
Headers: header,
|
||||
}
|
||||
|
||||
var err error
|
||||
if option.TLS {
|
||||
config.TLS = true
|
||||
tlsConfig := &tls.Config{
|
||||
config.TLSConfig, err = ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: option.Host,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
var err error
|
||||
config.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||
},
|
||||
Fingerprint: option.Fingerprint,
|
||||
Certificate: option.Certificate,
|
||||
PrivateKey: option.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -61,7 +67,6 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn,
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
conn, err = vmess.StreamWebsocketConn(ctx, conn, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package vless
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func ReadAddons(data []byte) (*Addons, error) {
|
||||
reader := bytes.NewReader(data)
|
||||
var addons Addons
|
||||
for reader.Len() > 0 {
|
||||
tag, err := binary.ReadUvarint(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
number, typ := int32(tag>>3), int8(tag&7)
|
||||
switch typ {
|
||||
case 0: // VARINT
|
||||
_, err = binary.ReadUvarint(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 5: // I32
|
||||
var i32 [4]byte
|
||||
_, err = io.ReadFull(reader, i32[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 1: // I64
|
||||
var i64 [8]byte
|
||||
_, err = io.ReadFull(reader, i64[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 2: // LEN
|
||||
var bytesLen uint64
|
||||
bytesLen, err = binary.ReadUvarint(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bytesData := make([]byte, bytesLen)
|
||||
_, err = io.ReadFull(reader, bytesData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch number {
|
||||
case 1:
|
||||
addons.Flow = string(bytesData)
|
||||
case 2:
|
||||
addons.Seed = bytesData
|
||||
}
|
||||
default: // group (3,4) has been deprecated we unneeded support
|
||||
return nil, fmt.Errorf("unknown protobuf message tag: %v", tag)
|
||||
}
|
||||
}
|
||||
return &addons, nil
|
||||
}
|
||||
|
||||
func WriteAddons(addons *Addons) []byte {
|
||||
var writer bytes.Buffer
|
||||
if len(addons.Flow) > 0 {
|
||||
WriteUvarint(&writer, (1<<3)|2) // (field << 3) bit-or wire_type encoded as uint32 varint
|
||||
WriteUvarint(&writer, uint64(len(addons.Flow)))
|
||||
writer.WriteString(addons.Flow)
|
||||
}
|
||||
if len(addons.Seed) > 0 {
|
||||
WriteUvarint(&writer, (2<<3)|2) // (field << 3) bit-or wire_type encoded as uint32 varint
|
||||
WriteUvarint(&writer, uint64(len(addons.Seed)))
|
||||
writer.Write(addons.Seed)
|
||||
}
|
||||
return writer.Bytes()
|
||||
}
|
||||
|
||||
func WriteUvarint(writer *bytes.Buffer, x uint64) {
|
||||
for x >= 0x80 {
|
||||
writer.WriteByte(byte(x) | 0x80)
|
||||
x >>= 7
|
||||
}
|
||||
writer.WriteByte(byte(x))
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package vless
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestAddons(t *testing.T) {
|
||||
var tests = []struct {
|
||||
flow string
|
||||
seed []byte
|
||||
}{
|
||||
{XRV, nil},
|
||||
{XRS, []byte{1, 2, 3}},
|
||||
{"", []byte{1, 2, 3}},
|
||||
{"", nil},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
t.Run("proto->handwritten", func(t *testing.T) {
|
||||
addons := new(Addons)
|
||||
addons.Flow = test.flow
|
||||
addons.Seed = test.seed
|
||||
|
||||
addonsBytes, err := proto.Marshal(addons)
|
||||
if err != nil {
|
||||
t.Errorf("error marshalling addons: %v", err)
|
||||
return
|
||||
}
|
||||
addons, err = ReadAddons(addonsBytes)
|
||||
if err != nil {
|
||||
t.Errorf("error reading addons: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if addons.Flow != test.flow {
|
||||
t.Errorf("got %v; want %v", addons.Flow, test.flow)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(addons.Seed, test.seed) {
|
||||
t.Errorf("got %v; want %v", addons.Seed, test.seed)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("handwritten->proto", func(t *testing.T) {
|
||||
addons := new(Addons)
|
||||
addons.Flow = test.flow
|
||||
addons.Seed = test.seed
|
||||
|
||||
addonsBytes := WriteAddons(addons)
|
||||
err := proto.Unmarshal(addonsBytes, addons)
|
||||
if err != nil {
|
||||
t.Errorf("error reading addons: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if addons.Flow != test.flow {
|
||||
t.Errorf("got %v; want %v", addons.Flow, test.flow)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(addons.Seed, test.seed) {
|
||||
t.Errorf("got %v; want %v", addons.Seed, test.seed)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("handwritten->handwritten", func(t *testing.T) {
|
||||
addons := new(Addons)
|
||||
addons.Flow = test.flow
|
||||
addons.Seed = test.seed
|
||||
|
||||
addonsBytes := WriteAddons(addons)
|
||||
addons, err := ReadAddons(addonsBytes)
|
||||
if err != nil {
|
||||
t.Errorf("error reading addons: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if addons.Flow != test.flow {
|
||||
t.Errorf("got %v; want %v", addons.Flow, test.flow)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(addons.Seed, test.seed) {
|
||||
t.Errorf("got %v; want %v", addons.Seed, test.seed)
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
@@ -16,17 +15,12 @@ import (
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
N.ExtendedWriter
|
||||
N.ExtendedReader
|
||||
net.Conn
|
||||
N.ExtendedConn
|
||||
dst *DstAddr
|
||||
id *uuid.UUID
|
||||
id uuid.UUID
|
||||
addons *Addons
|
||||
received bool
|
||||
|
||||
handshakeMutex sync.Mutex
|
||||
needHandshake bool
|
||||
err error
|
||||
sent bool
|
||||
}
|
||||
|
||||
func (vc *Conn) Read(b []byte) (int, error) {
|
||||
@@ -36,7 +30,7 @@ func (vc *Conn) Read(b []byte) (int, error) {
|
||||
}
|
||||
vc.received = true
|
||||
}
|
||||
return vc.ExtendedReader.Read(b)
|
||||
return vc.ExtendedConn.Read(b)
|
||||
}
|
||||
|
||||
func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error {
|
||||
@@ -46,58 +40,39 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error {
|
||||
}
|
||||
vc.received = true
|
||||
}
|
||||
return vc.ExtendedReader.ReadBuffer(buffer)
|
||||
return vc.ExtendedConn.ReadBuffer(buffer)
|
||||
}
|
||||
|
||||
func (vc *Conn) Write(p []byte) (int, error) {
|
||||
if vc.needHandshake {
|
||||
vc.handshakeMutex.Lock()
|
||||
if vc.needHandshake {
|
||||
vc.needHandshake = false
|
||||
if vc.sendRequest(p) {
|
||||
vc.handshakeMutex.Unlock()
|
||||
if vc.err != nil {
|
||||
return 0, vc.err
|
||||
if !vc.sent {
|
||||
if err := vc.sendRequest(p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(p), vc.err
|
||||
}
|
||||
if vc.err != nil {
|
||||
vc.handshakeMutex.Unlock()
|
||||
return 0, vc.err
|
||||
}
|
||||
}
|
||||
vc.handshakeMutex.Unlock()
|
||||
vc.sent = true
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
return vc.ExtendedWriter.Write(p)
|
||||
return vc.ExtendedConn.Write(p)
|
||||
}
|
||||
|
||||
func (vc *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
if vc.needHandshake {
|
||||
vc.handshakeMutex.Lock()
|
||||
if vc.needHandshake {
|
||||
vc.needHandshake = false
|
||||
if vc.sendRequest(buffer.Bytes()) {
|
||||
vc.handshakeMutex.Unlock()
|
||||
return vc.err
|
||||
if !vc.sent {
|
||||
if err := vc.sendRequest(buffer.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
if vc.err != nil {
|
||||
vc.handshakeMutex.Unlock()
|
||||
return vc.err
|
||||
}
|
||||
}
|
||||
vc.handshakeMutex.Unlock()
|
||||
vc.sent = true
|
||||
return nil
|
||||
}
|
||||
|
||||
return vc.ExtendedWriter.WriteBuffer(buffer)
|
||||
return vc.ExtendedConn.WriteBuffer(buffer)
|
||||
}
|
||||
|
||||
func (vc *Conn) sendRequest(p []byte) bool {
|
||||
func (vc *Conn) sendRequest(p []byte) (err error) {
|
||||
var addonsBytes []byte
|
||||
if vc.addons != nil {
|
||||
addonsBytes, vc.err = proto.Marshal(vc.addons)
|
||||
if vc.err != nil {
|
||||
return true
|
||||
addonsBytes, err = proto.Marshal(vc.addons)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,15 +116,15 @@ func (vc *Conn) sendRequest(p []byte) bool {
|
||||
|
||||
buf.Must(buf.Error(buffer.Write(p)))
|
||||
|
||||
_, vc.err = vc.ExtendedWriter.Write(buffer.Bytes())
|
||||
return true
|
||||
_, err = vc.ExtendedConn.Write(buffer.Bytes())
|
||||
return
|
||||
}
|
||||
|
||||
func (vc *Conn) recvResponse() error {
|
||||
func (vc *Conn) recvResponse() (err error) {
|
||||
var buffer [2]byte
|
||||
_, vc.err = io.ReadFull(vc.ExtendedReader, buffer[:])
|
||||
if vc.err != nil {
|
||||
return vc.err
|
||||
_, err = io.ReadFull(vc.ExtendedConn, buffer[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if buffer[0] != Version {
|
||||
@@ -158,29 +133,35 @@ func (vc *Conn) recvResponse() error {
|
||||
|
||||
length := int64(buffer[1])
|
||||
if length != 0 { // addon data length > 0
|
||||
io.CopyN(io.Discard, vc.ExtendedReader, length) // just discard
|
||||
io.CopyN(io.Discard, vc.ExtendedConn, length) // just discard
|
||||
}
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func (vc *Conn) Upstream() any {
|
||||
return vc.Conn
|
||||
return vc.ExtendedConn
|
||||
}
|
||||
|
||||
func (vc *Conn) ReaderReplaceable() bool {
|
||||
return vc.received
|
||||
}
|
||||
|
||||
func (vc *Conn) WriterReplaceable() bool {
|
||||
return vc.sent
|
||||
}
|
||||
|
||||
func (vc *Conn) NeedHandshake() bool {
|
||||
return vc.needHandshake
|
||||
return !vc.sent
|
||||
}
|
||||
|
||||
// newConn return a Conn instance
|
||||
func newConn(conn net.Conn, client *Client, dst *DstAddr) (net.Conn, error) {
|
||||
c := &Conn{
|
||||
ExtendedReader: N.NewExtendedReader(conn),
|
||||
ExtendedWriter: N.NewExtendedWriter(conn),
|
||||
Conn: conn,
|
||||
ExtendedConn: N.NewExtendedConn(conn),
|
||||
id: client.uuid,
|
||||
addons: client.Addons,
|
||||
dst: dst,
|
||||
needHandshake: true,
|
||||
}
|
||||
|
||||
if client.Addons != nil {
|
||||
@@ -190,7 +171,6 @@ func newConn(conn net.Conn, client *Client, dst *DstAddr) (net.Conn, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.addons = client.Addons
|
||||
return visionConn, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,23 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/blake3"
|
||||
"github.com/metacubex/utls/mlkem"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
var (
|
||||
// Keep in sync with crypto/tls/cipher_suites.go.
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
|
||||
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
|
||||
|
||||
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
|
||||
)
|
||||
|
||||
type ClientInstance struct {
|
||||
@@ -21,6 +33,8 @@ type ClientInstance struct {
|
||||
RelaysLength int
|
||||
XorMode uint32
|
||||
Seconds uint32
|
||||
PaddingLens [][3]int
|
||||
PaddingGaps [][3]int
|
||||
|
||||
RWLock sync.RWMutex
|
||||
Expire time.Time
|
||||
@@ -28,15 +42,13 @@ type ClientInstance struct {
|
||||
Ticket []byte
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (err error) {
|
||||
func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) {
|
||||
if i.NfsPKeys != nil {
|
||||
err = errors.New("already initialized")
|
||||
return
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
l := len(nfsPKeysBytes)
|
||||
if l == 0 {
|
||||
err = errors.New("empty nfsPKeysBytes")
|
||||
return
|
||||
return errors.New("empty nfsPKeysBytes")
|
||||
}
|
||||
i.NfsPKeys = make([]any, l)
|
||||
i.NfsPKeysBytes = nfsPKeysBytes
|
||||
@@ -58,18 +70,18 @@ func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (
|
||||
i.RelaysLength -= 32
|
||||
i.XorMode = xorMode
|
||||
i.Seconds = seconds
|
||||
return
|
||||
return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
if i.NfsPKeys == nil {
|
||||
return nil, errors.New("uninitialized")
|
||||
}
|
||||
c := &CommonConn{Conn: conn}
|
||||
c := NewCommonConn(conn, HasAESGCMHardwareSupport)
|
||||
|
||||
ivAndRealysLength := 16 + i.RelaysLength
|
||||
pfsKeyExchangeLength := 18 + 1184 + 32 + 16
|
||||
paddingLength := int(randBetween(100, 1000))
|
||||
paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)
|
||||
clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength)
|
||||
|
||||
iv := clientHello[:16]
|
||||
@@ -107,18 +119,18 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:])
|
||||
relays = relays[index+32:]
|
||||
}
|
||||
nfsGCM := NewGCM(iv, nfsKey)
|
||||
nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES)
|
||||
|
||||
if i.Seconds > 0 {
|
||||
i.RWLock.RLock()
|
||||
if time.Now().Before(i.Expire) {
|
||||
c.Client = i
|
||||
c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection
|
||||
nfsGCM.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil)
|
||||
nfsGCM.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil)
|
||||
nfsAEAD.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil)
|
||||
nfsAEAD.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil)
|
||||
i.RWLock.RUnlock()
|
||||
c.PreWrite = clientHello[:ivAndRealysLength+18+32]
|
||||
c.GCM = NewGCM(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey)
|
||||
c.AEAD = NewAEAD(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey, c.UseAES)
|
||||
if i.XorMode == 2 {
|
||||
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16)
|
||||
}
|
||||
@@ -128,26 +140,34 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
}
|
||||
|
||||
pfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength]
|
||||
nfsGCM.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil)
|
||||
nfsAEAD.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil)
|
||||
mlkem768DKey, _ := mlkem.GenerateKey768()
|
||||
x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
pfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...)
|
||||
nfsGCM.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil)
|
||||
nfsAEAD.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil)
|
||||
|
||||
padding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:]
|
||||
nfsGCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
|
||||
nfsGCM.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
|
||||
nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
|
||||
nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
|
||||
|
||||
if _, err := conn.Write(clientHello); err != nil {
|
||||
paddingLens[0] = ivAndRealysLength + pfsKeyExchangeLength + paddingLens[0]
|
||||
for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
|
||||
if l > 0 {
|
||||
if _, err := conn.Write(clientHello[:l]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
|
||||
clientHello = clientHello[l:]
|
||||
}
|
||||
if len(paddingGaps) > i {
|
||||
time.Sleep(paddingGaps[i])
|
||||
}
|
||||
}
|
||||
|
||||
encryptedPfsPublicKey := make([]byte, 1088+32+16)
|
||||
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nfsGCM.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil)
|
||||
nfsAEAD.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil)
|
||||
mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -164,14 +184,14 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
copy(pfsKey, mlkem768Key)
|
||||
copy(pfsKey[32:], x25519Key)
|
||||
c.UnitedKey = append(pfsKey, nfsKey...)
|
||||
c.GCM = NewGCM(pfsPublicKey, c.UnitedKey)
|
||||
c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1088+32], c.UnitedKey)
|
||||
c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)
|
||||
c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1088+32], c.UnitedKey, c.UseAES)
|
||||
|
||||
encryptedTicket := make([]byte, 32)
|
||||
if _, err := io.ReadFull(conn, encryptedTicket); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := c.PeerGCM.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil {
|
||||
if _, err := c.PeerAEAD.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seconds := DecodeLength(encryptedTicket)
|
||||
@@ -188,7 +208,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
if _, err := io.ReadFull(conn, encryptedLength); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := c.PeerGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
if _, err := c.PeerAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length := DecodeLength(encryptedLength[:2])
|
||||
|
||||
@@ -4,55 +4,68 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
|
||||
"github.com/metacubex/blake3"
|
||||
"github.com/metacubex/randv2"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
type CommonConn struct {
|
||||
net.Conn
|
||||
UseAES bool
|
||||
Client *ClientInstance
|
||||
UnitedKey []byte
|
||||
PreWrite []byte
|
||||
GCM *GCM
|
||||
AEAD *AEAD
|
||||
PeerAEAD *AEAD
|
||||
PeerPadding []byte
|
||||
PeerGCM *GCM
|
||||
rawInput bytes.Buffer // PeerInBytes
|
||||
input bytes.Reader // PeerCache
|
||||
}
|
||||
|
||||
func NewCommonConn(conn net.Conn, useAES bool) *CommonConn {
|
||||
return &CommonConn{
|
||||
Conn: conn,
|
||||
UseAES: useAES,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonConn) Write(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var data []byte
|
||||
outBytes := pool.Get(5 + 8192 + 16)
|
||||
defer pool.Put(outBytes)
|
||||
for n := 0; n < len(b); {
|
||||
b := b[n:]
|
||||
if len(b) > 8192 {
|
||||
b = b[:8192] // for avoiding another copy() in peer's Read()
|
||||
}
|
||||
n += len(b)
|
||||
data = make([]byte, 5+len(b)+16)
|
||||
EncodeHeader(data, len(b)+16)
|
||||
headerAndData := outBytes[:5+len(b)+16]
|
||||
EncodeHeader(headerAndData, len(b)+16)
|
||||
max := false
|
||||
if bytes.Equal(c.GCM.Nonce[:], MaxNonce) {
|
||||
if bytes.Equal(c.AEAD.Nonce[:], MaxNonce) {
|
||||
max = true
|
||||
}
|
||||
c.GCM.Seal(data[:5], nil, b, data[:5])
|
||||
c.AEAD.Seal(headerAndData[:5], nil, b, headerAndData[:5])
|
||||
if max {
|
||||
c.GCM = NewGCM(data[5:], c.UnitedKey)
|
||||
c.AEAD = NewAEAD(headerAndData, c.UnitedKey, c.UseAES)
|
||||
}
|
||||
if c.PreWrite != nil {
|
||||
data = append(c.PreWrite, data...)
|
||||
headerAndData = append(c.PreWrite, headerAndData...)
|
||||
c.PreWrite = nil
|
||||
}
|
||||
if _, err := c.Conn.Write(data); err != nil {
|
||||
if _, err := c.Conn.Write(headerAndData); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
@@ -63,12 +76,12 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if c.PeerGCM == nil { // client's 0-RTT
|
||||
if c.PeerAEAD == nil { // client's 0-RTT
|
||||
serverRandom := make([]byte, 16)
|
||||
if _, err := io.ReadFull(c.Conn, serverRandom); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.PeerGCM = NewGCM(serverRandom, c.UnitedKey)
|
||||
c.PeerAEAD = NewAEAD(serverRandom, c.UnitedKey, c.UseAES)
|
||||
if xorConn, ok := c.Conn.(*XorConn); ok {
|
||||
xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom)
|
||||
}
|
||||
@@ -77,7 +90,7 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
if _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := c.PeerGCM.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil {
|
||||
if _, err := c.PeerAEAD.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.PeerPadding = nil
|
||||
@@ -85,9 +98,13 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
if c.input.Len() > 0 {
|
||||
return c.input.Read(b)
|
||||
}
|
||||
h, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000
|
||||
peerHeader := [5]byte{}
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
l, err := DecodeHeader(peerHeader[:]) // l: 17~17000
|
||||
if err != nil {
|
||||
if c.Client != nil && strings.HasPrefix(err.Error(), "invalid header: ") { // client's 0-RTT
|
||||
if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT
|
||||
c.Client.RWLock.Lock()
|
||||
if bytes.HasPrefix(c.UnitedKey, c.Client.PfsKey) {
|
||||
c.Client.Expire = time.Now() // expired
|
||||
@@ -98,7 +115,10 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
c.Client = nil
|
||||
peerData := make([]byte, l)
|
||||
if c.rawInput.Cap() < l {
|
||||
c.rawInput.Grow(l) // no need to use sync.Pool, because we are always reading
|
||||
}
|
||||
peerData := c.rawInput.Bytes()[:l]
|
||||
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -106,13 +126,13 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
if len(dst) <= len(b) {
|
||||
dst = b[:len(dst)] // avoids another copy()
|
||||
}
|
||||
var newGCM *GCM
|
||||
if bytes.Equal(c.PeerGCM.Nonce[:], MaxNonce) {
|
||||
newGCM = NewGCM(peerData, c.UnitedKey)
|
||||
var newAEAD *AEAD
|
||||
if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) {
|
||||
newAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES)
|
||||
}
|
||||
_, err = c.PeerGCM.Open(dst[:0], nil, peerData, h)
|
||||
if newGCM != nil {
|
||||
c.PeerGCM = newGCM
|
||||
_, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:])
|
||||
if newAEAD != nil {
|
||||
c.PeerAEAD = newAEAD
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -124,28 +144,32 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
return len(dst), nil
|
||||
}
|
||||
|
||||
type GCM struct {
|
||||
type AEAD struct {
|
||||
cipher.AEAD
|
||||
Nonce [12]byte
|
||||
}
|
||||
|
||||
func NewGCM(ctx, key []byte) *GCM {
|
||||
func NewAEAD(ctx, key []byte, useAES bool) *AEAD {
|
||||
k := make([]byte, 32)
|
||||
blake3.DeriveKey(k, string(ctx), key)
|
||||
var aead cipher.AEAD
|
||||
if useAES {
|
||||
block, _ := aes.NewCipher(k)
|
||||
aead, _ := cipher.NewGCM(block)
|
||||
return &GCM{AEAD: aead}
|
||||
//chacha20poly1305.New()
|
||||
aead, _ = cipher.NewGCM(block)
|
||||
} else {
|
||||
aead, _ = chacha20poly1305.New(k)
|
||||
}
|
||||
return &AEAD{AEAD: aead}
|
||||
}
|
||||
|
||||
func (a *GCM) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
func (a *AEAD) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
if nonce == nil {
|
||||
nonce = IncreaseNonce(a.Nonce[:])
|
||||
}
|
||||
return a.AEAD.Seal(dst, nonce, plaintext, additionalData)
|
||||
}
|
||||
|
||||
func (a *GCM) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
func (a *AEAD) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
if nonce == nil {
|
||||
nonce = IncreaseNonce(a.Nonce[:])
|
||||
}
|
||||
@@ -180,41 +204,93 @@ func EncodeHeader(h []byte, l int) {
|
||||
h[4] = byte(l)
|
||||
}
|
||||
|
||||
var ErrInvalidHeader = errors.New("invalid header")
|
||||
|
||||
func DecodeHeader(h []byte) (l int, err error) {
|
||||
l = int(h[3])<<8 | int(h[4])
|
||||
if h[0] != 23 || h[1] != 3 || h[2] != 3 {
|
||||
l = 0
|
||||
}
|
||||
if l < 17 || l > 17000 { // TODO: TLSv1.3 max length
|
||||
err = fmt.Errorf("invalid header: %v", h[:5]) // DO NOT CHANGE: relied by client's Read()
|
||||
err = fmt.Errorf("%w: %v", ErrInvalidHeader, h[:5]) // DO NOT CHANGE: relied by client's Read()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadAndDecodeHeader(conn net.Conn) (h []byte, l int, err error) {
|
||||
h = make([]byte, 5)
|
||||
if _, err = io.ReadFull(conn, h); err != nil {
|
||||
func ParsePadding(padding string, paddingLens, paddingGaps *[][3]int) (err error) {
|
||||
if padding == "" {
|
||||
return
|
||||
}
|
||||
l, err = DecodeHeader(h)
|
||||
maxLen := 0
|
||||
for i, s := range strings.Split(padding, ".") {
|
||||
x := strings.Split(s, "-")
|
||||
if len(x) < 3 || x[0] == "" || x[1] == "" || x[2] == "" {
|
||||
return errors.New("invalid padding lenth/gap parameter: " + s)
|
||||
}
|
||||
y := [3]int{}
|
||||
if y[0], err = strconv.Atoi(x[0]); err != nil {
|
||||
return
|
||||
}
|
||||
if y[1], err = strconv.Atoi(x[1]); err != nil {
|
||||
return
|
||||
}
|
||||
if y[2], err = strconv.Atoi(x[2]); err != nil {
|
||||
return
|
||||
}
|
||||
if i == 0 && (y[0] < 100 || y[1] < 18+17 || y[2] < 18+17) {
|
||||
return errors.New("first padding length must not be smaller than 35")
|
||||
}
|
||||
if i%2 == 0 {
|
||||
*paddingLens = append(*paddingLens, y)
|
||||
maxLen += max(y[1], y[2])
|
||||
} else {
|
||||
*paddingGaps = append(*paddingGaps, y)
|
||||
}
|
||||
}
|
||||
if maxLen > 18+65535 {
|
||||
return errors.New("total padding length must not be larger than 65553")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadAndDiscardPaddings(conn net.Conn) (h []byte, l int, err error) {
|
||||
for {
|
||||
if h, l, err = ReadAndDecodeHeader(conn); err != nil {
|
||||
func CreatPadding(paddingLens, paddingGaps [][3]int) (length int, lens []int, gaps []time.Duration) {
|
||||
if len(paddingLens) == 0 {
|
||||
paddingLens = [][3]int{{100, 111, 1111}, {50, 0, 3333}}
|
||||
paddingGaps = [][3]int{{75, 0, 111}}
|
||||
}
|
||||
for _, y := range paddingLens {
|
||||
l := 0
|
||||
if y[0] >= int(randBetween(0, 100)) {
|
||||
l = int(randBetween(int64(y[1]), int64(y[2])))
|
||||
}
|
||||
lens = append(lens, l)
|
||||
length += l
|
||||
}
|
||||
for _, y := range paddingGaps {
|
||||
g := 0
|
||||
if y[0] >= int(randBetween(0, 100)) {
|
||||
g = int(randBetween(int64(y[1]), int64(y[2])))
|
||||
}
|
||||
gaps = append(gaps, time.Duration(g)*time.Millisecond)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func max[T ~int | ~uint | ~int64 | ~uint64 | ~int32 | ~uint32 | ~int16 | ~uint16 | ~int8 | ~uint8](a, b T) T {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
if _, err = io.ReadFull(conn, make([]byte, l)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func randBetween(from int64, to int64) int64 {
|
||||
if from == to {
|
||||
return from
|
||||
}
|
||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
||||
return from + bigInt.Int64()
|
||||
|
||||
if to < from {
|
||||
from, to = to, from
|
||||
}
|
||||
|
||||
return from + randv2.Int64N(to-from)
|
||||
}
|
||||
|
||||
@@ -20,4 +20,11 @@
|
||||
// https://github.com/XTLS/Xray-core/commit/ad7140641c44239c9dcdc3d7215ea639b1f0841c
|
||||
// https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902
|
||||
// https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e
|
||||
// https://github.com/XTLS/Xray-core/commit/b0b220985c9c1bc832665458d5fd6e0c287b67ae
|
||||
// https://github.com/XTLS/Xray-core/commit/82ea7a3cc5ff23280b87e3052f0f83b04f0267fa
|
||||
// https://github.com/XTLS/Xray-core/commit/e8b02cd6649f14889841e8ab8ee6b2acca71dbe6
|
||||
// https://github.com/XTLS/Xray-core/commit/6768a22f676c9121cfc9dc4f51181a8a07837c8d
|
||||
// https://github.com/XTLS/Xray-core/commit/4c6fd94d97159f5a3e740ba6dd2d9b65e3ed320c
|
||||
// https://github.com/XTLS/Xray-core/commit/19f890729656bc923ae3dee8426168c93b8ee9c2
|
||||
// https://github.com/XTLS/Xray-core/commit/cbade89ab11af26ba1e480a3688a6c205fa3c3f8
|
||||
package encryption
|
||||
|
||||
@@ -34,7 +34,12 @@ func NewClient(encryption string) (*ClientInstance, error) {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
var nfsPKeysBytes [][]byte
|
||||
var paddings []string
|
||||
for _, r := range s[3:] {
|
||||
if len(r) < 20 {
|
||||
paddings = append(paddings, r)
|
||||
continue
|
||||
}
|
||||
b, err := base64.RawURLEncoding.DecodeString(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
@@ -44,8 +49,9 @@ func NewClient(encryption string) (*ClientInstance, error) {
|
||||
}
|
||||
nfsPKeysBytes = append(nfsPKeysBytes, b)
|
||||
}
|
||||
padding := strings.Join(paddings, ".")
|
||||
client := &ClientInstance{}
|
||||
if err := client.Init(nfsPKeysBytes, xorMode, seconds); err != nil {
|
||||
if err := client.Init(nfsPKeysBytes, xorMode, seconds, padding); err != nil {
|
||||
return nil, fmt.Errorf("failed to use encryption: %w", err)
|
||||
}
|
||||
return client, nil
|
||||
@@ -71,20 +77,27 @@ func NewServer(decryption string) (*ServerInstance, error) {
|
||||
default:
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
var seconds uint32
|
||||
if s[2] != "1rtt" {
|
||||
t := strings.TrimSuffix(s[2], "s")
|
||||
if t == s[0] {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
i, err := strconv.Atoi(t)
|
||||
t := strings.SplitN(strings.TrimSuffix(s[2], "s"), "-", 2)
|
||||
i, err := strconv.Atoi(t[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
seconds = uint32(i)
|
||||
secondsFrom := int64(i)
|
||||
secondsTo := int64(0)
|
||||
if len(t) == 2 {
|
||||
i, err = strconv.Atoi(t[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
secondsTo = int64(i)
|
||||
}
|
||||
var nfsSKeysBytes [][]byte
|
||||
var paddings []string
|
||||
for _, r := range s[3:] {
|
||||
if len(r) < 20 {
|
||||
paddings = append(paddings, r)
|
||||
continue
|
||||
}
|
||||
b, err := base64.RawURLEncoding.DecodeString(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
@@ -94,8 +107,9 @@ func NewServer(decryption string) (*ServerInstance, error) {
|
||||
}
|
||||
nfsSKeysBytes = append(nfsSKeysBytes, b)
|
||||
}
|
||||
padding := strings.Join(paddings, ".")
|
||||
server := &ServerInstance{}
|
||||
if err := server.Init(nfsSKeysBytes, xorMode, seconds); err != nil {
|
||||
if err := server.Init(nfsSKeysBytes, xorMode, secondsFrom, secondsTo, padding); err != nil {
|
||||
return nil, fmt.Errorf("failed to use decryption: %w", err)
|
||||
}
|
||||
return server, nil
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user