grpc/grpc-go · error

dns resolver: missing port after port-separator colon

Error message

dns resolver: missing port after port-separator colon

What it means

ErrEndsWithColon (internal/resolver/dns/internal/internal.go:48) is returned by the dns resolver builder when the target name ends with a colon that is meant as the host:port separator but has no port after it. The comment (lines 43-47) clarifies '::' is a valid IPv6 host-only address, whereas '[::]:' is invalid because the trailing colon implies a port that is absent.

Source

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

// 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.
	// In non-test code, this is implemented by time.Now. In test code,
	// this can be used to control the current time for the resolver.
	TimeNowFunc func() time.Time

	// TimeUntilFunc is used by the DNS resolver to calculate the remaining
	// wait time for re-resolution. In non-test code, this is implemented by
	// time.Until. In test code, this can be used to control the remaining

View on GitHub (pinned to 03255a9237)

Solutions

  1. Append an explicit port: "dns:///host:443".
  2. Guard the port value: only append ":"+port when port is non-empty.
  3. For IPv6, use bracketed form with a real port: "dns:///[::1]:443", or pass host-only without a trailing colon.

Example fix

// before
port := os.Getenv("BACKEND_PORT") // ""
conn, _ := grpc.Dial(fmt.Sprintf("dns:///host:%s", port), ...) // "dns:///host:" -> err

// after
port := os.Getenv("BACKEND_PORT")
if port == "" { port = "443" }
conn, _ := grpc.Dial(fmt.Sprintf("dns:///host:%s", port), ...)
Defensive patterns

Strategy: validation

Validate before calling

host, port := targetHost, targetPort
if strings.HasSuffix(host, ":") {
    return errors.New("target host ends with ':' but no port")
}
if port == "" {
    return errors.New("target port is empty")
}

Try / catch

conn, err := grpc.Dial(target, ...)
if err != nil {
    if errors.Is(err, internal.ErrEndsWithColon) {
        log.Fatal("dns target missing port after ':'")
    }
    log.Fatal(err)
}

Prevention

When it happens

Trigger: Dialing a dns:// target whose endpoint ends in ':' with no port, e.g. "dns:///host.example.com:" or "dns:///[::]:". The resolver interprets the trailing colon as the host/port delimiter and rejects the empty port.

Common situations: Building the target by concatenating host + ':' + port where the port variable is empty; stripping a port and leaving the colon; IPv6 address formatting mistakes; templating that emits host: when port is unset.

Related errors


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