From c63dd62ed2a156bff72b19fd9526f3a56d6997e0 Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Mon, 19 Dec 2022 17:02:04 +0800 Subject: [PATCH] chore: support relay native udp when using ss and ssr protocol --- adapter/outbound/base.go | 10 +++ adapter/outbound/shadowsocks.go | 16 +++++ adapter/outbound/shadowsocksr.go | 17 +++++ adapter/outboundgroup/relay.go | 119 +++++++++++++++++++++++++------ constant/adapters.go | 3 + 5 files changed, 144 insertions(+), 21 deletions(-) diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go index a7e068dd..d328d5c6 100644 --- a/adapter/outbound/base.go +++ b/adapter/outbound/base.go @@ -62,6 +62,16 @@ func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, op return nil, errors.New("no support") } +// ListenPacketOnPacketConn implements C.ProxyAdapter +func (b *Base) ListenPacketOnPacketConn(ctx context.Context, c C.PacketConn, metadata *C.Metadata) (C.PacketConn, error) { + return nil, errors.New("no support") +} + +// SupportLPPC implements C.ProxyAdapter +func (b *Base) SupportLPPC() bool { + return false +} + // ListenPacketOnStreamConn implements C.ProxyAdapter func (b *Base) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { return nil, errors.New("no support") diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 99f402cb..4760e806 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -120,6 +120,22 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta return newPacketConn(pc, ss), nil } +// ListenPacketOnPacketConn implements C.ProxyAdapter +func (ss *ShadowSocks) ListenPacketOnPacketConn(ctx context.Context, c C.PacketConn, metadata *C.Metadata) (_ C.PacketConn, err error) { + addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer) + if err != nil { + return nil, err + } + + pc := ss.method.DialPacketConn(&bufio.BindPacketConn{PacketConn: c, Addr: addr}) + return newPacketConn(pc, ss), nil +} + +// SupportLPPC implements C.ProxyAdapter +func (ss *ShadowSocks) SupportLPPC() bool { + return true +} + // ListenPacketOnStreamConn implements C.ProxyAdapter func (ss *ShadowSocks) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { if ss.option.UDPOverTCP { diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go index 93c16401..237b9c03 100644 --- a/adapter/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -91,6 +91,23 @@ func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Me return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil } +// ListenPacketOnPacketConn implements C.ProxyAdapter +func (ssr *ShadowSocksR) ListenPacketOnPacketConn(ctx context.Context, c C.PacketConn, metadata *C.Metadata) (_ C.PacketConn, err error) { + addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer) + if err != nil { + return nil, err + } + + pc := ssr.cipher.PacketConn(c) + pc = ssr.protocol.PacketConn(pc) + return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil +} + +// SupportLPPC implements C.ProxyAdapter +func (ssr *ShadowSocksR) SupportLPPC() bool { + return true +} + func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { // SSR protocol compatibility // https://github.com/Dreamacro/clash/pull/2056 diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index 66954375..ee302097 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "net" "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/component/dialer" @@ -29,14 +30,23 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d first := proxies[0] last := proxies[len(proxies)-1] - c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...) + var c net.Conn + var currentMeta *C.Metadata + var err error + + currentMeta, err = addrToMetadata(proxies[1].Addr()) + if err != nil { + return nil, err + } + + c, err = first.DialContext(ctx, currentMeta, 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:] { + first = proxies[1] + + for _, proxy := range proxies[2:] { currentMeta, err = addrToMetadata(proxy.Addr()) if err != nil { return nil, err @@ -77,23 +87,85 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o return proxies[0].ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...) } + udtId := -1 + for i, proxy := range proxies { + if !proxy.SupportUDP() { + return nil, fmt.Errorf("%s don't support udp", proxy.Name()) + } + if proxy.SupportUOT() { + udtId = i // we need the latest id, so don't break + } + } + first := proxies[0] last := proxies[len(proxies)-1] - 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) - } - tcpKeepAlive(c) - + var pc C.PacketConn var currentMeta *C.Metadata - for _, proxy := range proxies[1:] { + if udtId != -1 { + 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) + } + tcpKeepAlive(c) + + for _, proxy := range proxies[1 : udtId+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 + } + + if first == last { + currentMeta = metadata + } else { + currentMeta, err = addrToMetadata(proxies[udtId+1].Addr()) + if err != nil { + return nil, err + } + currentMeta.NetWork = C.UDP + } + c, err = first.StreamConn(c, currentMeta) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + + pc, err = first.ListenPacketOnStreamConn(c, currentMeta) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + if first == last { + return pc, nil + } + } else { + currentMeta, err = addrToMetadata(proxies[1].Addr()) + if err != nil { + return nil, err + } + currentMeta.NetWork = C.UDP + pc, err = first.ListenPacketContext(ctx, currentMeta, r.Base.DialOptions(opts...)...) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + udtId = 0 + } + + first = proxies[udtId+1] + for _, proxy := range proxies[udtId+2:] { currentMeta, err = addrToMetadata(proxy.Addr()) if err != nil { return nil, err } + currentMeta.NetWork = C.UDP - c, err = first.StreamConn(c, currentMeta) + pc, err = first.ListenPacketOnPacketConn(ctx, pc, currentMeta) if err != nil { return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) } @@ -101,17 +173,11 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o first = proxy } - c, err = last.StreamConn(c, metadata) + pc, err = last.ListenPacketOnPacketConn(ctx, pc, metadata) if err != nil { return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err) } - var pc C.PacketConn - pc, err = last.ListenPacketOnStreamConn(c, metadata) - if err != nil { - return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) - } - for i := len(chainProxies) - 2; i >= 0; i-- { pc.AppendToChains(chainProxies[i]) } @@ -127,8 +193,19 @@ func (r *Relay) SupportUDP() bool { if len(proxies) == 0 { // C.Direct return true } - last := proxies[len(proxies)-1] - return last.SupportUDP() && last.SupportUOT() + for i := len(proxies) - 1; i >= 0; i-- { + proxy := proxies[i] + if !proxy.SupportUDP() { + return false + } + if proxy.SupportUOT() { + return true + } + if !proxy.SupportLPPC() { + return false + } + } + return true } // MarshalJSON implements C.ProxyAdapter diff --git a/constant/adapters.go b/constant/adapters.go index f4b8ba4d..419708cf 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -108,6 +108,9 @@ type ProxyAdapter interface { SupportUOT() bool ListenPacketOnStreamConn(c net.Conn, metadata *Metadata) (PacketConn, error) + SupportLPPC() bool + ListenPacketOnPacketConn(ctx context.Context, c PacketConn, metadata *Metadata) (PacketConn, error) + // Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract. Unwrap(metadata *Metadata, touch bool) Proxy }