From 6ad2cde9091b0ee3f7545af333fae7c0e208058a Mon Sep 17 00:00:00 2001 From: yaling888 <73897884+yaling888@users.noreply.github.com> Date: Tue, 24 May 2022 21:58:20 +0800 Subject: [PATCH] Feature: support relay Socks5 UDP supports relaying of all UDP traffic except the HTTP outbound. --- adapter/outbound/socks5.go | 106 +++++++++-------- adapter/outboundgroup/relay.go | 200 ++++++++++++++++++++++----------- adapter/outboundgroup/util.go | 3 +- 3 files changed, 195 insertions(+), 114 deletions(-) diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index 7d3f030f..2fea9121 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -37,12 +37,59 @@ type Socks5Option struct { // StreamConn implements C.ProxyAdapter func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + var err error + c, _, err = ss.streamConn(c, metadata) + + return c, err +} + +func (ss *Socks5) StreamSocks5PacketConn(c net.Conn, pc net.PacketConn, metadata *C.Metadata) (net.PacketConn, error) { + if c == nil { + return pc, fmt.Errorf("%s connect error: parameter net.Conn is nil", ss.addr) + } + + if pc == nil { + return pc, fmt.Errorf("%s connect error: parameter net.PacketConn is nil", ss.addr) + } + + cc, bindAddr, err := ss.streamConn(c, metadata) + if err != nil { + return pc, err + } + + c = cc + + go func() { + _, _ = 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() + }() + + // Support unspecified UDP bind address. + bindUDPAddr := bindAddr.UDPAddr() + if bindUDPAddr == nil { + return pc, errors.New("invalid UDP bind address") + } else if bindUDPAddr.IP.IsUnspecified() { + serverAddr, err := resolveUDPAddr("udp", ss.Addr()) + if err != nil { + return pc, err + } + + bindUDPAddr.IP = serverAddr.IP + } + + return &socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, nil +} + +func (ss *Socks5) streamConn(c net.Conn, metadata *C.Metadata) (_ net.Conn, bindAddr socks5.Addr, err error) { if ss.tls { cc := tls.Client(c, ss.tlsConfig) err := cc.Handshake() c = cc if err != nil { - return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) + return c, nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } } @@ -53,10 +100,14 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) Password: ss.pass, } } - if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil { - return nil, err + + if metadata.NetWork == C.UDP { + bindAddr, err = socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user) + } else { + bindAddr, err = socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user) } - return c, nil + + return c, bindAddr, err } // DialContext implements C.ProxyAdapter @@ -81,61 +132,24 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts .. func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...) if err != nil { - err = fmt.Errorf("%s connect error: %w", ss.addr, err) - return - } - - if ss.tls { - cc := tls.Client(c, ss.tlsConfig) - err = cc.Handshake() - c = cc + return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } defer safeConnClose(c, err) - tcpKeepAlive(c) - var user *socks5.User - if ss.user != "" { - user = &socks5.User{ - Username: ss.user, - Password: ss.pass, - } - } - - bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user) - if err != nil { - err = fmt.Errorf("client hanshake error: %w", err) - return - } - pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...) if err != nil { return } - go func() { - _, _ = 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() - }() + tcpKeepAlive(c) - // Support unspecified UDP bind address. - bindUDPAddr := bindAddr.UDPAddr() - if bindUDPAddr == nil { - err = errors.New("invalid UDP bind address") + pc, err = ss.StreamSocks5PacketConn(c, pc, metadata) + if err != nil { return - } else if bindUDPAddr.IP.IsUnspecified() { - serverAddr, err := resolveUDPAddr("udp", ss.Addr()) - if err != nil { - return nil, err - } - - bindUDPAddr.IP = serverAddr.IP } - return NewPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil + return NewPacketConn(pc, ss), nil } func NewSocks5(option Socks5Option) *Socks5 { diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index 8325e101..945e7ebd 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -7,6 +7,7 @@ import ( "net" "net/netip" + "github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/component/dialer" @@ -37,30 +38,12 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...) } - first := proxies[0] - last := proxies[len(proxies)-1] - - c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...) + c, err := r.streamContext(ctx, proxies, r.Base.DialOptions(opts...)...) if err != nil { - return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) - } - tcpKeepAlive(c) - - var currentMeta *C.Metadata - for _, proxy := range proxies[1:] { - currentMeta, err = addrToMetadata(proxy.Addr()) - if err != nil { - return nil, err - } - - c, err = first.StreamConn(c, currentMeta) - if err != nil { - return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) - } - - first = proxy + return nil, err } + last := proxies[len(proxies)-1] c, err = last.StreamConn(c, metadata) if err != nil { return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err) @@ -78,7 +61,9 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o } } - switch len(proxies) { + length := len(proxies) + + switch length { case 0: return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...) case 1: @@ -90,12 +75,16 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o } var ( - first = proxies[0] - last = proxies[len(proxies)-1] - rawUDPRelay bool - udpOverTCPEndIndex = -1 + firstIndex = 0 + nextIndex = 1 + lastUDPOverTCPIndex = -1 + rawUDPRelay = false + + first = proxies[firstIndex] + last = proxies[length-1] c net.Conn + cc net.Conn err error currentMeta *C.Metadata ) @@ -104,38 +93,47 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", last.Addr(), last.Name()) } - rawUDPRelay, udpOverTCPEndIndex = isRawUDPRelay(proxies) + rawUDPRelay, lastUDPOverTCPIndex = isRawUDPRelay(proxies) - if rawUDPRelay { + if first.Type() == C.Socks5 { + cc1, err1 := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...) + if err1 != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + cc = cc1 + tcpKeepAlive(cc) + + var pc net.PacketConn + pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...) + c = outbound.WrapConn(pc) + } else 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...)...) + firstIndex = lastUDPOverTCPIndex + nextIndex = firstIndex + 1 + first = proxies[firstIndex] + c, err = r.streamContext(ctx, proxies[:nextIndex], 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() - } - }() + if nextIndex < length { + for i, proxy := range proxies[nextIndex:] { // raw udp in loop + currentMeta, err = addrToMetadata(proxy.Addr()) + if err != nil { + return nil, err + } + currentMeta.NetWork = C.UDP - 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) { + if needResolveIP(first, currentMeta) { var ip netip.Addr ip, err = resolver.ResolveProxyServerHost(currentMeta.Host) if err != nil { @@ -144,28 +142,57 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o currentMeta.DstIP = ip } - currentMeta.NetWork = C.UDP - c, err = first.StreamPacketConn(c, currentMeta) - } else { - c, err = first.StreamConn(c, currentMeta) - } + if cc != nil { // socks5 + c, err = streamSocks5PacketConn(first, cc, c, currentMeta) + cc = nil + } else { + c, err = first.StreamPacketConn(c, currentMeta) + } - if err != nil { - return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) - } + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } - first = proxy + if proxy.Type() == C.Socks5 { + endIndex := nextIndex + i + 1 + cc, err = r.streamContext(ctx, proxies[:endIndex], r.Base.DialOptions(opts...)...) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + } + + first = proxy + } } - c, err = last.StreamPacketConn(c, metadata) + if cc != nil { + c, err = streamSocks5PacketConn(last, cc, c, metadata) + } else { + c, err = last.StreamPacketConn(c, metadata) + } if err != nil { - return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err) } return outbound.NewPacketConn(c.(net.PacketConn), r), nil } +// SupportUDP implements C.ProxyAdapter +func (r *Relay) SupportUDP() bool { + proxies := r.rawProxies(true) + + l := len(proxies) + + if l == 0 { + return true + } + + last := proxies[l-1] + + return isRawUDP(last) || last.SupportUDP() +} + // MarshalJSON implements C.ProxyAdapter func (r *Relay) MarshalJSON() ([]byte, error) { var all []string @@ -200,12 +227,47 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy { return proxies } +func (r *Relay) streamContext(ctx context.Context, proxies []C.Proxy, opts ...dialer.Option) (net.Conn, error) { + first := proxies[0] + + c, err := dialer.DialContext(ctx, "tcp", first.Addr(), opts...) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + tcpKeepAlive(c) + + if len(proxies) > 1 { + var currentMeta *C.Metadata + for _, proxy := range proxies[1:] { + currentMeta, err = addrToMetadata(proxy.Addr()) + if err != nil { + return nil, err + } + + c, err = first.StreamConn(c, currentMeta) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + + first = proxy + } + } + + return c, nil +} + +func streamSocks5PacketConn(proxy C.Proxy, cc, c net.Conn, metadata *C.Metadata) (net.Conn, error) { + pc, err := proxy.(*adapter.Proxy).ProxyAdapter.(*outbound.Socks5).StreamSocks5PacketConn(cc, c.(net.PacketConn), metadata) + return outbound.WrapConn(pc), err +} + func isRawUDPRelay(proxies []C.Proxy) (bool, int) { var ( - lastIndex = len(proxies) - 1 - isLastRawUDP = isRawUDP(proxies[lastIndex]) - isUDPOverTCP = false - udpOverTCPEndIndex = -1 + lastIndex = len(proxies) - 1 + last = proxies[lastIndex] + isLastRawUDP = isRawUDP(last) + isUDPOverTCP = false + lastUDPOverTCPIndex = -1 ) for i := lastIndex; i >= 0; i-- { @@ -213,26 +275,33 @@ func isRawUDPRelay(proxies []C.Proxy) (bool, int) { isUDPOverTCP = isUDPOverTCP || !isRawUDP(p) - if isLastRawUDP && isUDPOverTCP && udpOverTCPEndIndex == -1 { - udpOverTCPEndIndex = i + if isLastRawUDP && isUDPOverTCP && lastUDPOverTCPIndex == -1 { + lastUDPOverTCPIndex = i } } - return !isUDPOverTCP, udpOverTCPEndIndex + if !isLastRawUDP { + lastUDPOverTCPIndex = lastIndex + } + + return !isUDPOverTCP, lastUDPOverTCPIndex } func isRawUDP(proxy C.ProxyAdapter) bool { - if proxy.Type() == C.Shadowsocks || proxy.Type() == C.ShadowsocksR { + if proxy.Type() == C.Shadowsocks || proxy.Type() == C.ShadowsocksR || proxy.Type() == C.Socks5 { return true } return false } -func needResolveIP(proxy C.ProxyAdapter) bool { - if proxy.Type() == C.Vmess || proxy.Type() == C.Vless { - return true +func needResolveIP(proxy C.ProxyAdapter, metadata *C.Metadata) bool { + if metadata.Resolved() { + return false } - return false + if proxy.Type() != C.Vmess && proxy.Type() != C.Vless { + return false + } + return true } func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay { @@ -240,7 +309,6 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re 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 cbe9b489..46b86826 100644 --- a/adapter/outboundgroup/util.go +++ b/adapter/outboundgroup/util.go @@ -24,8 +24,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) { DstIP: netip.Addr{}, DstPort: port, } - err = nil - return + return addr, nil } else if ip.Is4() { addr = &C.Metadata{ AddrType: C.AtypIPv4,