grpc/grpc-go · error

dns resolver: missing address

Error message

dns resolver: missing address

What it means

ErrMissingAddr (internal/resolver/dns/internal/internal.go:41) is the sentinel returned by the dns resolver builder when the target name passed in is empty. The dns resolver needs a host name to resolve; with an empty endpoint there is nothing to look up, so the resolver build fails immediately. Exported as a package var so callers can errors.Is-match it.

Source

Thrown at internal/resolver/dns/internal/internal.go:41

	"context"
	"errors"
	"net"
	"time"
)

// NetResolver groups the methods on net.Resolver that are used by the DNS
// resolver implementation. This allows the default net.Resolver instance to be
// overridden from tests.
type NetResolver interface {
	LookupHost(ctx context.Context, host string) (addrs []string, err error)
	LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*net.SRV, err error)
	LookupTXT(ctx context.Context, name string) (txts []string, err error)
}

var (
	// ErrMissingAddr is the error returned when building a DNS resolver when
	// the provided target name is empty.
	ErrMissingAddr = errors.New("dns resolver: missing address")

	// ErrEndsWithColon is the error returned when building a DNS resolver when
	// the provided target name ends with a colon that is supposed to be the
	// separator between host and port.  E.g. "::" is a valid address as it is
	// an IPv6 address (host only) and "[::]:" is invalid as it ends with a
	// colon as the host and port separator
	ErrEndsWithColon = errors.New("dns resolver: missing port after port-separator colon")
)

// The following vars are overridden from tests.
var (
	// TimeAfterFunc is used by the DNS resolver to wait for the given duration
	// to elapse. In non-test code, this is implemented by time.After. In test
	// code, this can be used to control the amount of time the resolver is
	// blocked waiting for the duration to elapse.
	TimeAfterFunc func(time.Duration) <-chan time.Time

	// TimeNowFunc is used by the DNS resolver to get the current time.

View on GitHub (pinned to 03255a9237)

Solutions

  1. Provide a non-empty host in the dns:// target, e.g. grpc.Dial("dns:///service.example.com:443", ...).
  2. Validate the resolved host string is non-empty before constructing the target.
  3. Check for this specific error with errors.Is(err, internal.ErrMissingAddr) (or the resolver's typed error) to give a clearer startup message.

Example fix

// before
host := os.Getenv("BACKEND_HOST") // ""
conn, _ := grpc.Dial(fmt.Sprintf("dns:///%s:443", host), ...) // err: missing address

// after
host := os.Getenv("BACKEND_HOST")
if host == "" { log.Fatal("BACKEND_HOST not set") }
conn, _ := grpc.Dial(fmt.Sprintf("dns:///%s:443", host), ...)
Defensive patterns

Strategy: validation

Validate before calling

host := strings.TrimSpace(targetHost)
if host == "" {
    return errors.New("dns target host is empty")
}
dialTarget := fmt.Sprintf("dns:///%s", host)

Try / catch

conn, err := grpc.Dial(target, ...)
if err != nil {
    if errors.Is(err, internal.ErrMissingAddr) {
        log.Fatal("dial target is empty; set the backend host")
    }
    log.Fatal(err)
}

Prevention

When it happens

Trigger: Dialing with a dns:// target whose authority/endpoint is empty, e.g. grpc.Dial("dns:///") or a programmatically-built target where Endpoint() returns "". The resolver Build() detects the empty endpoint and returns ErrMissingAddr, causing Dial to fail.

Common situations: Building a target string via fmt.Sprintf with an empty host var; dropping the host part while keeping the dns:// scheme; env var holding the backend host being unset so the dial target becomes dns:///:443; mis-parsed service discovery URL.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/8814e66c484bac82. Report an issue: GitHub.