fix(net): defer DNS resolution to final dialer to preserve domain in proxy chain
Commit 0522ae8 added inline DNS resolution (PreferGo: true) in Resolve(),
resolving domain names to IPs before routing. This caused the SOCKS5
connector to send CONNECT with ATYP=IPv4 instead of ATYP=domain, breaking
chains where the backend (e.g., ssh -D) cannot reach the gost-resolved IP.
Revert the DNS fallback in Resolve() — return the address unchanged so
the original hostname flows through the proxy chain. Instead, add
net.Resolver{PreferGo: true} to the TCP dialer in internal/net/dialer,
which resolves DNS at the point of actual TCP connection — still pure
Go (no cgo thread exhaustion) but after the proxy chain routing is done.
Also rename resovle.go → resolve.go (fix typo).
Fixes go-gost/gost#877
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -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) {
|
||||
Reference in New Issue
Block a user