fix(handler/socks5): resolve UDP domains via configured resolver to stop DNS leak
SOCKS5 UDP datagrams carrying ATYP=DOMAINNAME were force-resolved via net.ResolveUDPAddr in udpConn.ReadFrom, which always used the system resolver and ignored any configured handler resolver (resolver=1.1.1.1), leaking the query locally. The premature domain→IP conversion also stripped the hostname before it reached the relay chain, so the exit node could not resolve DNS itself. udpConn.ReadFrom now returns a domainAddr for domain targets, letting the name flow untouched through udp.Relay into the upstream WriteTo. Chain-backed PacketConns (udpTunConn, udpRelayConn) encode it as ATYP=Domain and forward to the exit. Direct (no-chain) associations yield a raw *net.UDPConn that cannot consume a domainAddr, so they are wrapped with resolvePacketConn, which resolves via hostMapper → configured resolver → system DNS.
This commit is contained in:
@@ -93,6 +93,21 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, network st
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A direct (no-chain) UDP association yields a raw *net.UDPConn, which
|
||||||
|
// cannot consume the domainAddr returned by udpConn.ReadFrom for
|
||||||
|
// ATYP=DOMAINNAME datagrams. Wrap it so domains are resolved through the
|
||||||
|
// configured resolver (hostMapper → resolver → system DNS) instead of the
|
||||||
|
// previous hardcoded net.ResolveUDPAddr in the SOCKS5 decode path.
|
||||||
|
// Chain-backed PacketConns (udpTunConn, udpRelayConn, ...) encode domains
|
||||||
|
// as ATYP=Domain themselves and are left untouched.
|
||||||
|
if _, isDirect := pc.(*net.UDPConn); isDirect {
|
||||||
|
pc = &resolvePacketConn{
|
||||||
|
PacketConn: pc,
|
||||||
|
resolver: h.options.Router.Options().Resolver,
|
||||||
|
hostMapper: h.options.Router.Options().HostMapper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
saddr := gosocks5.Addr{}
|
saddr := gosocks5.Addr{}
|
||||||
saddr.ParseFrom(cc.LocalAddr().String())
|
saddr.ParseFrom(cc.LocalAddr().String())
|
||||||
|
|
||||||
@@ -203,6 +218,55 @@ func (f *filteredPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolvePacketConn wraps the upstream net.PacketConn for a direct (no-chain)
|
||||||
|
// UDP association and resolves domain addresses to IPs in WriteTo calls.
|
||||||
|
//
|
||||||
|
// udpConn.ReadFrom now returns a domainAddr for ATYP=DOMAINNAME datagrams so
|
||||||
|
// the domain survives to the chain; a raw *net.UDPConn cannot consume a
|
||||||
|
// domainAddr (WriteTo needs *net.UDPAddr), so direct connections are wrapped
|
||||||
|
// here. Resolution order is hostMapper → configured resolver → system DNS, so a
|
||||||
|
// configured resolver (e.g. resolver=1.1.1.1) is honored instead of leaking
|
||||||
|
// through the system resolver.
|
||||||
|
type resolvePacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
resolver resolver.Resolver
|
||||||
|
hostMapper hosts.HostMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *resolvePacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
|
host, portStr, err := net.SplitHostPort(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
return c.PacketConn.WriteTo(b, addr)
|
||||||
|
}
|
||||||
|
if net.ParseIP(host) != nil {
|
||||||
|
return c.PacketConn.WriteTo(b, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ips []net.IP
|
||||||
|
if c.hostMapper != nil {
|
||||||
|
ips, _ = c.hostMapper.Lookup(context.Background(), "ip", host)
|
||||||
|
}
|
||||||
|
if len(ips) == 0 && c.resolver != nil {
|
||||||
|
ips, _ = c.resolver.Resolve(context.Background(), "ip", host)
|
||||||
|
}
|
||||||
|
if len(ips) == 0 {
|
||||||
|
ips, _ = net.LookupIP(host)
|
||||||
|
}
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return 0, fmt.Errorf("socks5 udp: cannot resolve %s", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := ips[0]
|
||||||
|
for _, candidate := range ips {
|
||||||
|
if candidate.To4() != nil {
|
||||||
|
ip = candidate
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
port, _ := strconv.Atoi(portStr)
|
||||||
|
return c.PacketConn.WriteTo(b, &net.UDPAddr{IP: ip, Port: port})
|
||||||
|
}
|
||||||
|
|
||||||
// domainResolvePacketConn wraps a net.PacketConn and resolves domain
|
// domainResolvePacketConn wraps a net.PacketConn and resolves domain
|
||||||
// addresses to IPs in WriteTo calls. This guarantees that SOCKS5 UDP
|
// addresses to IPs in WriteTo calls. This guarantees that SOCKS5 UDP
|
||||||
// response datagrams never carry ATYP=Domain (0x03), which some clients
|
// response datagrams never carry ATYP=Domain (0x03), which some clients
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package socks
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/go-gost/core/common/bufpool"
|
"github.com/go-gost/core/common/bufpool"
|
||||||
"github.com/go-gost/gosocks5"
|
"github.com/go-gost/gosocks5"
|
||||||
@@ -106,6 +107,20 @@ func UDPConn(c net.PacketConn, bufferSize int) net.PacketConn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// domainAddr is a net.Addr that preserves a domain name without resolving it.
|
||||||
|
// Returned by udpConn.ReadFrom when a SOCKS5 UDP datagram carries
|
||||||
|
// ATYP=DOMAINNAME, so that downstream consumers (e.g. relay-chain WriteTo) can
|
||||||
|
// forward the domain verbatim instead of forcing a local DNS lookup that would
|
||||||
|
// leak the query through the system resolver and bypass any configured resolver.
|
||||||
|
type domainAddr struct {
|
||||||
|
network string
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *domainAddr) Network() string { return a.network }
|
||||||
|
func (a *domainAddr) String() string { return net.JoinHostPort(a.host, strconv.Itoa(a.port)) }
|
||||||
|
|
||||||
// ReadFrom reads an UDP datagram.
|
// ReadFrom reads an UDP datagram.
|
||||||
// NOTE: for server side,
|
// NOTE: for server side,
|
||||||
// the returned addr is the target address the client want to relay to.
|
// the returned addr is the target address the client want to relay to.
|
||||||
@@ -128,7 +143,11 @@ func (c *udpConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
|||||||
}
|
}
|
||||||
n = copy(b, buf[hlen:n])
|
n = copy(b, buf[hlen:n])
|
||||||
|
|
||||||
|
if net.ParseIP(socksAddr.Host) != nil {
|
||||||
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
|
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
|
||||||
|
} else {
|
||||||
|
addr = &domainAddr{network: "udp", host: socksAddr.Host, port: int(socksAddr.Port)}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user