diff --git a/internal/net/dialer/dialer.go b/internal/net/dialer/dialer.go index 7008892b..1bc17679 100644 --- a/internal/net/dialer/dialer.go +++ b/internal/net/dialer/dialer.go @@ -146,6 +146,7 @@ func (d *Dialer) dialOnce(ctx context.Context, network, addr, ifceName string, i return nil, fmt.Errorf("dial: unsupported network %s", network) } netd := net.Dialer{ + Resolver: &net.Resolver{PreferGo: true}, LocalAddr: ifAddr, Control: func(network, address string, c syscall.RawConn) error { return c.Control(func(fd uintptr) { diff --git a/internal/net/resovle.go b/internal/net/resolve.go similarity index 61% rename from internal/net/resovle.go rename to internal/net/resolve.go index 8e0bb460..153ea5a0 100644 --- a/internal/net/resovle.go +++ b/internal/net/resolve.go @@ -13,8 +13,10 @@ import ( // Resolve resolves addr to a concrete IP address. If hosts is non-nil, it is // consulted first. If r is non-nil and no host mapping matched, the resolver -// is used. If neither is available, Go's native pure-Go resolver is used as a -// fallback to avoid cgo-based DNS lookups that would block OS threads. +// is used. If neither is available, the address is returned unchanged so that +// the original hostname is preserved for downstream connectors (e.g., SOCKS5 +// ATYP=domain). DNS resolution is deferred to the final TCP dialer, which uses +// a pure-Go resolver to avoid cgo thread exhaustion. func Resolve(ctx context.Context, network, addr string, r resolver.Resolver, hosts hosts.HostMapper, log logger.Logger) (string, error) { if addr == "" { return addr, nil @@ -53,22 +55,9 @@ func Resolve(ctx context.Context, network, addr string, r resolver.Resolver, hos return net.JoinHostPort(ips[0].String(), port), nil } - // A literal IP address needs no resolution. - if net.ParseIP(host) != nil { - return addr, nil - } - - // Fallback to Go's native (pure-Go) resolver when no custom resolver or - // host mapper is configured. This avoids cgo-based DNS lookups in - // downstream net.Dialer.DialContext which would block OS threads and - // cause thread exhaustion under high connection concurrency. - lr := &net.Resolver{PreferGo: true} - ips, err := lr.LookupIP(ctx, "ip", host) - if err != nil { - return "", err - } - if len(ips) == 0 { - return "", fmt.Errorf("resolver: domain %s does not exist", host) - } - return net.JoinHostPort(ips[0].String(), port), nil + // Return the address unchanged so that the original hostname is + // preserved through the proxy chain. DNS resolution happens in the + // final TCP dialer (dialer.go) which uses net.Resolver{PreferGo: true} + // to avoid cgo-based lookups that block OS threads. + return addr, nil } diff --git a/internal/net/resovle_test.go b/internal/net/resolve_test.go similarity index 87% rename from internal/net/resovle_test.go rename to internal/net/resolve_test.go index adb25210..ca4599be 100644 --- a/internal/net/resovle_test.go +++ b/internal/net/resolve_test.go @@ -115,11 +115,10 @@ func TestResolve_ResolverEmptyResult(t *testing.T) { } func TestResolve_NoResolverNoHosts(t *testing.T) { - // When no resolver and no host mapper are configured, the function now - // resolves via Go's native DNS resolver (PreferGo: true). This test - // uses an IP address which is short-circuited at the top of Resolve, - // avoiding the need for actual DNS resolution in unit tests. - // DNS fallback behavior is verified by the e2e tests. + // When no resolver and no host mapper are configured, the address is + // returned unchanged so that the original hostname is preserved + // through the proxy chain (e.g., SOCKS5 ATYP=domain). + // DNS resolution is deferred to the final TCP dialer. ctx := context.Background() addr, err := Resolve(ctx, "tcp", "1.2.3.4:8080", nil, nil, nil) if err != nil { @@ -128,6 +127,13 @@ func TestResolve_NoResolverNoHosts(t *testing.T) { if addr != "1.2.3.4:8080" { t.Errorf("expected 1.2.3.4:8080, got %s", addr) } + addr, err = Resolve(ctx, "tcp", "example.com:8080", nil, nil, nil) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if addr != "example.com:8080" { + t.Errorf("expected example.com:8080, got %s", addr) + } } func TestResolve_HostsNoMatchUsesResolver(t *testing.T) {