Tencent/WeKnora · error

DNS resolution failed for %s: %w

Error message

DNS resolution failed for %s: %w

What it means

SSRFSafeDialContext resolves the hostname itself (once, via net.DefaultResolver.LookupIPAddr) so it can validate every answer before connecting, and this error means that lookup failed. The library resolves explicitly to close the DNS-rebinding window between validation and connection; a resolver failure aborts the dial. The wrapped error (%w) carries the underlying DNS failure (NXDOMAIN, timeout, no such host, resolver unreachable).

Source

Thrown at internal/utils/security.go:832

	hostLower := strings.ToLower(host)
	for _, restricted := range restrictedHostnames {
		if hostLower == restricted {
			return nil, fmt.Errorf("connection blocked: hostname %s is restricted", host)
		}
	}
	for _, suffix := range restrictedHostSuffixes {
		if strings.HasSuffix(hostLower, suffix) {
			return nil, fmt.Errorf("connection blocked: hostname suffix %s is restricted", suffix)
		}
	}

	// Resolve the hostname once, validate every answer, and then dial one of
	// those exact IPs. Dialing the original hostname here would make the
	// standard dialer resolve it a second time, leaving a DNS-rebinding window
	// between validation and connection establishment.
	ips, err := net.DefaultResolver.LookupIPAddr(ctx, host)
	if err != nil {
		return nil, fmt.Errorf("DNS resolution failed for %s: %w", host, err)
	}
	if len(ips) == 0 {
		return nil, fmt.Errorf("DNS resolution returned no addresses for %s", host)
	}

	// Validate all resolved IPs
	for _, ipAddr := range ips {
		if restricted, reason := isRestrictedIP(ipAddr.IP); restricted {
			return nil, fmt.Errorf("connection blocked: %s resolves to restricted IP %s (%s)", host, ipAddr.IP.String(), reason)
		}
	}

	// If we get here, all IPs are safe. Pin the connection to the validated DNS
	// answers; TLS still uses the request hostname for SNI/certificate checks.
	dialer := &net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
	}

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Verify the hostname resolves from the same environment: run a lookup (nslookup/getent hosts or net.LookupIP in Go) and fix the name or DNS records if it fails there too.
  2. Fix resolver configuration (/etc/resolv.conf, VPC DNS settings, CoreDNS) so the process can reach a working nameserver.
  3. Retry with backoff if the wrapped error indicates a transient resolver timeout.
  4. If the host is IP-only anyway, dial the literal IP (still subject to isRestrictedIP checks) to skip DNS entirely.

Example fix

// before
conn, err := utils.SSRFSafeDialContext(ctx, "tcp", "api.exmaple.com:443") // typo, NXDOMAIN

// after
if _, err := net.LookupIP("api.example.com"); err != nil { return fmt.Errorf("resolve check failed: %w", err) }
conn, err := utils.SSRFSafeDialContext(ctx, "tcp", "api.example.com:443")
Defensive patterns

Strategy: retry

Validate before calling

ips, err := net.LookupIP(host)
if err != nil {
    return fmt.Errorf("pre-flight DNS check failed for %s: %w", host, err)
}

Try / catch

conn, err := utils.SSRFSafeDialContext(ctx, "tcp", addr)
if err != nil && strings.Contains(err.Error(), "DNS resolution failed") {
    var dnsErr *net.DNSError
    if errors.As(err, &dnsErr) && dnsErr.IsTimeout {
        // transient: retry with backoff
    }
    return nil, fmt.Errorf("check DNS/resolver configuration for %s: %w", host, err)
}

Prevention

When it happens

Trigger: Dialing a hostname that does not exist in DNS, with an unreachable/misconfigured resolver, under a network policy that blocks UDP/TCP 53, or when the resolver times out before LookupIPAddr returns — reached via SSRFSafeDialContext, SSRFSafeGRPCDialer, or an http.Transport using it as DialContext.

Common situations: Typo in the hostname in config; running in a container/air-gapped network without working DNS; DNS outage or flaky resolvers; corporate networks that require an internal resolver not visible to the process.

Understand the failure class

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/49f88c1f530cc1b5. Report an issue: GitHub.