fix: dns dial to wrong target
Some checks are pending
Trigger CMFA Update / trigger-CMFA-update (push) Waiting to run

This commit is contained in:
wwqgtxx 2024-06-15 00:33:03 +08:00
parent ad5bc51c77
commit 40f40f6d24
7 changed files with 142 additions and 175 deletions

View File

@ -3,7 +3,6 @@ package outbound
import ( import (
"context" "context"
"errors" "errors"
"net/netip"
"os" "os"
"strconv" "strconv"
@ -58,7 +57,7 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
} }
metadata.DstIP = ip metadata.DstIP = ip
} }
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort)) pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", metadata.AddrPort())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -5,28 +5,20 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net" "net"
"net/netip"
"strings" "strings"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/randv2"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
type client struct { type client struct {
*D.Client *D.Client
r *Resolver port string
port string host string
host string dialer *dnsDialer
iface string addr string
proxyAdapter C.ProxyAdapter
proxyName string
addr string
} }
var _ dnsClient = (*client)(nil) var _ dnsClient = (*client)(nil)
@ -49,38 +41,13 @@ func (c *client) Address() string {
} }
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) { func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
var (
ip netip.Addr
err error
)
if c.r == nil {
// a default ip dns
if ip, err = netip.ParseAddr(c.host); err != nil {
return nil, fmt.Errorf("dns %s not a valid ip", c.host)
}
} else {
ips, err := resolver.LookupIPWithResolver(ctx, c.host, c.r)
if err != nil {
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
} else if len(ips) == 0 {
return nil, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, c.host)
}
ip = ips[randv2.IntN(len(ips))]
}
network := "udp" network := "udp"
if strings.HasPrefix(c.Client.Net, "tcp") { if strings.HasPrefix(c.Client.Net, "tcp") {
network = "tcp" network = "tcp"
} }
var options []dialer.Option addr := net.JoinHostPort(c.host, c.port)
if c.iface != "" { conn, err := c.dialer.DialContext(ctx, network, addr)
options = append(options, dialer.WithInterface(c.iface))
}
dialHandler := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)
addr := net.JoinHostPort(ip.String(), c.port)
conn, err := dialHandler(ctx, network, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -115,7 +82,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
tcpClient.Net = "tcp" tcpClient.Net = "tcp"
network = "tcp" network = "tcp"
log.Debugln("[DNS] Truncated reply from %s:%s for %s over UDP, retrying over TCP", c.host, c.port, m.Question[0].String()) log.Debugln("[DNS] Truncated reply from %s:%s for %s over UDP, retrying over TCP", c.host, c.port, m.Question[0].String())
dConn.Conn, err = dialHandler(ctx, network, addr) dConn.Conn, err = c.dialer.DialContext(ctx, network, addr)
if err != nil { if err != nil {
ch <- result{msg, err} ch <- result{msg, err}
return return

View File

@ -6,7 +6,6 @@ import "github.com/metacubex/mihomo/tunnel"
const RespectRules = tunnel.DnsRespectRules const RespectRules = tunnel.DnsRespectRules
type dialHandler = tunnel.DnsDialHandler type dnsDialer = tunnel.DNSDialer
var getDialHandler = tunnel.GetDnsDialHandler var newDNSDialer = tunnel.NewDNSDialer
var listenPacket = tunnel.DnsListenPacket

View File

@ -62,10 +62,8 @@ type dnsOverHTTPS struct {
quicConfig *quic.Config quicConfig *quic.Config
quicConfigGuard sync.Mutex quicConfigGuard sync.Mutex
url *url.URL url *url.URL
r *Resolver
httpVersions []C.HTTPVersion httpVersions []C.HTTPVersion
proxyAdapter C.ProxyAdapter dialer *dnsDialer
proxyName string
addr string addr string
} }
@ -85,11 +83,9 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
} }
doh := &dnsOverHTTPS{ doh := &dnsOverHTTPS{
url: u, url: u,
addr: u.String(), addr: u.String(),
r: r, dialer: newDNSDialer(r, proxyAdapter, proxyName),
proxyAdapter: proxyAdapter,
proxyName: proxyName,
quicConfig: &quic.Config{ quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod, KeepAlivePeriod: QUICKeepAlivePeriod,
TokenStore: newQUICTokenStore(), TokenStore: newQUICTokenStore(),
@ -388,13 +384,12 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
nextProtos = append(nextProtos, string(v)) nextProtos = append(nextProtos, string(v))
} }
tlsConfig.NextProtos = nextProtos tlsConfig.NextProtos = nextProtos
dialContext := getDialHandler(doh.r, doh.proxyAdapter, doh.proxyName)
if slices.Contains(doh.httpVersions, C.HTTPVersion3) { if slices.Contains(doh.httpVersions, C.HTTPVersion3) {
// First, we attempt to create an HTTP3 transport. If the probe QUIC // First, we attempt to create an HTTP3 transport. If the probe QUIC
// connection is established successfully, we'll be using HTTP3 for this // connection is established successfully, we'll be using HTTP3 for this
// upstream. // upstream.
transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext) transportH3, err := doh.createTransportH3(ctx, tlsConfig)
if err == nil { if err == nil {
log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String()) log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String())
return transportH3, nil return transportH3, nil
@ -410,7 +405,7 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
transport := &http.Transport{ transport := &http.Transport{
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
DisableCompression: true, DisableCompression: true,
DialContext: dialContext, DialContext: doh.dialer.DialContext,
IdleConnTimeout: transportDefaultIdleConnTimeout, IdleConnTimeout: transportDefaultIdleConnTimeout,
MaxConnsPerHost: dohMaxConnsPerHost, MaxConnsPerHost: dohMaxConnsPerHost,
MaxIdleConns: dohMaxIdleConns, MaxIdleConns: dohMaxIdleConns,
@ -490,13 +485,12 @@ func (h *http3Transport) Close() (err error) {
func (doh *dnsOverHTTPS) createTransportH3( func (doh *dnsOverHTTPS) createTransportH3(
ctx context.Context, ctx context.Context,
tlsConfig *tls.Config, tlsConfig *tls.Config,
dialContext dialHandler,
) (roundTripper http.RoundTripper, err error) { ) (roundTripper http.RoundTripper, err error) {
if !doh.supportsH3() { if !doh.supportsH3() {
return nil, errors.New("HTTP3 support is not enabled") return nil, errors.New("HTTP3 support is not enabled")
} }
addr, err := doh.probeH3(ctx, tlsConfig, dialContext) addr, err := doh.probeH3(ctx, tlsConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -534,7 +528,7 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
IP: net.ParseIP(ip), IP: net.ParseIP(ip),
Port: portInt, Port: portInt,
} }
conn, err := listenPacket(ctx, doh.proxyAdapter, doh.proxyName, "udp", addr, doh.r) conn, err := doh.dialer.ListenPacket(ctx, "udp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -557,12 +551,11 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
func (doh *dnsOverHTTPS) probeH3( func (doh *dnsOverHTTPS) probeH3(
ctx context.Context, ctx context.Context,
tlsConfig *tls.Config, tlsConfig *tls.Config,
dialContext dialHandler,
) (addr string, err error) { ) (addr string, err error) {
// We're using bootstrapped address instead of what's passed to the function // We're using bootstrapped address instead of what's passed to the function
// it does not create an actual connection, but it helps us determine // it does not create an actual connection, but it helps us determine
// what IP is actually reachable (when there are v4/v6 addresses). // what IP is actually reachable (when there are v4/v6 addresses).
rawConn, err := dialContext(ctx, "udp", doh.url.Host) rawConn, err := doh.dialer.DialContext(ctx, "udp", doh.url.Host)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to dial: %w", err) return "", fmt.Errorf("failed to dial: %w", err)
} }
@ -592,7 +585,7 @@ func (doh *dnsOverHTTPS) probeH3(
chQuic := make(chan error, 1) chQuic := make(chan error, 1)
chTLS := make(chan error, 1) chTLS := make(chan error, 1)
go doh.probeQUIC(ctx, addr, probeTLSCfg, chQuic) go doh.probeQUIC(ctx, addr, probeTLSCfg, chQuic)
go doh.probeTLS(ctx, dialContext, probeTLSCfg, chTLS) go doh.probeTLS(ctx, probeTLSCfg, chTLS)
select { select {
case quicErr := <-chQuic: case quicErr := <-chQuic:
@ -635,10 +628,10 @@ func (doh *dnsOverHTTPS) probeQUIC(ctx context.Context, addr string, tlsConfig *
// probeTLS attempts to establish a TLS connection to the specified address. We // probeTLS attempts to establish a TLS connection to the specified address. We
// run probeQUIC and probeTLS in parallel and see which one is faster. // run probeQUIC and probeTLS in parallel and see which one is faster.
func (doh *dnsOverHTTPS) probeTLS(ctx context.Context, dialContext dialHandler, tlsConfig *tls.Config, ch chan error) { func (doh *dnsOverHTTPS) probeTLS(ctx context.Context, tlsConfig *tls.Config, ch chan error) {
startTime := time.Now() startTime := time.Now()
conn, err := doh.tlsDial(ctx, dialContext, "tcp", tlsConfig) conn, err := doh.tlsDial(ctx, "tcp", tlsConfig)
if err != nil { if err != nil {
ch <- fmt.Errorf("opening TLS connection: %w", err) ch <- fmt.Errorf("opening TLS connection: %w", err)
return return
@ -694,10 +687,10 @@ func isHTTP3(client *http.Client) (ok bool) {
// tlsDial is basically the same as tls.DialWithDialer, but we will call our own // tlsDial is basically the same as tls.DialWithDialer, but we will call our own
// dialContext function to get connection. // dialContext function to get connection.
func (doh *dnsOverHTTPS) tlsDial(ctx context.Context, dialContext dialHandler, network string, config *tls.Config) (*tls.Conn, error) { func (doh *dnsOverHTTPS) tlsDial(ctx context.Context, network string, config *tls.Config) (*tls.Conn, error) {
// We're using bootstrapped address instead of what's passed // We're using bootstrapped address instead of what's passed
// to the function. // to the function.
rawConn, err := dialContext(ctx, network, doh.url.Host) rawConn, err := doh.dialer.DialContext(ctx, network, doh.url.Host)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -60,10 +60,8 @@ type dnsOverQUIC struct {
bytesPool *sync.Pool bytesPool *sync.Pool
bytesPoolGuard sync.Mutex bytesPoolGuard sync.Mutex
addr string addr string
proxyAdapter C.ProxyAdapter dialer *dnsDialer
proxyName string
r *Resolver
} }
// type check // type check
@ -72,10 +70,8 @@ var _ dnsClient = (*dnsOverQUIC)(nil)
// newDoQ returns the DNS-over-QUIC Upstream. // newDoQ returns the DNS-over-QUIC Upstream.
func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) { func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) {
doq := &dnsOverQUIC{ doq := &dnsOverQUIC{
addr: addr, addr: addr,
proxyAdapter: proxyAdapter, dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
proxyName: proxyName,
r: resolver,
quicConfig: &quic.Config{ quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod, KeepAlivePeriod: QUICKeepAlivePeriod,
TokenStore: newQUICTokenStore(), TokenStore: newQUICTokenStore(),
@ -300,7 +296,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
// we're using bootstrapped address instead of what's passed to the function // we're using bootstrapped address instead of what's passed to the function
// it does not create an actual connection, but it helps us determine // it does not create an actual connection, but it helps us determine
// what IP is actually reachable (when there're v4/v6 addresses). // what IP is actually reachable (when there're v4/v6 addresses).
rawConn, err := getDialHandler(doq.r, doq.proxyAdapter, doq.proxyName)(ctx, "udp", doq.addr) rawConn, err := doq.dialer.DialContext(ctx, "udp", doq.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open a QUIC connection: %w", err) return nil, fmt.Errorf("failed to open a QUIC connection: %w", err)
} }
@ -315,7 +311,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
p, err := strconv.Atoi(port) p, err := strconv.Atoi(port)
udpAddr := net.UDPAddr{IP: net.ParseIP(ip), Port: p} udpAddr := net.UDPAddr{IP: net.ParseIP(ip), Port: p}
udp, err := listenPacket(ctx, doq.proxyAdapter, doq.proxyName, "udp", addr, doq.r) udp, err := doq.dialer.ListenPacket(ctx, "udp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -12,6 +12,7 @@ import (
"github.com/metacubex/mihomo/common/nnip" "github.com/metacubex/mihomo/common/nnip"
"github.com/metacubex/mihomo/common/picker" "github.com/metacubex/mihomo/common/picker"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@ -115,6 +116,11 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
continue continue
} }
var options []dialer.Option
if s.Interface != "" {
options = append(options, dialer.WithInterface(s.Interface))
}
host, port, _ := net.SplitHostPort(s.Addr) host, port, _ := net.SplitHostPort(s.Addr)
ret = append(ret, &client{ ret = append(ret, &client{
Client: &D.Client{ Client: &D.Client{
@ -125,12 +131,9 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
UDPSize: 4096, UDPSize: 4096,
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
}, },
port: port, port: port,
host: host, host: host,
iface: s.Interface, dialer: newDNSDialer(resolver, s.ProxyAdapter, s.ProxyName, options...),
r: resolver,
proxyAdapter: s.ProxyAdapter,
proxyName: s.ProxyName,
}) })
} }
return ret return ret

View File

@ -6,7 +6,6 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"net/netip"
"strings" "strings"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
@ -18,110 +17,121 @@ import (
const DnsRespectRules = "RULES" const DnsRespectRules = "RULES"
type DnsDialHandler func(ctx context.Context, network, addr string) (net.Conn, error) type DNSDialer struct {
r resolver.Resolver
proxyAdapter C.ProxyAdapter
proxyName string
opts []dialer.Option
}
func GetDnsDialHandler(r resolver.Resolver, proxyAdapter C.ProxyAdapter, proxyName string, opts ...dialer.Option) DnsDialHandler { func NewDNSDialer(r resolver.Resolver, proxyAdapter C.ProxyAdapter, proxyName string, opts ...dialer.Option) *DNSDialer {
return func(ctx context.Context, network, addr string) (net.Conn, error) { return &DNSDialer{r: r, proxyAdapter: proxyAdapter, proxyName: proxyName, opts: opts}
if len(proxyName) == 0 && proxyAdapter == nil { }
opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...) func (d *DNSDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
} else { r := d.r
metadata := &C.Metadata{ proxyName := d.proxyName
NetWork: C.TCP, proxyAdapter := d.proxyAdapter
Type: C.INNER, opts := d.opts
} var rule C.Rule
err := metadata.SetRemoteAddress(addr) // tcp can resolve host by remote metadata := &C.Metadata{
NetWork: C.TCP,
Type: C.INNER,
}
err := metadata.SetRemoteAddress(addr) // tcp can resolve host by remote
if err != nil {
return nil, err
}
if !strings.Contains(network, "tcp") {
metadata.NetWork = C.UDP
if !metadata.Resolved() {
// udp must resolve host first
dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !strings.Contains(network, "tcp") { metadata.DstIP = dstIP
metadata.NetWork = C.UDP }
if !metadata.Resolved() { }
// udp must resolve host first
dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, err
}
metadata.DstIP = dstIP
}
}
var rule C.Rule if proxyAdapter == nil && len(proxyName) != 0 {
if proxyAdapter == nil { if proxyName == DnsRespectRules {
if proxyName == DnsRespectRules { if !metadata.Resolved() {
if !metadata.Resolved() { // resolve here before resolveMetadata to avoid its inner resolver.ResolveIP
// resolve here before resolveMetadata to avoid its inner resolver.ResolveIP dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, err
}
metadata.DstIP = dstIP
}
proxyAdapter, rule, err = resolveMetadata(metadata)
if err != nil {
return nil, err
}
} else {
var ok bool
proxyAdapter, ok = Proxies()[proxyName]
if !ok {
opts = append(opts, dialer.WithInterface(proxyName))
}
}
}
if strings.Contains(network, "tcp") {
if proxyAdapter == nil {
opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...)
}
if proxyAdapter.IsL3Protocol(metadata) { // L3 proxy should resolve domain before to avoid loopback
if !metadata.Resolved() {
dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, err
}
metadata.DstIP = dstIP
}
metadata.Host = "" // clear host to avoid double resolve in proxy
}
conn, err := proxyAdapter.DialContext(ctx, metadata, opts...)
if err != nil { if err != nil {
logMetadataErr(metadata, rule, proxyAdapter, err)
return nil, err return nil, err
} }
logMetadata(metadata, rule, conn) metadata.DstIP = dstIP
}
conn = statistic.NewTCPTracker(conn, statistic.DefaultManager, metadata, rule, 0, 0, false) proxyAdapter, rule, err = resolveMetadata(metadata)
if err != nil {
return conn, nil return nil, err
} else { }
if proxyAdapter == nil { } else {
return dialer.DialContext(ctx, network, addr, opts...) var ok bool
} proxyAdapter, ok = Proxies()[proxyName]
if !ok {
if !proxyAdapter.SupportUDP() { opts = append(opts, dialer.WithInterface(proxyName))
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter)
}
packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
if err != nil {
logMetadataErr(metadata, rule, proxyAdapter, err)
return nil, err
}
logMetadata(metadata, rule, packetConn)
packetConn = statistic.NewUDPTracker(packetConn, statistic.DefaultManager, metadata, rule, 0, 0, false)
return N.NewBindPacketConn(packetConn, metadata.UDPAddr()), nil
} }
} }
} }
if metadata.NetWork == C.TCP {
if proxyAdapter == nil {
opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...)
}
if proxyAdapter.IsL3Protocol(metadata) { // L3 proxy should resolve domain before to avoid loopback
if !metadata.Resolved() {
dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, err
}
metadata.DstIP = dstIP
}
metadata.Host = "" // clear host to avoid double resolve in proxy
}
conn, err := proxyAdapter.DialContext(ctx, metadata, opts...)
if err != nil {
logMetadataErr(metadata, rule, proxyAdapter, err)
return nil, err
}
logMetadata(metadata, rule, conn)
conn = statistic.NewTCPTracker(conn, statistic.DefaultManager, metadata, rule, 0, 0, false)
return conn, nil
} else {
if proxyAdapter == nil {
return dialer.DialContext(ctx, network, metadata.AddrPort().String(), opts...)
}
if !proxyAdapter.SupportUDP() {
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter)
}
packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
if err != nil {
logMetadataErr(metadata, rule, proxyAdapter, err)
return nil, err
}
logMetadata(metadata, rule, packetConn)
packetConn = statistic.NewUDPTracker(packetConn, statistic.DefaultManager, metadata, rule, 0, 0, false)
return N.NewBindPacketConn(packetConn, metadata.UDPAddr()), nil
}
} }
func DnsListenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName string, network string, addr string, r resolver.Resolver, opts ...dialer.Option) (net.PacketConn, error) { func (d *DNSDialer) ListenPacket(ctx context.Context, network, addr string) (net.PacketConn, error) {
r := d.r
proxyAdapter := d.proxyAdapter
proxyName := d.proxyName
opts := d.opts
metadata := &C.Metadata{ metadata := &C.Metadata{
NetWork: C.UDP, NetWork: C.UDP,
Type: C.INNER, Type: C.INNER,
@ -156,7 +166,7 @@ func DnsListenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName
} }
if proxyAdapter == nil { if proxyAdapter == nil {
return dialer.NewDialer(opts...).ListenPacket(ctx, network, "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort)) return dialer.NewDialer(opts...).ListenPacket(ctx, network, "", metadata.AddrPort())
} }
if !proxyAdapter.SupportUDP() { if !proxyAdapter.SupportUDP() {