diff --git a/README.md b/README.md index 9fd33484..9e340a7c 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,8 @@ Support outbound protocol `VLESS`. Support `Trojan` with XTLS. +Support relay `UDP` traffic. + Currently XTLS only supports TCP transport. ```yaml proxies: @@ -252,6 +254,25 @@ proxies: # udp: true # sni: example.com # aka server name # skip-cert-verify: 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 @@ -292,7 +313,7 @@ $ systemctl start clash ``` ### Display Process name -To display process name online by click [https://yacd.clash-plus.cf](https://yacd.clash-plus.cf). +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 @@ -309,6 +330,9 @@ external-ui: dashboard ``` Open [http://127.0.0.1:9090/ui/](http://127.0.0.1:9090/ui/) by web browser. +## Plus Pro Release +[Release](https://github.com/yaling888/clash/releases/tag/plus) + ## Development If you want to build an application that uses clash as a library, check out the the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library) 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/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/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)