grpc/grpc-go · error

invalid target address

Error message

invalid target address %v, error info: %v

What it means

The dns resolver's parseTarget (dns_resolver.go:390-417) tries several strategies: empty target -> ErrMissingAddr; pure IP -> returned as-is; SplitHostPort success -> host/port; otherwise it appends the default port and retries SplitHostPort. If even that final attempt fails, the target is unparseable and this error is returned. This is the dns scheme's own parser, distinct from the delegating resolver's parseTarget.

Solutions

  1. Use a well-formed target: "dns:///host:443", "dns:///host", "dns:///[::1]:443", or a plain hostname.
  2. For IPv6, wrap the address in brackets when a port is present: "[2001:db8::1]:443".
  3. Log the exact target string passed to grpc.Dial and validate it before dialing.

Example fix

// before:
//   conn, err := grpc.Dial("dns:///::1:443", opts) // malformed IPv6

// after:
//   conn, err := grpc.Dial("dns:///[::1]:443", opts)
Defensive patterns

Strategy: validation

Validate before calling

package main

import (
	"fmt"
	"net"
	"net/netip"
	"strings"
)

// validateDialTarget mirrors the dns resolver's parseTarget rules so the error
// is caught before grpc.Dial.
func validateDnsTarget(target, defaultPort string) (string, string, error) {
	if target == "" {
		return "", "", fmt.Errorf("missing target")
	}
	if _, err := netip.ParseAddr(target); err == nil {
		return target, defaultPort, nil
	}
	if h, p, err := net.SplitHostPort(target); err == nil {
		if p == "" {
			return "", "", fmt.Errorf("target ends with colon, missing port")
		}
		if h == "" {
			h = "localhost"
		}
		return h, p, nil
	}
	if h, p, err := net.SplitHostPort(target + ":" + defaultPort); err == nil {
		return h, p, nil
	}
	return "", "", fmt.Errorf("invalid target %q", target)
}

var _ = strings.TrimSpace

// func main() { _, _, _ = validateDnsTarget("[::1]:443", "443") }

Try / catch

// grpc.Dial returns this synchronously from the dns resolver's Build.
//
//   conn, err := grpc.Dial("dns:///"+target, opts)
//   if err != nil && strings.Contains(err.Error(), "invalid target address") {
//       return fmt.Errorf("fix dial target: %w", err)
//   }

Prevention

When it happens

Trigger: Triggered when the dns resolver's parseTarget cannot make sense of the target string by any strategy, e.g. a target with unbalanced brackets, embedded illegal characters, or a malformed IPv6 literal.

Common situations: A dial target with a malformed IPv6 address (missing/extra brackets), stray characters, or a target that was incorrectly assembled by configuration code.

Related errors


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

Appendix: source

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

	if host, port, err = net.SplitHostPort(target); err == nil {
		if port == "" {
			// If the port field is empty (target ends with colon), e.g. "[::1]:",
			// this is an error.
			return "", "", internal.ErrEndsWithColon
		}
		// target has port, i.e ipv4-host:port, [ipv6-host]:port, host-name:port
		if host == "" {
			// Keep consistent with net.Dial(): If the host is empty, as in ":80",
			// the local system is assumed.
			host = "localhost"
		}
		return host, port, nil
	}
	if host, port, err = net.SplitHostPort(target + ":" + defaultPort); err == nil {
		// target doesn't have port
		return host, port, nil
	}
	return "", "", fmt.Errorf("invalid target address %v, error info: %v", target, err)
}

type rawChoice struct {
	ClientLanguage *[]string        `json:"clientLanguage,omitempty"`
	Percentage     *int             `json:"percentage,omitempty"`
	ClientHostName *[]string        `json:"clientHostName,omitempty"`
	ServiceConfig  *json.RawMessage `json:"serviceConfig,omitempty"`
}

func containsString(a *[]string, b string) bool {
	if a == nil {
		return true
	}
	for _, c := range *a {
		if c == b {
			return true
		}
	}

View on GitHub (pinned to 0c51461d27)