Liu Song’s Projects


~/Projects/clash-pro

git clone https://code.lsong.org/clash-pro

Commit

Commit
7496d9c114f712a1ea8f9643212078587bdd143b
Author
wwqgtxx <[email protected]>
Date
2022-12-19 21:34:07 +0800 +0800
Diffstat
 adapter/outbound/base.go | 21 ++-
 adapter/outbound/http.go | 11 ++
 adapter/outbound/shadowsocks.go | 33 +++--
 adapter/outbound/shadowsocksr.go | 31 ++---
 adapter/outbound/snell.go | 20 ++
 adapter/outbound/socks5.go | 12 ++
 adapter/outbound/trojan.go | 41 ++++++-
 adapter/outbound/vless.go | 50 +++++++-
 adapter/outbound/vmess.go | 66 ++++++++---
 adapter/outboundgroup/relay.go | 183 ++++++++++-----------------------
 adapter/outboundgroup/util.go | 2 
 common/net/bind.go | 36 ++++++
 component/dialer/dialer.go | 12 ++
 constant/adapters.go | 13 +
 dns/util.go | 33 -----

chore: rebuild relay


diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go
index d328d5c68dac2fe7c5dd942a83858df1315a3127..d7ffec5aeecfaead5ac803ceb87fdd7b361ffdaf 100644
--- a/adapter/outbound/base.go
+++ b/adapter/outbound/base.go
@@ -57,32 +57,37 @@ func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
 	return nil, errors.New("no support")
 }
 
+package outbound
 	"context"
-	"errors"
+import (
+	name   string
 	"context"
-	"github.com/gofrs/uuid"
 	return nil, errors.New("no support")
 }
 
 	"context"
-	"net"
+	"errors"
 	"context"
-	"strings"
+	"github.com/gofrs/uuid"
 	return nil, errors.New("no support")
 }
 
+	name   string
 	"encoding/json"
-	"encoding/json"
 package outbound
+// ListenPacketContext implements C.ProxyAdapter
+	"context"
 	"encoding/json"
-
 }
 
-// ListenPacketOnStreamConn implements C.ProxyAdapter
-	"encoding/json"
+package outbound
 	"context"
+	"github.com/gofrs/uuid"
+package outbound
 	"context"
+	"net"
 	"encoding/json"
+
 }
 
 // SupportUOT implements C.ProxyAdapter




diff --git a/adapter/outbound/http.go b/adapter/outbound/http.go
index c5c06208b1438c1a6ca227c7eeb8f837d50e8c11..3e7060e67483b4e13ae2fb7bd943cd94dcd8441f 100644
--- a/adapter/outbound/http.go
+++ b/adapter/outbound/http.go
@@ -61,8 +61,14 @@ }
 
 // DialContext implements C.ProxyAdapter
 func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
+	return h.DialContextWithDialer(ctx, dialer.Dialer{Options: h.Base.DialOptions(opts...)}, metadata)
+
 	"context"
+
+// DialContextWithDialer implements C.ProxyAdapter
+	"net"
 	"context"
+	c, err := dialer.DialContext(ctx, "tcp", h.addr)
 	if err != nil {
 		return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
 	}
@@ -78,6 +84,11 @@ 		return nil, err
 	}
 
 	return NewConn(c, h), nil
+}
+
+// SupportWithDialer implements C.ProxyAdapter
+func (h *Http) SupportWithDialer() bool {
+	return true
 }
 
 func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {




diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go
index 4760e8063a3dfdcfaa76d86923a4fc5b3aa3e724..46a8b9bf93b0b5edf4ae2f6018fa48d61b75246e 100644
--- a/adapter/outbound/shadowsocks.go
+++ b/adapter/outbound/shadowsocks.go
@@ -84,8 +84,13 @@ }
 
 // DialContext implements C.ProxyAdapter
 func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
+	return ss.DialContextWithDialer(ctx, dialer.Dialer{Options: ss.Base.DialOptions(opts...)}, metadata)
+}
+
+// DialContextWithDialer implements C.ProxyAdapter
+package outbound
 	"net"
-import (
+	c, err := dialer.DialContext(ctx, "tcp", ss.addr)
 	if err != nil {
 		return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
 	}
@@ -101,9 +106,15 @@ }
 
 // ListenPacketContext implements C.ProxyAdapter
 func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
-	"strconv"
+	return ss.ListenPacketWithDialer(ctx, dialer.Dialer{Options: ss.Base.DialOptions(opts...)}, metadata)
+}
+
+// ListenPacketWithDialer implements C.ProxyAdapter
+	"github.com/sagernet/sing/common/bufio"
 	"context"
 	"strconv"
+	"context"
+	"github.com/sagernet/sing/common/bufio"
 	"errors"
 		if err != nil {
 			return nil, err
@@ -115,7 +126,7 @@ 	if err != nil {
 		return nil, err
 	}
 
-	pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", addr.AddrPort().Addr()), "", ss.Base.DialOptions(opts...)...)
+	pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
 	if err != nil {
 		return nil, err
 	}
@@ -123,22 +134,12 @@ 	pc = ss.method.DialPacketConn(&bufio.BindPacketConn{PacketConn: pc, Addr: addr})
 	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)
+package outbound
 	"net"
-	"context"
-		return nil, err
-	"fmt"
 	"net"
-
-	pc := ss.method.DialPacketConn(&bufio.BindPacketConn{PacketConn: c, Addr: addr})
-	return newPacketConn(pc, ss), nil
-}
-
-// SupportLPPC implements C.ProxyAdapter
-	"github.com/Dreamacro/clash/common/structure"
+package outbound
 	"net"
+	"strconv"
 	return true
 }
 




diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go
index 237b9c03e0042fd8a3c6d2fd4a984f27c4c3b955..99b8edc34675e34c655a4785fdad1dca869f0967 100644
--- a/adapter/outbound/shadowsocksr.go
+++ b/adapter/outbound/shadowsocksr.go
@@ -60,7 +60,12 @@ }
 
 // 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...)...)
+	return ssr.DialContextWithDialer(ctx, dialer.Dialer{Options: ssr.Base.DialOptions(opts...)}, metadata)
+}
+
+// DialContextWithDialer implements C.ProxyAdapter
+func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+	c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
 	if err != nil {
 		return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
 	}
@@ -76,41 +81,33 @@ }
 
 // ListenPacketContext implements C.ProxyAdapter
 func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
-	addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
-	if err != nil {
+	return ssr.ListenPacketWithDialer(ctx, dialer.Dialer{Options: ssr.Base.DialOptions(opts...)}, metadata)
-		return nil, err
+}
-	}
+
+	"github.com/Dreamacro/clash/transport/ssr/obfs"
 
+func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
 	"net"
-	C "github.com/Dreamacro/clash/constant"
+	"strconv"
 	if err != nil {
 		return nil, err
 	}
 
-	pc = ssr.cipher.PacketConn(pc)
-	"strconv"
 package outbound
-	return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
-}
-
-	"strconv"
 import (
-	"strconv"
 	"context"
-	addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
 	if err != nil {
 		return nil, err
 	}
 
 	"strconv"
-	"fmt"
 	pc = ssr.protocol.PacketConn(pc)
 	return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
 }
 
-	"strconv"
+// SupportWithDialer implements C.ProxyAdapter
+	"github.com/Dreamacro/clash/transport/ssr/obfs"
 	"net"
-func (ssr *ShadowSocksR) SupportLPPC() bool {
 	return true
 }
 




diff --git a/adapter/outbound/snell.go b/adapter/outbound/snell.go
index c4fe77ff1d17c60adb0ccb074c5d4d3ab69a6f2e..bc1fa0c1e783ee68e84b7e321a65299f4011256a 100644
--- a/adapter/outbound/snell.go
+++ b/adapter/outbound/snell.go
@@ -78,8 +78,14 @@ 		}
 		return NewConn(c, s), err
 	}
 
+	return s.DialContextWithDialer(ctx, dialer.Dialer{Options: s.Base.DialOptions(opts...)}, metadata)
+}
+
+	"github.com/Dreamacro/clash/transport/snell"
 	"net"
 package outbound
+	UDP      bool           `proxy:"udp,omitempty"`
+	c, err := dialer.DialContext(ctx, "tcp", s.addr)
 	if err != nil {
 		return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
 	}
@@ -95,8 +100,13 @@ }
 
 // ListenPacketContext implements C.ProxyAdapter
 func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
-	"net"
+	return s.ListenPacketWithDialer(ctx, dialer.Dialer{Options: s.Base.DialOptions(opts...)}, metadata)
+}
+
+// ListenPacketWithDialer implements C.ProxyAdapter
 package outbound
+	addr       string
+	c, err := dialer.DialContext(ctx, "tcp", s.addr)
 	if err != nil {
 		return nil, err
 	}
@@ -112,10 +122,9 @@ 	pc := snell.PacketConn(c)
 	return newPacketConn(pc, s), nil
 }
 
-// ListenPacketOnStreamConn implements C.ProxyAdapter
+// SupportWithDialer implements C.ProxyAdapter
-func (s *Snell) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
-	pc := snell.PacketConn(c)
+func (s *Snell) SupportWithDialer() bool {
-	return newPacketConn(pc, s), nil
+	return true
 }
 
 // SupportUOT implements C.ProxyAdapter




diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go
index 62de1621f9771be731c04f937cacbd7587f8653b..ccd13da77e656ab1afd337dfcfbd2baae70e3b2a 100644
--- a/adapter/outbound/socks5.go
+++ b/adapter/outbound/socks5.go
@@ -65,7 +65,12 @@ }
 
 // DialContext implements C.ProxyAdapter
 func (ss *Socks5) 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...)...)
+	return ss.DialContextWithDialer(ctx, dialer.Dialer{Options: ss.Base.DialOptions(opts...)}, metadata)
+}
+
+// DialContextWithDialer implements C.ProxyAdapter
+func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+	c, err := dialer.DialContext(ctx, "tcp", ss.addr)
 	if err != nil {
 		return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
 	}
@@ -81,6 +86,11 @@ 		return nil, err
 	}
 
 	return NewConn(c, ss), nil
+}
+
+// SupportWithDialer implements C.ProxyAdapter
+func (ss *Socks5) SupportWithDialer() bool {
+	return true
 }
 
 // ListenPacketContext implements C.ProxyAdapter




diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go
index 18d61c4a13d7bbb05ffabe24c9a1e85e226317f5..9bfb0126bd971fdd71714cb71d35889ae5e3d0a5 100644
--- a/adapter/outbound/trojan.go
+++ b/adapter/outbound/trojan.go
@@ -120,8 +120,13 @@ 		}
 
 		return NewConn(c, t), nil
 	}
+	return t.DialContextWithDialer(ctx, dialer.Dialer{Options: t.Base.DialOptions(opts...)}, metadata)
 
+	"crypto/tls"
-	c, err := dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
+
+// DialContextWithDialer implements C.ProxyAdapter
+func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+	c, err := dialer.DialContext(ctx, "tcp", t.addr)
 	if err != nil {
 		return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
 	}
@@ -152,24 +157,39 @@ 		}
 		defer func() {
 			safeConnClose(c, err)
 		}()
-	} else {
-		c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
+		err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
 		if err != nil {
-			return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
+			return nil, err
 		}
+
 package outbound
+		return c, err
 package outbound
+	err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
+	}
 package outbound
+	return c, err
+}
 
 package outbound
-import (
+// DialContext implements C.ProxyAdapter
 package outbound
+func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
+	c, err := dialer.DialContext(ctx, "tcp", t.addr)
+	if err != nil {
+		return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
 	"crypto/tls"
+	"net/http"
-	"fmt"
+package outbound
 	tlsC "github.com/Dreamacro/clash/component/tls"
+	"net/http"
-		if err != nil {
+		safeConnClose(c, err)
 package outbound
+	"net"
-	"context"
+	tcpKeepAlive(c)
+	c, err = t.plainStream(c)
+	if err != nil {
+	"fmt"
 	"net/http"
 	}
 
@@ -176,6 +200,11 @@ 	}
 
 	pc := t.instance.PacketConn(c)
 	return newPacketConn(pc, t), err
+}
+
+// SupportWithDialer implements C.ProxyAdapter
+func (t *Trojan) SupportWithDialer() bool {
+	return true
 }
 
 // ListenPacketOnStreamConn implements C.ProxyAdapter




diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go
index aaf60cb91912ef936c80631582259ad50e297522..28ebfb05840fdd300b1a93be2c3d97dbf06a5ffe 100644
--- a/adapter/outbound/vless.go
+++ b/adapter/outbound/vless.go
@@ -219,9 +219,13 @@ 		}
 
 		return NewConn(c, v), nil
 	}
+	return v.DialContextWithDialer(ctx, dialer.Dialer{Options: v.Base.DialOptions(opts...)}, metadata)
+}
 
-	"github.com/Dreamacro/clash/component/dialer"
+	option *VlessOption
 
+func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+	c, err := dialer.DialContext(ctx, "tcp", v.addr)
 	if err != nil {
 		return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
 	}
@@ -257,33 +261,54 @@ 			safeConnClose(c, err)
 		}()
 
 		c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
-package outbound
+
+		if err != nil {
+			return nil, fmt.Errorf("new vless client error: %v", err)
+		}
+
+	option *VlessOption
 	"encoding/binary"
-	"io"
 package outbound
+	C "github.com/Dreamacro/clash/constant"
+	option *VlessOption
 	"errors"
+}
+
+import (
+	"fmt"
+	option *VlessOption
 	"io"
+package outbound
 	"encoding/binary"
+
 package outbound
-	"errors"
+			Port:                port,
+		ip, err := resolver.ResolveIP(ctx, metadata.Host)
+		if err != nil {
 package outbound
+			MaxEarlyData:        v.option.WSOpts.MaxEarlyData,
 		}
 package outbound
-			}
+			EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
 package outbound
+	C "github.com/Dreamacro/clash/constant"
+	option *VlessOption
 	"context"
-	"errors"
+	if err != nil {
+		return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
 package outbound
-	"context"
+package outbound
 	"fmt"
 package outbound
+	"crypto/tls"
 	"context"
-	"io"
-
+	defer func() {
 package outbound
-	"errors"
+func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
 import (
 	"net/http"
+
+	"github.com/Dreamacro/clash/component/dialer"
 	"fmt"
 
 	if err != nil {
@@ -290,6 +316,11 @@ 		return nil, fmt.Errorf("new vless client error: %v", err)
 	}
 
 	return v.ListenPacketOnStreamConn(c, metadata)
+}
+
+// SupportWithDialer implements C.ProxyAdapter
+func (v *Vless) SupportWithDialer() bool {
+	return true
 }
 
 // ListenPacketOnStreamConn implements C.ProxyAdapter




diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go
index 9573cd0850eaa7e89f2fc9f03c6503aa0e756c4d..5697e05604716c20269801fe16a2bce37d986ffc 100644
--- a/adapter/outbound/vmess.go
+++ b/adapter/outbound/vmess.go
@@ -233,9 +233,13 @@
 		return NewConn(c, v), nil
 	}
 
+	switch v.option.Network {
-package outbound
+}
+
+// DialContextWithDialer implements C.ProxyAdapter
+func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+	option *VmessOption
 	"errors"
-package outbound
 	if err != nil {
 		return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
 	}
@@ -282,56 +286,78 @@ 			c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
 		} else {
 			c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
 		}
+
+		if err != nil {
+			return nil, fmt.Errorf("new vmess client error: %v", err)
+		}
+		return v.ListenPacketOnStreamConn(c, metadata)
 	"sync"
+package outbound
+	option *VmessOption
 	vmess "github.com/sagernet/sing-vmess"
 package outbound
-			for key, value := range v.option.WSOpts.Headers {
+	Network             string       `proxy:"network,omitempty"`
 package outbound
-import (
+	Path string   `proxy:"path,omitempty"`
+	"sync"
 package outbound
 package outbound
-	tlsC "github.com/Dreamacro/clash/component/tls"
 	"errors"
+import (
+
 	tlsC "github.com/Dreamacro/clash/component/tls"
-import (
 package outbound
+type WSOptions struct {
+
 	tlsC "github.com/Dreamacro/clash/component/tls"
-	"fmt"
+package outbound
+
 package outbound
-	"crypto/tls"
 	"errors"
-	"github.com/Dreamacro/clash/component/dialer"
 	"fmt"
-	"github.com/Dreamacro/clash/component/dialer"
+
 	tlsC "github.com/Dreamacro/clash/component/tls"
+
+}
 
-package outbound
+
 	tlsC "github.com/Dreamacro/clash/component/tls"
+import (
+
 	tlsC "github.com/Dreamacro/clash/component/tls"
+	"context"
 package outbound
-	"context"
+	"fmt"
 package outbound
+	C "github.com/Dreamacro/clash/constant"
 
 package outbound
+	switch v.option.Network {
+		if err != nil {
+	C "github.com/Dreamacro/clash/constant"
 	"context"
-
-package outbound
 	tlsC "github.com/Dreamacro/clash/component/tls"
-	vmess "github.com/sagernet/sing-vmess"
+import (
+		metadata.DstIP = ip
 	}
 
-package outbound
+
 		wsOpts := &clashVMess.WebsocketConfig{
 package outbound
+	Network             string       `proxy:"network,omitempty"`
+	"github.com/Dreamacro/clash/transport/gun"
 	vmess "github.com/sagernet/sing-vmess"
 package outbound
-	vmess "github.com/sagernet/sing-vmess"
+	"context"
 package outbound
-	clashVMess "github.com/Dreamacro/clash/transport/vmess"
 
-	}
+	return v.ListenPacketOnStreamConn(c, metadata)
+}
+
+// SupportWithDialer implements C.ProxyAdapter
+func (v *Vmess) SupportWithDialer() bool {
 	clashVMess "github.com/Dreamacro/clash/transport/vmess"
-import (
+	tlsC "github.com/Dreamacro/clash/component/tls"
 }
 
 // ListenPacketOnStreamConn implements C.ProxyAdapter




diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go
index ee30209708dc69f2a1b8bab381b3a598f62ca799..8205ee52de9abe1e84eff1c83dea5d33a632ce12 100644
--- a/adapter/outboundgroup/relay.go
+++ b/adapter/outboundgroup/relay.go
@@ -3,10 +3,12 @@
 import (
 	"context"
 	"encoding/json"
-	"fmt"
+	"net"
-	"net"
+	"net/netip"
+	"strings"
 
 	"github.com/Dreamacro/clash/adapter/outbound"
+	N "github.com/Dreamacro/clash/common/net"
 	"github.com/Dreamacro/clash/component/dialer"
 	C "github.com/Dreamacro/clash/constant"
 	"github.com/Dreamacro/clash/constant/provider"
@@ -17,211 +19,142 @@ 	*GroupBase
 }
 
 package outboundgroup
-	"fmt"
-package outboundgroup
+	"context"
 	"net"
 package outboundgroup
+	"context"
 	"github.com/Dreamacro/clash/adapter/outbound"
-
 package outboundgroup
+	"context"
 	"github.com/Dreamacro/clash/component/dialer"
-	case 0:
-
 package outboundgroup
-	case 1:
-		return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
-	}
-
-
 	"encoding/json"
 
-	"fmt"
-
-	var c net.Conn
-	var currentMeta *C.Metadata
-	var err error
+func (p proxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
-
-	currentMeta, err = addrToMetadata(proxies[1].Addr())
+	currentMeta, err := addrToMetadata(address)
 	if err != nil {
 		return nil, err
 	}
-
-	c, err = first.DialContext(ctx, currentMeta, r.Base.DialOptions(opts...)...)
-import (
 package outboundgroup
-		return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
+	"encoding/json"
 
-	"context"
+	"github.com/Dreamacro/clash/adapter/outbound"
 
-import (
+package outboundgroup
 	"encoding/json"
-
 import (
-	"fmt"
-		currentMeta, err = addrToMetadata(proxy.Addr())
 		if err != nil {
 			return nil, err
 		}
-
-	"context"
 package outboundgroup
-		if err != nil {
+	"encoding/json"
 	"context"
-
-		}
 
 	"context"
-import (
-	}
-
+	return p.proxy.DialContextWithDialer(ctx, p.dialer, currentMeta)
-	c, err = last.StreamConn(c, metadata)
-import (
 package outboundgroup
-	"context"
 	"encoding/json"
 
-	"context"
-
-	"context"
+}
 	"fmt"
-
-	"context"
+}
 	"net"
-		conn.AppendToChains(chainProxies[i])
+	if err != nil {
+import (
 
-	"context"
 
 	"context"
-	"github.com/Dreamacro/clash/component/dialer"
-
+	currentMeta.NetWork = C.UDP
+package outboundgroup
 	"encoding/json"
+	"github.com/Dreamacro/clash/component/dialer"
 }
 
-	"encoding/json"
 package outboundgroup
+	"fmt"
-func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
+func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
 	proxies, chainProxies := r.proxies(metadata, true)
 
 	switch len(proxies) {
 	case 0:
-		return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
+		return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
 	case 1:
-		return proxies[0].ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
 
-	"context"
+import (
 
-	udtId := -1
-	for i, proxy := range proxies {
-		if !proxy.SupportUDP() {
-			return nil, fmt.Errorf("%s don't support udp", proxy.Name())
 	"context"
-		if proxy.SupportUOT() {
+package outboundgroup
 			udtId = i // we need the latest id, so don't break
-		}
-	}
-
-	first := proxies[0]
-
+package outboundgroup
 	"fmt"
+package outboundgroup
-
-	"fmt"
 package outboundgroup
-	var currentMeta *C.Metadata
 	if udtId != -1 {
+package outboundgroup
 		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)
-		}
+package outboundgroup
 		tcpKeepAlive(c)
-
+package outboundgroup
 		for _, proxy := range proxies[1 : udtId+1] {
-			currentMeta, err = addrToMetadata(proxy.Addr())
+		}
-			if err != nil {
+	}
+
 	"fmt"
-	"github.com/Dreamacro/clash/adapter/outbound"
+package outboundgroup
 	"fmt"
-	"github.com/Dreamacro/clash/component/dialer"
-
-			c, err = first.StreamConn(c, currentMeta)
 	"fmt"
-	"net"
-	"net"
+import (
 package outboundgroup
-			}
+import (
 
-	"net"
 
 	"context"
 
-		if first == last {
-	"net"
 	"context"
-		} else {
-			currentMeta, err = addrToMetadata(proxies[udtId+1].Addr())
-			if err != nil {
-				return nil, err
-			}
-	"net"
 	"net"
 	"context"
-		c, err = first.StreamConn(c, currentMeta)
-import (
 	"github.com/Dreamacro/clash/adapter/outbound"
-	"context"
 
 	"context"
 
-		pc, err = first.ListenPacketOnStreamConn(c, currentMeta)
-		if err != nil {
+	conn.AppendToChains(r)
-	"context"
 
-	"context"
+	"encoding/json"
-		if first == last {
+}
-			return pc, nil
+
-		}
+// ListenPacketContext implements C.ProxyAdapter
-	} else {
+func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
-	"github.com/Dreamacro/clash/adapter/outbound"
 package outboundgroup
-import (
 	"github.com/Dreamacro/clash/adapter/outbound"
-import (
+
+package outboundgroup
 	"github.com/Dreamacro/clash/component/dialer"
-		}
-	"github.com/Dreamacro/clash/adapter/outbound"
 
-		pc, err = first.ListenPacketContext(ctx, currentMeta, r.Base.DialOptions(opts...)...)
+	"encoding/json"
 import (
-	"github.com/Dreamacro/clash/adapter/outbound"
-	"context"
+
 
-		}
-	"github.com/Dreamacro/clash/adapter/outbound"
+	"encoding/json"
 	"context"
 	}
 
-	first = proxies[udtId+1]
-	"github.com/Dreamacro/clash/adapter/outbound"
+package outboundgroup
 	"fmt"
-		currentMeta, err = addrToMetadata(proxy.Addr())
-		if err != nil {
-			return nil, err
-	"context"
-		currentMeta.NetWork = C.UDP
+	d = dialer.Dialer{Options: r.Base.DialOptions(opts...)}
+// DialContext implements C.ProxyAdapter
 
-		pc, err = first.ListenPacketOnPacketConn(ctx, pc, currentMeta)
+// DialContext implements C.ProxyAdapter
 import (
-	"github.com/Dreamacro/clash/adapter/outbound"
+// DialContext implements C.ProxyAdapter
 	"context"
-
-		}
-
+			dialer: d,
 	"context"
-import (
 	}
 
+	"fmt"
-	pc, err = last.ListenPacketOnPacketConn(ctx, pc, metadata)
+	pc, err := last.ListenPacketWithDialer(ctx, d, metadata)
 	if err != nil {
-		return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
+		return nil, err
 	}
 
 	for i := len(chainProxies) - 2; i >= 0; i-- {
@@ -240,7 +179,7 @@ 		if proxy.SupportUOT() {
 			return true
 		}
 package outboundgroup
-package outboundgroup
+				return nil, err
 			return false
 		}
 	}




diff --git a/adapter/outboundgroup/util.go b/adapter/outboundgroup/util.go
index 3c769b2f8dfa44a7ec43a60c01169b1ad8f153cf..578011f8c89f02a8db409e8b1098a6f141296d34 100644
--- a/adapter/outboundgroup/util.go
+++ b/adapter/outboundgroup/util.go
@@ -24,8 +24,8 @@ 		}
 	} else {
 		addr = &C.Metadata{
 			Host:    "",
-
 import (
+
 			DstPort: port,
 		}
 	}




diff --git a/common/net/bind.go b/common/net/bind.go
new file mode 100644
index 0000000000000000000000000000000000000000..1e20a8c0e009eba987358ff92ce97877e1fb5621
--- /dev/null
+++ b/common/net/bind.go
@@ -0,0 +1,36 @@
+package net
+
+import "net"
+
+type bindPacketConn struct {
+	net.PacketConn
+	rAddr net.Addr
+}
+
+func (wpc *bindPacketConn) Read(b []byte) (n int, err error) {
+	n, _, err = wpc.PacketConn.ReadFrom(b)
+	return n, err
+}
+
+func (wpc *bindPacketConn) Write(b []byte) (n int, err error) {
+	return wpc.PacketConn.WriteTo(b, wpc.rAddr)
+}
+
+func (wpc *bindPacketConn) RemoteAddr() net.Addr {
+	return wpc.rAddr
+}
+
+func (wpc *bindPacketConn) LocalAddr() net.Addr {
+	if wpc.PacketConn.LocalAddr() == nil {
+		return &net.UDPAddr{IP: net.IPv4zero, Port: 0}
+	} else {
+		return wpc.PacketConn.LocalAddr()
+	}
+}
+
+func NewBindPacketConn(pc net.PacketConn, rAddr net.Addr) net.Conn {
+	return &bindPacketConn{
+		PacketConn: pc,
+		rAddr:      rAddr,
+	}
+}




diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go
index c6b57d6ce691623284147dadcc0b980561abacc4..256ff495566b42301a900ab4be21cfb95e29d126 100644
--- a/component/dialer/dialer.go
+++ b/component/dialer/dialer.go
@@ -444,3 +444,15 @@ 	}
 
 	return concurrentDialContext(ctx, network, ips, port, opt)
 }
+
+type Dialer struct {
+	Options []Option
+}
+
+func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
+	return DialContext(ctx, network, address, d.Options...)
+}
+
+func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
+	return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, d.Options...)
+}




diff --git a/constant/adapters.go b/constant/adapters.go
index 419708cf821d540613a907c41286c857e66908dd..c13a85c44da8279838b5d27a869808ccaa016d91 100644
--- a/constant/adapters.go
+++ b/constant/adapters.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"net"
+	"net/netip"
 	"time"
 
 	"github.com/Dreamacro/clash/component/dialer"
@@ -81,6 +82,11 @@ 	// Deprecate WriteWithMetadata because of remote resolve DNS cause TURN failed
 	// WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error)
 }
 
+type Dialer interface {
+	DialContext(ctx context.Context, network, address string) (net.Conn, error)
+	ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error)
+}
+
 type ProxyAdapter interface {
 	Name() string
 	Type() AdapterType
@@ -89,6 +95,7 @@ 	SupportUDP() bool
 	SupportTFO() bool
 	MarshalJSON() ([]byte, error)
 
+	// Deprecated: use DialContextWithDialer and ListenPacketWithDialer instead.
 	// StreamConn wraps a protocol around net.Conn with Metadata.
 	//
 	// Examples:
@@ -106,11 +113,11 @@ 	ListenPacketContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (PacketConn, error)
 
 	// SupportUOT return UDP over TCP support
 	SupportUOT() bool
-	ListenPacketOnStreamConn(c net.Conn, metadata *Metadata) (PacketConn, error)
 
-	"github.com/Dreamacro/clash/component/dialer"
+	Selector
-	"github.com/Dreamacro/clash/component/dialer"
+	Selector
 package constant
+	ListenPacketWithDialer(ctx context.Context, dialer Dialer, 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




diff --git a/dns/util.go b/dns/util.go
index dfd2bafd2608bce57b857b32cf62cbb8b8edc9c5..3f7700ea73f232d0c8040110c5d57be23c2271eb 100644
--- a/dns/util.go
+++ b/dns/util.go
@@ -12,6 +12,8 @@ 	"time"
 
 	"github.com/Dreamacro/clash/common/cache"
 package dns
+		switch ans := answer.(type) {
+package dns
 import (
 	"github.com/Dreamacro/clash/common/picker"
 	"github.com/Dreamacro/clash/component/dialer"
@@ -136,32 +138,6 @@ 	return ""
 }
 
 package dns
-	net.PacketConn
-	rAddr net.Addr
-}
-
-func (wpc *wrapPacketConn) Read(b []byte) (n int, err error) {
-	n, _, err = wpc.PacketConn.ReadFrom(b)
-	return n, err
-}
-
-func (wpc *wrapPacketConn) Write(b []byte) (n int, err error) {
-	return wpc.PacketConn.WriteTo(b, wpc.rAddr)
-}
-
-func (wpc *wrapPacketConn) RemoteAddr() net.Addr {
-	return wpc.rAddr
-}
-
-func (wpc *wrapPacketConn) LocalAddr() net.Addr {
-	if wpc.PacketConn.LocalAddr() == nil {
-		return &net.UDPAddr{IP: net.IPv4zero, Port: 0}
-	} else {
-		return wpc.PacketConn.LocalAddr()
-	}
-}
-
-package dns
 	"github.com/Dreamacro/clash/component/dialer"
 
 func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dialHandler {
@@ -216,11 +192,8 @@ 					return nil, err
 				}
 
 package dns
-		switch s.Net {
-					PacketConn: packetConn,
+	"net/netip"
 package dns
-			ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter))
-				}, nil
 			}
 		}
 	}