grpc/grpc-go · warning

dns: error parsing A record IP address

Error message

dns: error parsing A record IP address %v: %v

What it means

During SRV-record based gRPC load balancer discovery, lookupSRV at dns_resolver.go:255-273 resolves each SRV target's hostname via LookupHost, then parses each returned address as an IP with formatIP. If netip.ParseAddr fails (the resolver returned something that is not an IP), the resolver returns this error, aborting the SRV lookup for that record.

Solutions

  1. Run a direct DNS query (dig SRV _grpclb._tcp.<host> and dig A <srv-target>) to see what the resolver returns.
  2. If the DNS server is returning invalid data, fix the records on the authoritative server.
  3. If SRV lookups are not needed, leave EnableSRVLookups at its default (false) to skip the SRV path entirely.
  4. If using a custom net.Resolver via WithContextDialerOption/resolver option, ensure it returns only IP literals.

Example fix

// before: SRV lookups enabled but DNS returns bad A records
//   grpc.EnableSRVLookups = true

// after: disable SRV (default) if not using grpclb
//   // leave EnableSRVLookups = false (default)
Defensive patterns

Strategy: validation

Validate before calling

package main

import (
	"fmt"
	"net/netip"
)

// If you supply a custom resolver result for SRV targets, ensure every address
// is a valid IP literal.
func allIPLiterals(addrs []string) error {
	for _, a := range addrs {
		if _, err := netip.ParseAddr(a); err != nil {
			return fmt.Errorf("non-IP in A result %q: %w", a, err)
		}
	}
	return nil
}

// func main() { _ = allIPLiterals }

Try / catch

// The DNS resolver logs this at warning and returns it from the lookup; gRPC
// will back off. Surface it through your resolver-error handler.
//
//   // Use grpclog.Component or grpc.WithReturnConnectionError to observe.
//   // Treat SRV/A parse failures as transient unless persistent.

Prevention

When it happens

Trigger: Triggered when EnableSRVLookups is true, an SRV record is found for the host, and one of the addresses returned by LookupHost(s.Target) is not parseable by netip.ParseAddr. This indicates the underlying system resolver returned a non-IP string (e.g. a CNAME, a malformed record).

Common situations: A misbehaving DNS server returns a hostname or garbage where an A/AAAA record is expected, the SRV target resolves to a CNAME chain that leaks through as a name, or a custom net.Resolver returns non-standard results.

Related errors


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

Appendix: source

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

	if err != nil {
		err = handleDNSError(err, "SRV") // may become nil
		return nil, err
	}
	for _, s := range srvs {
		lbAddrs, err := d.resolver.LookupHost(ctx, s.Target)
		if err != nil {
			err = handleDNSError(err, "A") // may become nil
			if err == nil {
				// If there are other SRV records, look them up and ignore this
				// one that does not exist.
				continue
			}
			return nil, err
		}
		for _, a := range lbAddrs {
			ip, err := formatIP(a)
			if err != nil {
				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)

View on GitHub (pinned to 0c51461d27)