grpc/grpc-go · warning

dns: record lookup error

Error message

dns: %v record lookup error: %v

What it means

handleDNSError (dns_resolver.go:278-291) inspects errors from LookupSRV/LookupTXT/LookupHost. Transient or timeout DNS errors are wrapped with this message and logged at Info level so gRPC can back off and retry; non-timeout, non-temporary failures (e.g. NXDOMAIN) are suppressed (returned as nil). The %v fields are the lookup type (SRV/TXT/A) and the underlying net.DNSError.

Solutions

  1. Verify the DNS server configured in /etc/resolv.conf (or container dnsPolicy) is reachable: nslookup <host> <server>.
  2. Increase the gRPC DNS resolution timeout (ResolvingTimeout) if the resolver is legitimately slow.
  3. Add retries / circuit-breaking at the caller; gRPC itself will back off, but upstream code should tolerate transient resolution failures.
  4. If running in Kubernetes, confirm dnsPolicy (ClusterFirst) and that the cluster-dns Service is healthy.

Example fix

// before: default resolution timeout too short for slow DNS

// after:
//   import (
//       dns "google.golang.org/grpc/internal/resolver/dns"
//   )
//   // (configure via available options / environment as supported by your gRPC version)
//   // and ensure resolv.conf points to a reachable nameserver
Defensive patterns

Strategy: retry

Validate before calling

package main

import (
	"context"
	"net"
	"time"
)

// Probe DNS reachability at startup so transient resolver problems surface
// before traffic flows.
func probeDNS(ctx context.Context, host string) error {
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()
	var r net.Resolver
	_, err := r.LookupHost(ctx, host)
	return err
}

// func main() { _ = probeDNS }

Try / catch

// Transient DNS errors cause gRPC connection TRANSIENT_FAILURE with backoff;
// it will retry automatically. Surface it via connection-state watching.
//
//   for {
//       conn.WaitForStateChange(ctx, conn.GetState())
//       if conn.GetState() == connectivity.Ready { break }
//   }

Prevention

When it happens

Trigger: Triggered when a DNS lookup returns a *net.DNSError whose IsTimeout or IsTemporary is true, or when a non-DNSError is returned. The lookup type label ('A', 'SRV', 'TXT') indicates which resolution failed.

Common situations: DNS server unreachable, packet loss to the resolver, very slow DNS causing timeouts, a temporary network partition, /etc/resolv.conf pointing at an unreachable nameserver, or container DNS misconfiguration.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/6d0f00b5583a8c1d. Report an issue: GitHub.

Appendix: source

Thrown at internal/resolver/dns/dns_resolver.go:287

				return nil, fmt.Errorf("dns: error parsing A record IP address %v: %v", a, err)
			}
			addr := ip + ":" + strconv.Itoa(int(s.Port))
			newAddrs = append(newAddrs, resolver.Address{Addr: addr, ServerName: s.Target})
		}
	}
	return newAddrs, nil
}

func handleDNSError(err error, lookupType string) error {
	dnsErr, ok := err.(*net.DNSError)
	if ok && !dnsErr.IsTimeout && !dnsErr.IsTemporary {
		// Timeouts and temporary errors should be communicated to gRPC to
		// attempt another DNS query (with backoff).  Other errors should be
		// suppressed (they may represent the absence of a TXT record).
		return nil
	}
	if err != nil {
		err = fmt.Errorf("dns: %v record lookup error: %v", lookupType, err)
		logger.Info(err)
	}
	return err
}

func (d *dnsResolver) lookupTXT(ctx context.Context) *serviceconfig.ParseResult {
	ss, err := d.resolver.LookupTXT(ctx, txtPrefix+d.host)
	if err != nil {
		if envconfig.TXTErrIgnore {
			return nil
		}
		if err = handleDNSError(err, "TXT"); err != nil {
			return &serviceconfig.ParseResult{Err: err}
		}
		return nil
	}
	var res string
	for _, s := range ss {

View on GitHub (pinned to 0c51461d27)