diff --git a/README.md b/README.md index 9e358fc7..f9757894 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,13 @@ ls bin/ sudo bin/clash-local ``` +### General configuration +```yaml +sniffing: true # Sniff TLS SNI + +force-cert-verify: true # force verify TLS Certificate, prevent machine-in-the-middle attacks +``` + ### MITM configuration A root CA certificate is required, the MITM proxy server will generate a CA certificate file and a CA private key file in your Clash home directory, you can use your own certificate replace it. @@ -317,6 +324,8 @@ Support outbound protocol `VLESS`. Support `Trojan` with XTLS. +Support relay `UDP` traffic. + Currently XTLS only supports TCP transport. ```yaml proxies: @@ -354,12 +363,25 @@ proxies: # udp: true # sni: example.com # aka server name # skip-cert-verify: true -``` -### Sniffing configuration -Sniff TLS SNI. -```yaml -sniffing: true +proxy-groups: + # Relay chains the proxies. proxies shall not contain a relay. + # Support relay UDP traffic. + # Traffic: clash <-> ss1 <-> trojan <-> vmess <-> ss2 <-> Internet + - name: "relay-udp-over-tcp" + type: relay + proxies: + - ss1 + - trojan + - vmess + - ss2 + + - name: "relay-raw-udp" + type: relay + proxies: + - ss1 + - ss2 + - ss3 ``` ### IPTABLES configuration @@ -400,7 +422,7 @@ $ systemctl start clash ``` ### Display Process name -To display process name online by click [http://yacd.clash-plus.cf](http://yacd.clash-plus.cf) or [https://yacd.clash-plus.cf](https://yacd.clash-plus.cf) for local. +To display process name online by click [http://yacd.clash-plus.cf](http://yacd.clash-plus.cf) for local API by Safari or [https://yacd.clash-plus.cf](https://yacd.clash-plus.cf) for local API by Chrome. You can download the [Dashboard](https://github.com/yaling888/yacd/archive/gh-pages.zip) into Clash home directory: ```sh diff --git a/adapter/adapter.go b/adapter/adapter.go index cffc0fb2..a9fee742 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -3,14 +3,12 @@ package adapter import ( "context" "encoding/json" - "errors" "fmt" "net" "net/http" "net/netip" "net/url" "time" - _ "unsafe" "github.com/Dreamacro/clash/common/queue" "github.com/Dreamacro/clash/component/dialer" @@ -19,9 +17,6 @@ import ( "go.uber.org/atomic" ) -//go:linkname errCanceled net.errCanceled -var errCanceled error - type Proxy struct { C.ProxyAdapter history *queue.Queue[C.DelayHistory] @@ -43,7 +38,7 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { // DialContext implements C.ProxyAdapter func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...) - p.alive.Store(err == nil || errors.Is(err, errCanceled)) + p.alive.Store(err == nil) return conn, err } diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go index e9119415..eb922d1f 100644 --- a/adapter/outbound/base.go +++ b/adapter/outbound/base.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "io" "net" "github.com/Dreamacro/clash/component/dialer" @@ -30,12 +31,17 @@ func (b *Base) Type() C.AdapterType { } // StreamConn implements C.ProxyAdapter -func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { +func (b *Base) StreamConn(c net.Conn, _ *C.Metadata) (net.Conn, error) { + return c, errors.New("no support") +} + +// StreamPacketConn implements C.ProxyAdapter +func (b *Base) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) { return c, errors.New("no support") } // ListenPacketContext implements C.ProxyAdapter -func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { +func (b *Base) ListenPacketContext(_ context.Context, _ *C.Metadata, _ ...dialer.Option) (C.PacketConn, error) { return nil, errors.New("no support") } @@ -57,7 +63,7 @@ func (b *Base) Addr() string { } // Unwrap implements C.ProxyAdapter -func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy { +func (b *Base) Unwrap(_ *C.Metadata) C.Proxy { return nil } @@ -133,6 +139,40 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) { c.chain = append(c.chain, a.Name()) } -func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { +func NewPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { return &packetConn{pc, []string{a.Name()}} } + +type wrapConn struct { + net.PacketConn +} + +func (*wrapConn) Read([]byte) (int, error) { + return 0, io.EOF +} + +func (*wrapConn) Write([]byte) (int, error) { + return 0, io.EOF +} + +func (*wrapConn) RemoteAddr() net.Addr { + return nil +} + +func WrapConn(packetConn net.PacketConn) net.Conn { + return &wrapConn{ + PacketConn: packetConn, + } +} + +func IsPacketConn(c net.Conn) bool { + if _, ok := c.(net.PacketConn); !ok { + return false + } + + if ua, ok := c.LocalAddr().(*net.UnixAddr); ok { + return ua.Net == "unixgram" + } + + return true +} diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index 84f26764..2fcd6c6e 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -39,7 +39,7 @@ func (d *Direct) ListenPacketContext(ctx context.Context, _ *C.Metadata, opts .. if err != nil { return nil, err } - return newPacketConn(&directPacketConn{pc}, d), nil + return NewPacketConn(&directPacketConn{pc}, d), nil } type directPacketConn struct { diff --git a/adapter/outbound/reject.go b/adapter/outbound/reject.go index 3ab11453..8ae644f3 100644 --- a/adapter/outbound/reject.go +++ b/adapter/outbound/reject.go @@ -48,7 +48,7 @@ func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ... // ListenPacketContext implements C.ProxyAdapter func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { - return newPacketConn(&nopPacketConn{}, r), nil + return NewPacketConn(&nopPacketConn{}, r), nil } func NewReject() *Reject { diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index d80c7962..ba1d8252 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -74,6 +74,21 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e return c, err } +// StreamPacketConn implements C.ProxyAdapter +func (ss *ShadowSocks) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) { + if !IsPacketConn(c) { + return c, fmt.Errorf("%s connect error: can not convert net.Conn to net.PacketConn", ss.addr) + } + + addr, err := resolveUDPAddr("udp", ss.addr) + if err != nil { + return c, err + } + + pc := ss.cipher.PacketConn(c.(net.PacketConn)) + return WrapConn(&ssPacketConn{PacketConn: pc, rAddr: addr}), nil +} + // DialContext implements C.ProxyAdapter func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...) @@ -95,14 +110,13 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta return nil, err } - addr, err := resolveUDPAddr("udp", ss.addr) + c, err := ss.StreamPacketConn(WrapConn(pc), metadata) if err != nil { - pc.Close() + _ = pc.Close() return nil, err } - pc = ss.cipher.PacketConn(pc) - return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil + return NewPacketConn(c.(net.PacketConn), ss), nil } func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go index ea1c2838..4951860f 100644 --- a/adapter/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -59,6 +59,22 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, return c, err } +// StreamPacketConn implements C.ProxyAdapter +func (ssr *ShadowSocksR) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) { + if !IsPacketConn(c) { + return c, fmt.Errorf("%s connect error: can not convert net.Conn to net.PacketConn", ssr.addr) + } + + addr, err := resolveUDPAddr("udp", ssr.addr) + if err != nil { + return c, err + } + + pc := ssr.cipher.PacketConn(c.(net.PacketConn)) + pc = ssr.protocol.PacketConn(pc) + return WrapConn(&ssPacketConn{PacketConn: pc, rAddr: addr}), nil +} + // DialContext implements C.ProxyAdapter func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { c, err := dialer.DialContext(ctx, "tcp", ssr.addr, ssr.Base.DialOptions(opts...)...) @@ -80,15 +96,13 @@ func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Me return nil, err } - addr, err := resolveUDPAddr("udp", ssr.addr) + c, err := ssr.StreamPacketConn(WrapConn(pc), metadata) if err != nil { - pc.Close() + _ = pc.Close() return nil, err } - pc = ssr.cipher.PacketConn(pc) - pc = ssr.protocol.PacketConn(pc) - return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil + return NewPacketConn(c.(net.PacketConn), ssr), nil } func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { diff --git a/adapter/outbound/snell.go b/adapter/outbound/snell.go index 07f3d89d..90bf6b3a 100644 --- a/adapter/outbound/snell.go +++ b/adapter/outbound/snell.go @@ -58,6 +58,18 @@ func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { return c, err } +// StreamPacketConn implements C.ProxyAdapter +func (s *Snell) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) { + c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) + + err := snell.WriteUDPHeader(c, s.version) + if err != nil { + return c, err + } + + return WrapConn(snell.PacketConn(c)), nil +} + // DialContext implements C.ProxyAdapter func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { if s.version == snell.Version2 && len(opts) == 0 { @@ -68,7 +80,7 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d port, _ := strconv.ParseUint(metadata.DstPort, 10, 16) if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil { - c.Close() + _ = c.Close() return nil, err } return NewConn(c, s), err @@ -93,15 +105,14 @@ func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o return nil, err } tcpKeepAlive(c) - c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) - err = snell.WriteUDPHeader(c, s.version) + pc, err := s.StreamPacketConn(c, metadata) if err != nil { + _ = c.Close() return nil, err } - pc := snell.PacketConn(c) - return newPacketConn(pc, s), nil + return NewPacketConn(pc.(net.PacketConn), s), nil } func NewSnell(option SnellOption) (*Snell, error) { diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index 398ee3b2..7d3f030f 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -114,11 +114,11 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, } go func() { - io.Copy(io.Discard, c) - c.Close() + _, _ = io.Copy(io.Discard, c) + _ = c.Close() // A UDP association terminates when the TCP connection that the UDP // ASSOCIATE request arrived on terminates. RFC1928 - pc.Close() + _ = pc.Close() }() // Support unspecified UDP bind address. @@ -135,7 +135,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, bindUDPAddr.IP = serverAddr.IP } - return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil + return NewPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil } func NewSocks5(option Socks5Option) *Socks5 { @@ -199,6 +199,6 @@ func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { } func (uc *socksPacketConn) Close() error { - uc.tcpConn.Close() + _ = uc.tcpConn.Close() return uc.PacketConn.Close() } diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index aa389b34..a366ce95 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -72,8 +72,7 @@ func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) { return t.instance.StreamConn(c) } -// StreamConn implements C.ProxyAdapter -func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { +func (t *Trojan) trojanStream(c net.Conn, metadata *C.Metadata) (net.Conn, error) { var err error if t.transport != nil { c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig) @@ -85,39 +84,63 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } - c, err = t.instance.PresetXTLSConn(c) + c, err = t.instance.PrepareXTLSConn(c) if err != nil { - return nil, err + return c, err + } + + if metadata.NetWork == C.UDP { + err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) + return c, err } err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) return c, err } +// StreamConn implements C.ProxyAdapter +func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + return t.trojanStream(c, metadata) +} + +// StreamPacketConn implements C.ProxyAdapter +func (t *Trojan) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + var err error + c, err = t.trojanStream(c, metadata) + if err != nil { + return c, err + } + + pc := t.instance.PacketConn(c) + return WrapConn(pc), nil +} + // DialContext implements C.ProxyAdapter func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + var c net.Conn + // gun transport if t.transport != nil && len(opts) == 0 { - c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig) + c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig) if err != nil { return nil, err } - c, err = t.instance.PresetXTLSConn(c) + defer safeConnClose(c, err) + + c, err = t.instance.PrepareXTLSConn(c) if err != nil { - c.Close() return nil, err } if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil { - c.Close() return nil, err } return NewConn(c, t), nil } - c, err := dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...) + c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } @@ -137,33 +160,44 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ... func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { var c net.Conn - // grpc transport + // gun transport if t.transport != nil && len(opts) == 0 { c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig) if err != nil { - return nil, fmt.Errorf("%s connect error: %w", t.addr, err) + return nil, err } + defer safeConnClose(c, err) - } else { - c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...) + + c, err = t.instance.PrepareXTLSConn(c) if err != nil { - return nil, fmt.Errorf("%s connect error: %w", t.addr, err) + return nil, err } - defer safeConnClose(c, err) - tcpKeepAlive(c) - c, err = t.plainStream(c) - if err != nil { - return nil, fmt.Errorf("%s connect error: %w", t.addr, err) + + if err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)); err != nil { + return nil, err } + + pc := t.instance.PacketConn(c) + + return NewPacketConn(pc, t), nil } - err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) + c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", t.addr, err) + } + + tcpKeepAlive(c) + + defer safeConnClose(c, err) + + c, err = t.StreamPacketConn(c, metadata) if err != nil { return nil, err } - pc := t.instance.PacketConn(c) - return newPacketConn(pc, t), err + return NewPacketConn(c.(net.PacketConn), t), nil } func NewTrojan(option TrojanOption) (*Trojan, error) { diff --git a/adapter/outbound/util.go b/adapter/outbound/util.go index 8fa1d503..29d6ac08 100644 --- a/adapter/outbound/util.go +++ b/adapter/outbound/util.go @@ -52,7 +52,7 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { } func safeConnClose(c net.Conn, err error) { - if err != nil { + if err != nil && c != nil { _ = c.Close() } } diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 8fddeca2..c60e02b6 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -58,6 +58,7 @@ type VlessOption struct { ServerName string `proxy:"servername,omitempty"` } +// StreamConn implements C.ProxyAdapter func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { var err error switch v.option.Network { @@ -147,6 +148,26 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { return v.client.StreamConn(c, parseVlessAddr(metadata)) } +// StreamPacketConn implements C.ProxyAdapter +func (v *Vless) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr + if !metadata.Resolved() { + ip, err := resolver.ResolveIP(metadata.Host) + if err != nil { + return nil, errors.New("can't resolve ip") + } + metadata.DstIP = ip + } + + var err error + c, err = v.StreamConn(c, metadata) + if err != nil { + return nil, fmt.Errorf("new vmess client error: %v", err) + } + + return WrapConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}), nil +} + func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) { host, _, _ := net.SplitHostPort(v.addr) @@ -219,18 +240,18 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d // ListenPacketContext implements C.ProxyAdapter func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { - // vless use stream-oriented udp with a special address, so we needs a net.UDPAddr - if !metadata.Resolved() { - ip, err := resolver.ResolveIP(metadata.Host) - if err != nil { - return nil, errors.New("can't resolve ip") - } - metadata.DstIP = ip - } - var c net.Conn // gun transport if v.transport != nil && len(opts) == 0 { + // vless use stream-oriented udp with a special address, so we needs a net.UDPAddr + if !metadata.Resolved() { + ip, err := resolver.ResolveIP(metadata.Host) + if err != nil { + return nil, errors.New("can't resolve ip") + } + metadata.DstIP = ip + } + c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig) if err != nil { return nil, err @@ -238,22 +259,27 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o defer safeConnClose(c, err) c, err = v.client.StreamConn(c, parseVlessAddr(metadata)) - } else { - c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...) if err != nil { - return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) + return nil, fmt.Errorf("new vless client error: %v", err) } - tcpKeepAlive(c) - defer safeConnClose(c, err) - c, err = v.StreamConn(c, metadata) + return NewPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil } + c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...) + if err != nil { + return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) + } + + tcpKeepAlive(c) + defer safeConnClose(c, err) + + c, err = v.StreamPacketConn(c, metadata) if err != nil { return nil, fmt.Errorf("new vless client error: %v", err) } - return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil + return NewPacketConn(c.(net.PacketConn), v), nil } func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr { @@ -292,7 +318,7 @@ type vlessPacketConn struct { mux sync.Mutex } -func (vc *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { +func (vc *vlessPacketConn) WriteTo(b []byte, _ net.Addr) (int, error) { total := len(b) if total == 0 { return 0, nil diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index b32349d0..aa77a17d 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -3,7 +3,6 @@ package outbound import ( "context" "crypto/tls" - "errors" "fmt" "net" "net/http" @@ -195,6 +194,26 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { return v.client.StreamConn(c, parseVmessAddr(metadata)) } +// StreamPacketConn implements C.ProxyAdapter +func (v *Vmess) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr + if !metadata.Resolved() { + ip, err := resolver.ResolveIP(metadata.Host) + if err != nil { + return c, fmt.Errorf("can't resolve ip: %w", err) + } + metadata.DstIP = ip + } + + var err error + c, err = v.StreamConn(c, metadata) + if err != nil { + return c, fmt.Errorf("new vmess client error: %v", err) + } + + return WrapConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}), nil +} + // DialContext implements C.ProxyAdapter func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { // gun transport @@ -226,18 +245,18 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d // ListenPacketContext implements C.ProxyAdapter func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { - // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr - if !metadata.Resolved() { - ip, err := resolver.ResolveIP(metadata.Host) - if err != nil { - return nil, errors.New("can't resolve ip") - } - metadata.DstIP = ip - } - var c net.Conn // gun transport if v.transport != nil && len(opts) == 0 { + // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr + if !metadata.Resolved() { + ip, err := resolver.ResolveIP(metadata.Host) + if err != nil { + return nil, fmt.Errorf("can't resolve ip: %w", err) + } + metadata.DstIP = ip + } + c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig) if err != nil { return nil, err @@ -245,22 +264,27 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o defer safeConnClose(c, err) c, err = v.client.StreamConn(c, parseVmessAddr(metadata)) - } else { - c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...) if err != nil { - return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) + return nil, fmt.Errorf("new vmess client error: %v", err) } - tcpKeepAlive(c) - defer safeConnClose(c, err) - c, err = v.StreamConn(c, metadata) + return NewPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil } + c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...) + if err != nil { + return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) + } + + tcpKeepAlive(c) + defer safeConnClose(c, err) + + c, err = v.StreamPacketConn(c, metadata) if err != nil { return nil, fmt.Errorf("new vmess client error: %v", err) } - return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil + return NewPacketConn(c.(net.PacketConn), v), nil } func NewVmess(option VmessOption) (*Vmess, error) { @@ -368,7 +392,7 @@ type vmessPacketConn struct { rAddr net.Addr } -func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { +func (uc *vmessPacketConn) WriteTo(b []byte, _ net.Addr) (int, error) { return uc.Conn.Write(b) } diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index 7c0ca181..8325e101 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -4,10 +4,13 @@ import ( "context" "encoding/json" "fmt" + "net" + "net/netip" "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" ) @@ -66,6 +69,103 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d return outbound.NewConn(c, r), nil } +// ListenPacketContext implements C.ProxyAdapter +func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + var proxies []C.Proxy + for _, proxy := range r.proxies(metadata, true) { + if proxy.Type() != C.Direct { + proxies = append(proxies, proxy) + } + } + + switch len(proxies) { + case 0: + return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...) + case 1: + proxy := proxies[0] + if !proxy.SupportUDP() { + return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported", proxy.Addr(), proxy.Name()) + } + return proxy.ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...) + } + + var ( + first = proxies[0] + last = proxies[len(proxies)-1] + rawUDPRelay bool + udpOverTCPEndIndex = -1 + + c net.Conn + err error + currentMeta *C.Metadata + ) + + if !last.SupportUDP() { + return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", last.Addr(), last.Name()) + } + + rawUDPRelay, udpOverTCPEndIndex = isRawUDPRelay(proxies) + + if rawUDPRelay { + var pc net.PacketConn + pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...) + c = outbound.WrapConn(pc) + } else { + c, err = dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...) + } + + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + + defer func() { + if err != nil && c != nil { + _ = c.Close() + } + }() + + for i, proxy := range proxies[1:] { + currentMeta, err = addrToMetadata(proxy.Addr()) + if err != nil { + return nil, err + } + + if outbound.IsPacketConn(c) || udpOverTCPEndIndex == i { + if !isRawUDP(first) && !first.SupportUDP() { + return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", first.Addr(), first.Name()) + } + + if !currentMeta.Resolved() && needResolveIP(first) { + var ip netip.Addr + ip, err = resolver.ResolveProxyServerHost(currentMeta.Host) + if err != nil { + return nil, fmt.Errorf("can't resolve ip: %w", err) + } + currentMeta.DstIP = ip + } + + currentMeta.NetWork = C.UDP + c, err = first.StreamPacketConn(c, currentMeta) + } else { + c, err = first.StreamConn(c, currentMeta) + } + + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + + first = proxy + } + + c, err = last.StreamPacketConn(c, metadata) + + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + + return outbound.NewPacketConn(c.(net.PacketConn), r), nil +} + // MarshalJSON implements C.ProxyAdapter func (r *Relay) MarshalJSON() ([]byte, error) { var all []string @@ -100,11 +200,47 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy { return proxies } +func isRawUDPRelay(proxies []C.Proxy) (bool, int) { + var ( + lastIndex = len(proxies) - 1 + isLastRawUDP = isRawUDP(proxies[lastIndex]) + isUDPOverTCP = false + udpOverTCPEndIndex = -1 + ) + + for i := lastIndex; i >= 0; i-- { + p := proxies[i] + + isUDPOverTCP = isUDPOverTCP || !isRawUDP(p) + + if isLastRawUDP && isUDPOverTCP && udpOverTCPEndIndex == -1 { + udpOverTCPEndIndex = i + } + } + + return !isUDPOverTCP, udpOverTCPEndIndex +} + +func isRawUDP(proxy C.ProxyAdapter) bool { + if proxy.Type() == C.Shadowsocks || proxy.Type() == C.ShadowsocksR { + return true + } + return false +} + +func needResolveIP(proxy C.ProxyAdapter) bool { + if proxy.Type() == C.Vmess || proxy.Type() == C.Vless { + return true + } + return false +} + func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay { return &Relay{ Base: outbound.NewBase(outbound.BaseOption{ Name: option.Name, Type: C.Relay, + UDP: true, Interface: option.Interface, RoutingMark: option.RoutingMark, }), diff --git a/adapter/outboundgroup/util.go b/adapter/outboundgroup/util.go index 266fce8d..cbe9b489 100644 --- a/adapter/outboundgroup/util.go +++ b/adapter/outboundgroup/util.go @@ -24,6 +24,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) { DstIP: netip.Addr{}, DstPort: port, } + err = nil return } else if ip.Is4() { addr = &C.Metadata{ diff --git a/adapter/parser.go b/adapter/parser.go index 13701bc3..444edd1a 100644 --- a/adapter/parser.go +++ b/adapter/parser.go @@ -8,7 +8,7 @@ import ( C "github.com/Dreamacro/clash/constant" ) -func ParseProxy(mapping map[string]any) (C.Proxy, error) { +func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) { decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) proxyType, existType := mapping["type"].(string) if !existType { @@ -40,6 +40,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { if err != nil { break } + if forceCertVerify { + socksOption.SkipCertVerify = false + } proxy = outbound.NewSocks5(*socksOption) case "http": httpOption := &outbound.HttpOption{} @@ -47,6 +50,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { if err != nil { break } + if forceCertVerify { + httpOption.SkipCertVerify = false + } proxy = outbound.NewHttp(*httpOption) case "vmess": vmessOption := &outbound.VmessOption{ @@ -59,6 +65,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { if err != nil { break } + if forceCertVerify { + vmessOption.SkipCertVerify = false + } proxy, err = outbound.NewVmess(*vmessOption) case "vless": vlessOption := &outbound.VlessOption{} @@ -66,6 +75,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { if err != nil { break } + if forceCertVerify { + vlessOption.SkipCertVerify = false + } proxy, err = outbound.NewVless(*vlessOption) case "snell": snellOption := &outbound.SnellOption{} @@ -80,6 +92,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { if err != nil { break } + if forceCertVerify { + trojanOption.SkipCertVerify = false + } proxy, err = outbound.NewTrojan(*trojanOption) default: return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) diff --git a/adapter/provider/fetcher.go b/adapter/provider/fetcher.go index 4a7be8b1..0426ecc2 100644 --- a/adapter/provider/fetcher.go +++ b/adapter/provider/fetcher.go @@ -16,28 +16,28 @@ var ( dirMode os.FileMode = 0o755 ) -type parser = func([]byte) (any, error) +type parser[V any] func([]byte) (V, error) -type fetcher struct { +type fetcher[V any] struct { name string vehicle types.Vehicle updatedAt *time.Time ticker *time.Ticker done chan struct{} hash [16]byte - parser parser - onUpdate func(any) + parser parser[V] + onUpdate func(V) } -func (f *fetcher) Name() string { +func (f *fetcher[V]) Name() string { return f.name } -func (f *fetcher) VehicleType() types.VehicleType { +func (f *fetcher[V]) VehicleType() types.VehicleType { return f.vehicle.Type() } -func (f *fetcher) Initial() (any, error) { +func (f *fetcher[V]) Initial() (V, error) { var ( buf []byte err error @@ -53,24 +53,24 @@ func (f *fetcher) Initial() (any, error) { } if err != nil { - return nil, err + return getZero[V](), err } proxies, err := f.parser(buf) if err != nil { if !isLocal { - return nil, err + return getZero[V](), err } // parse local file error, fallback to remote buf, err = f.vehicle.Read() if err != nil { - return nil, err + return getZero[V](), err } proxies, err = f.parser(buf) if err != nil { - return nil, err + return getZero[V](), err } isLocal = false @@ -78,7 +78,7 @@ func (f *fetcher) Initial() (any, error) { if f.vehicle.Type() != types.File && !isLocal { if err := safeWrite(f.vehicle.Path(), buf); err != nil { - return nil, err + return getZero[V](), err } } @@ -92,28 +92,28 @@ func (f *fetcher) Initial() (any, error) { return proxies, nil } -func (f *fetcher) Update() (any, bool, error) { +func (f *fetcher[V]) Update() (V, bool, error) { buf, err := f.vehicle.Read() if err != nil { - return nil, false, err + return getZero[V](), false, err } now := time.Now() hash := md5.Sum(buf) if bytes.Equal(f.hash[:], hash[:]) { f.updatedAt = &now - os.Chtimes(f.vehicle.Path(), now, now) - return nil, true, nil + _ = os.Chtimes(f.vehicle.Path(), now, now) + return getZero[V](), true, nil } proxies, err := f.parser(buf) if err != nil { - return nil, false, err + return getZero[V](), false, err } if f.vehicle.Type() != types.File { if err := safeWrite(f.vehicle.Path(), buf); err != nil { - return nil, false, err + return getZero[V](), false, err } } @@ -123,14 +123,14 @@ func (f *fetcher) Update() (any, bool, error) { return proxies, false, nil } -func (f *fetcher) Destroy() error { +func (f *fetcher[V]) Destroy() error { if f.ticker != nil { f.done <- struct{}{} } return nil } -func (f *fetcher) pullLoop() { +func (f *fetcher[V]) pullLoop() { for { select { case <-f.ticker.C: @@ -168,13 +168,13 @@ func safeWrite(path string, buf []byte) error { return os.WriteFile(path, buf, fileMode) } -func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(any)) *fetcher { +func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] { var ticker *time.Ticker if interval != 0 { ticker = time.NewTicker(interval) } - return &fetcher{ + return &fetcher[V]{ name: name, ticker: ticker, vehicle: vehicle, @@ -183,3 +183,8 @@ func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, pars onUpdate: onUpdate, } } + +func getZero[V any]() V { + var result V + return result +} diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index 887ac5cd..3833dc7e 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -20,15 +20,16 @@ type healthCheckSchema struct { } type proxyProviderSchema struct { - Type string `provider:"type"` - Path string `provider:"path"` - URL string `provider:"url,omitempty"` - Interval int `provider:"interval,omitempty"` - Filter string `provider:"filter,omitempty"` - HealthCheck healthCheckSchema `provider:"health-check,omitempty"` + Type string `provider:"type"` + Path string `provider:"path"` + URL string `provider:"url,omitempty"` + Interval int `provider:"interval,omitempty"` + Filter string `provider:"filter,omitempty"` + HealthCheck healthCheckSchema `provider:"health-check,omitempty"` + ForceCertVerify bool `provider:"force-cert-verify,omitempty"` } -func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) { +func ParseProxyProvider(name string, mapping map[string]any, forceCertVerify bool) (types.ProxyProvider, error) { decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) schema := &proxyProviderSchema{ @@ -36,6 +37,11 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide Lazy: true, }, } + + if forceCertVerify { + schema.ForceCertVerify = true + } + if err := decoder.Decode(mapping, schema); err != nil { return nil, err } @@ -60,5 +66,5 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide interval := time.Duration(uint(schema.Interval)) * time.Second filter := schema.Filter - return NewProxySetProvider(name, interval, filter, vehicle, hc) + return NewProxySetProvider(name, interval, filter, vehicle, hc, schema.ForceCertVerify) } diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 69f547da..8c3c272c 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -23,13 +23,13 @@ type ProxySchema struct { Proxies []map[string]any `yaml:"proxies"` } -// for auto gc +// ProxySetProvider for auto gc type ProxySetProvider struct { *proxySetProvider } type proxySetProvider struct { - *fetcher + *fetcher[[]C.Proxy] proxies []C.Proxy healthCheck *HealthCheck } @@ -93,10 +93,10 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) { func stopProxyProvider(pd *ProxySetProvider) { pd.healthCheck.close() - pd.fetcher.Destroy() + _ = pd.fetcher.Destroy() } -func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { +func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck, forceCertVerify bool) (*ProxySetProvider, error) { filterReg, err := regexp.Compile(filter) if err != nil { return nil, fmt.Errorf("invalid filter regex: %w", err) @@ -111,45 +111,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh healthCheck: hc, } - onUpdate := func(elm any) { - ret := elm.([]C.Proxy) - pd.setProxies(ret) - } - - proxiesParseAndFilter := func(buf []byte) (any, error) { - schema := &ProxySchema{} - - if err := yaml.Unmarshal(buf, schema); err != nil { - return nil, err - } - - if schema.Proxies == nil { - return nil, errors.New("file must have a `proxies` field") - } - - proxies := []C.Proxy{} - for idx, mapping := range schema.Proxies { - if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) { - continue - } - proxy, err := adapter.ParseProxy(mapping) - if err != nil { - return nil, fmt.Errorf("proxy %d error: %w", idx, err) - } - proxies = append(proxies, proxy) - } - - if len(proxies) == 0 { - if len(filter) > 0 { - return nil, errors.New("doesn't match any proxy, please check your filter") - } - return nil, errors.New("file doesn't have any proxy") - } - - return proxies, nil - } - - fetcher := newFetcher(name, interval, vehicle, proxiesParseAndFilter, onUpdate) + fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg, forceCertVerify), proxiesOnUpdate(pd)) pd.fetcher = fetcher wrapper := &ProxySetProvider{pd} @@ -157,7 +119,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh return wrapper, nil } -// for auto gc +// CompatibleProvider for auto gc type CompatibleProvider struct { *compatibleProvider } @@ -233,3 +195,44 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co runtime.SetFinalizer(wrapper, stopCompatibleProvider) return wrapper, nil } + +func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) { + return func(elm []C.Proxy) { + pd.setProxies(elm) + } +} + +func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVerify bool) parser[[]C.Proxy] { + return func(buf []byte) ([]C.Proxy, error) { + schema := &ProxySchema{} + + if err := yaml.Unmarshal(buf, schema); err != nil { + return nil, err + } + + if schema.Proxies == nil { + return nil, errors.New("file must have a `proxies` field") + } + + proxies := []C.Proxy{} + for idx, mapping := range schema.Proxies { + if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) { + continue + } + proxy, err := adapter.ParseProxy(mapping, forceCertVerify) + if err != nil { + return nil, fmt.Errorf("proxy %d error: %w", idx, err) + } + proxies = append(proxies, proxy) + } + + if len(proxies) == 0 { + if len(filter) > 0 { + return nil, errors.New("doesn't match any proxy, please check your filter") + } + return nil, errors.New("file doesn't have any proxy") + } + + return proxies, nil + } +} diff --git a/config/config.go b/config/config.go index bb39c5f9..5df35b5f 100644 --- a/config/config.go +++ b/config/config.go @@ -193,6 +193,7 @@ type RawConfig struct { Interface string `yaml:"interface-name"` RoutingMark int `yaml:"routing-mark"` Sniffing bool `yaml:"sniffing"` + ForceCertVerify bool `yaml:"force-cert-verify"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` Hosts map[string]string `yaml:"hosts"` @@ -221,16 +222,17 @@ func Parse(buf []byte) (*Config, error) { func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { // config with default value rawCfg := &RawConfig{ - AllowLan: false, - Sniffing: false, - BindAddress: "*", - Mode: T.Rule, - Authentication: []string{}, - LogLevel: log.INFO, - Hosts: map[string]string{}, - Rule: []string{}, - Proxy: []map[string]any{}, - ProxyGroup: []map[string]any{}, + AllowLan: false, + Sniffing: false, + ForceCertVerify: false, + BindAddress: "*", + Mode: T.Rule, + Authentication: []string{}, + LogLevel: log.INFO, + Hosts: map[string]string{}, + Rule: []string{}, + Proxy: []map[string]any{}, + ProxyGroup: []map[string]any{}, Tun: Tun{ Enable: false, Device: "", @@ -403,6 +405,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ proxiesConfig := cfg.Proxy groupsConfig := cfg.ProxyGroup providersConfig := cfg.ProxyProvider + forceCertVerify := cfg.ForceCertVerify var proxyList []string @@ -412,7 +415,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ // parse proxy for idx, mapping := range proxiesConfig { - proxy, err := adapter.ParseProxy(mapping) + proxy, err := adapter.ParseProxy(mapping, forceCertVerify) if err != nil { return nil, nil, fmt.Errorf("proxy %d: %w", idx, err) } @@ -444,7 +447,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ return nil, nil, fmt.Errorf("can not defined a provider called `%s`", provider.ReservedName) } - pd, err := provider.ParseProxyProvider(name, mapping) + pd, err := provider.ParseProxyProvider(name, mapping, forceCertVerify) if err != nil { return nil, nil, fmt.Errorf("parse proxy provider %s error: %w", name, err) } diff --git a/constant/adapters.go b/constant/adapters.go index 40849422..d11ceba8 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -93,9 +93,14 @@ type ProxyAdapter interface { // a new session (if any) StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error) + // StreamPacketConn wraps a UDP protocol around net.Conn with Metadata. + StreamPacketConn(c net.Conn, metadata *Metadata) (net.Conn, error) + // DialContext return a C.Conn with protocol which // contains multiplexing-related reuse logic (if any) DialContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (Conn, error) + + // ListenPacketContext listen for a PacketConn ListenPacketContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (PacketConn, error) // Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract. diff --git a/dns/util.go b/dns/util.go index 86d410d4..515bb88f 100644 --- a/dns/util.go +++ b/dns/util.go @@ -8,6 +8,7 @@ import ( "net/netip" "time" + "github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/nnip" "github.com/Dreamacro/clash/component/dialer" @@ -132,16 +133,13 @@ func (wpc *wrapPacketConn) RemoteAddr() net.Addr { } func dialContextWithProxyAdapter(ctx context.Context, adapterName string, network string, dstIP netip.Addr, port string, opts ...dialer.Option) (net.Conn, error) { - adapter, ok := tunnel.Proxies()[adapterName] + proxy, ok := tunnel.Proxies()[adapterName] if !ok { return nil, fmt.Errorf("proxy adapter [%s] not found", adapterName) } networkType := C.TCP if network == "udp" { - if !adapter.SupportUDP() { - return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", adapterName) - } networkType = C.UDP } @@ -158,8 +156,14 @@ func dialContextWithProxyAdapter(ctx context.Context, adapterName string, networ DstPort: port, } + rawAdapter := fetchRawProxyAdapter(proxy.(*adapter.Proxy).ProxyAdapter, metadata) + if networkType == C.UDP { - packetConn, err := adapter.ListenPacketContext(ctx, metadata, opts...) + if !rawAdapter.SupportUDP() { + return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", rawAdapter.Name()) + } + + packetConn, err := rawAdapter.ListenPacketContext(ctx, metadata, opts...) if err != nil { return nil, err } @@ -170,5 +174,13 @@ func dialContextWithProxyAdapter(ctx context.Context, adapterName string, networ }, nil } - return adapter.DialContext(ctx, metadata, opts...) + return rawAdapter.DialContext(ctx, metadata, opts...) +} + +func fetchRawProxyAdapter(proxyAdapter C.ProxyAdapter, metadata *C.Metadata) C.ProxyAdapter { + if p := proxyAdapter.Unwrap(metadata); p != nil { + return fetchRawProxyAdapter(p.(*adapter.Proxy).ProxyAdapter, metadata) + } + + return proxyAdapter } diff --git a/go.mod b/go.mod index 9619cc8d..00cf6550 100644 --- a/go.mod +++ b/go.mod @@ -18,18 +18,18 @@ require ( go.etcd.io/bbolt v1.3.6 go.uber.org/atomic v1.9.0 go.uber.org/automaxprocs v1.5.1 - golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 - golang.org/x/exp v0.0.0-20220428152302-39d4317da171 - golang.org/x/net v0.0.0-20220513224357-95641704303c + golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 + golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf + golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 - golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab golang.org/x/time v0.0.0-20220411224347-583f2d630306 golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e google.golang.org/protobuf v1.28.0 gopkg.in/yaml.v2 v2.4.0 - gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38 + gvisor.dev/gvisor v0.0.0-20220520211629-7e72240f4f2e ) require ( @@ -40,8 +40,8 @@ require ( github.com/oschwald/maxminddb-golang v1.9.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect - golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect - golang.org/x/tools v0.1.9 // indirect + golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect + golang.org/x/tools v0.1.10 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect diff --git a/go.sum b/go.sum index a4aebf87..fd7043b5 100644 --- a/go.sum +++ b/go.sum @@ -80,13 +80,13 @@ go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c= -golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5DcL5XQr4au4sZ2F2NV4= -golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0= +golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw= +golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4= -golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -99,8 +99,8 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220513224357-95641704303c h1:nF9mHSvoKBLkQNQhJZNsc66z2UzAMUbLGjC95CF3pU0= -golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4= @@ -125,8 +125,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY= -golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -139,8 +139,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -162,5 +162,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38 h1:a4PsEqiJG7UmtAMcEtxgLcscq49mqpjZgBAi82+2Y1c= -gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI= +gvisor.dev/gvisor v0.0.0-20220520211629-7e72240f4f2e h1:NfZ2QezHUJrGICokWPLyOtn5ZOyTAmIB89zvyZb5ibU= +gvisor.dev/gvisor v0.0.0-20220520211629-7e72240f4f2e/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= diff --git a/listener/tproxy/tproxy_iptables.go b/listener/tproxy/tproxy_iptables.go index fbd0d2d2..75502169 100644 --- a/listener/tproxy/tproxy_iptables.go +++ b/listener/tproxy/tproxy_iptables.go @@ -162,15 +162,12 @@ func CleanupTProxyIPTables() { func addLocalnetworkToChain(chain string) { execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 0.0.0.0/8 -j RETURN", chain)) execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 10.0.0.0/8 -j RETURN", chain)) - execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 100.64.0.0/10 -j RETURN", chain)) execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 127.0.0.0/8 -j RETURN", chain)) execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 169.254.0.0/16 -j RETURN", chain)) - execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 172.16.0.0/12 -j RETURN", chain)) execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.0.0.0/24 -j RETURN", chain)) execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.0.2.0/24 -j RETURN", chain)) execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.88.99.0/24 -j RETURN", chain)) execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.168.0.0/16 -j RETURN", chain)) - execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 198.51.100.0/24 -j RETURN", chain)) execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 203.0.113.0/24 -j RETURN", chain)) execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 224.0.0.0/4 -j RETURN", chain)) execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 240.0.0.0/4 -j RETURN", chain)) diff --git a/listener/tun/ipstack/commons/router.go b/listener/tun/ipstack/commons/router.go index d378cef7..d4ac5743 100644 --- a/listener/tun/ipstack/commons/router.go +++ b/listener/tun/ipstack/commons/router.go @@ -7,6 +7,7 @@ import ( "time" "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/log" ) @@ -61,6 +62,8 @@ func StartDefaultInterfaceChangeMonitor() { dialer.DefaultInterface.Store(interfaceName) + iface.FlushCache() + log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName) case <-monitorStop: break diff --git a/listener/tun/ipstack/commons/router_darwin.go b/listener/tun/ipstack/commons/router_darwin.go index 5cbab1fa..a889aaf5 100644 --- a/listener/tun/ipstack/commons/router_darwin.go +++ b/listener/tun/ipstack/commons/router_darwin.go @@ -2,24 +2,23 @@ package commons import ( "fmt" + "net" "net/netip" "strings" + "syscall" "github.com/Dreamacro/clash/common/cmd" "github.com/Dreamacro/clash/listener/tun/device" + + "golang.org/x/net/route" ) func GetAutoDetectInterface() (string, error) { - rs, err := cmd.ExecCmd("/bin/bash -c /sbin/route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n") + iface, err := defaultRouteInterface() if err != nil { return "", err } - - if rs == "" || strings.HasSuffix(rs, "\n") { - return "", fmt.Errorf("invalid interface name: %s", rs) - } - - return rs, nil + return iface.Name, nil } func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error { @@ -71,3 +70,43 @@ func execRouterCmd(action, inet, route string, interfaceName string) error { _, err := cmd.ExecCmd(fmt.Sprintf("/sbin/route %s %s %s -interface %s", action, inet, route, interfaceName)) return err } + +func defaultRouteInterface() (*net.Interface, error) { + rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0) + if err != nil { + return nil, fmt.Errorf("route.FetchRIB: %w", err) + } + + msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib) + if err != nil { + return nil, fmt.Errorf("route.ParseRIB: %w", err) + } + + for _, message := range msgs { + routeMessage := message.(*route.RouteMessage) + if routeMessage.Flags&(syscall.RTF_UP|syscall.RTF_GATEWAY|syscall.RTF_STATIC) == 0 { + continue + } + + addresses := routeMessage.Addrs + + if (addresses[0].Family() == syscall.AF_INET && addresses[0].(*route.Inet4Addr).IP != *(*[4]byte)(net.IPv4zero)) || + (addresses[0].Family() == syscall.AF_INET6 && addresses[0].(*route.Inet6Addr).IP != *(*[16]byte)(net.IPv6zero)) { + + continue + } + + iface, err1 := net.InterfaceByIndex(routeMessage.Index) + if err1 != nil { + continue + } + + if strings.HasPrefix(iface.Name, "utun") { + continue + } + + return iface, nil + } + + return nil, fmt.Errorf("ambiguous gateway interfaces found") +} diff --git a/test/clash_test.go b/test/clash_test.go index ade2ed76..49c1466d 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net" + "net/netip" "os" "path/filepath" "runtime" @@ -38,7 +39,7 @@ const ( var ( waitTime = time.Second - localIP = net.ParseIP("127.0.0.1") + localIP = netip.MustParseAddr("127.0.0.1") defaultExposedPorts = nat.PortSet{ "10002/tcp": struct{}{}, @@ -67,10 +68,11 @@ func init() { C.SetHomeDir(homeDir) if isDarwin { - localIP, err = defaultRouteIP() + routeIp, err := defaultRouteIP() if err != nil { panic(err) } + localIP = netip.MustParseAddr(routeIp.String()) } c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) @@ -309,7 +311,7 @@ func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error { } defer l.Close() - rAddr := &net.UDPAddr{IP: localIP, Port: 10001} + rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001} pingCh, pongCh, test := newPingPongPair() go func() { @@ -448,7 +450,7 @@ func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error { } defer l.Close() - rAddr := &net.UDPAddr{IP: localIP, Port: 10001} + rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001} times := 50 chunkSize := int64(1024) diff --git a/test/go.mod b/test/go.mod index 206790f9..e335edb7 100644 --- a/test/go.mod +++ b/test/go.mod @@ -8,7 +8,7 @@ require ( github.com/docker/go-connections v0.4.0 github.com/miekg/dns v1.1.49 github.com/stretchr/testify v1.7.1 - golang.org/x/net v0.0.0-20220513224357-95641704303c + golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 ) replace github.com/Dreamacro/clash => ../ @@ -39,14 +39,14 @@ require ( github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 // indirect - golang.org/x/exp v0.0.0-20220428152302-39d4317da171 // indirect - golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect + golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 // indirect + golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect + golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect - golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect - golang.org/x/tools v0.1.9 // indirect + golang.org/x/tools v0.1.10 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d // indirect @@ -57,5 +57,5 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gotest.tools/v3 v3.1.0 // indirect - gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38 // indirect + gvisor.dev/gvisor v0.0.0-20220520211629-7e72240f4f2e // indirect ) diff --git a/test/go.sum b/test/go.sum index 6175f656..fd97d8b3 100644 --- a/test/go.sum +++ b/test/go.sum @@ -912,8 +912,8 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c= -golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0= +golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -924,8 +924,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5DcL5XQr4au4sZ2F2NV4= -golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw= +golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -951,8 +951,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4= -golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1013,8 +1013,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220513224357-95641704303c h1:nF9mHSvoKBLkQNQhJZNsc66z2UzAMUbLGjC95CF3pU0= -golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1145,8 +1145,8 @@ golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY= -golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1237,8 +1237,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1415,8 +1415,8 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk= gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= -gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38 h1:a4PsEqiJG7UmtAMcEtxgLcscq49mqpjZgBAi82+2Y1c= -gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI= +gvisor.dev/gvisor v0.0.0-20220520211629-7e72240f4f2e h1:NfZ2QezHUJrGICokWPLyOtn5ZOyTAmIB89zvyZb5ibU= +gvisor.dev/gvisor v0.0.0-20220520211629-7e72240f4f2e/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 207d7b3a..67c299df 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -133,7 +133,7 @@ func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption) }) } -func (t *Trojan) PresetXTLSConn(conn net.Conn) (net.Conn, error) { +func (t *Trojan) PrepareXTLSConn(conn net.Conn) (net.Conn, error) { switch t.option.Flow { case vless.XRO, vless.XRD, vless.XRS: if xtlsConn, ok := conn.(*xtls.Conn); ok { @@ -189,7 +189,7 @@ func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) { defer pool.PutBuffer(buf) buf.Write(socks5Addr) - binary.Write(buf, binary.BigEndian, uint16(len(payload))) + _ = binary.Write(buf, binary.BigEndian, uint16(len(payload))) buf.Write(crlf) buf.Write(payload)