{"record":{"id":"77a46595a457e6c6","repo":"grpc/grpc-go","slug":"dns-resolver-missing-port-after-port-separator-co","errorCode":null,"errorMessage":"dns resolver: missing port after port-separator colon","messagePattern":"dns resolver: missing port after port-separator colon","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/resolver/dns/internal/internal.go","lineNumber":48,"sourceCode":"// resolver implementation. This allows the default net.Resolver instance to be\n// overridden from tests.\ntype NetResolver interface {\n\tLookupHost(ctx context.Context, host string) (addrs []string, err error)\n\tLookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*net.SRV, err error)\n\tLookupTXT(ctx context.Context, name string) (txts []string, err error)\n}\n\nvar (\n\t// ErrMissingAddr is the error returned when building a DNS resolver when\n\t// the provided target name is empty.\n\tErrMissingAddr = errors.New(\"dns resolver: missing address\")\n\n\t// ErrEndsWithColon is the error returned when building a DNS resolver when\n\t// the provided target name ends with a colon that is supposed to be the\n\t// separator between host and port.  E.g. \"::\" is a valid address as it is\n\t// an IPv6 address (host only) and \"[::]:\" is invalid as it ends with a\n\t// colon as the host and port separator\n\tErrEndsWithColon = errors.New(\"dns resolver: missing port after port-separator colon\")\n)\n\n// The following vars are overridden from tests.\nvar (\n\t// TimeAfterFunc is used by the DNS resolver to wait for the given duration\n\t// to elapse. In non-test code, this is implemented by time.After. In test\n\t// code, this can be used to control the amount of time the resolver is\n\t// blocked waiting for the duration to elapse.\n\tTimeAfterFunc func(time.Duration) <-chan time.Time\n\n\t// TimeNowFunc is used by the DNS resolver to get the current time.\n\t// In non-test code, this is implemented by time.Now. In test code,\n\t// this can be used to control the current time for the resolver.\n\tTimeNowFunc func() time.Time\n\n\t// TimeUntilFunc is used by the DNS resolver to calculate the remaining\n\t// wait time for re-resolution. In non-test code, this is implemented by\n\t// time.Until. In test code, this can be used to control the remaining","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/resolver/dns/internal/internal.go#L30-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Append an explicit port: \"dns:///host:443\".","Guard the port value: only append \":\"+port when port is non-empty.","For IPv6, use bracketed form with a real port: \"dns:///[::1]:443\", or pass host-only without a trailing colon."],"exampleFix":"// before\nport := os.Getenv(\"BACKEND_PORT\") // \"\"\nconn, _ := grpc.Dial(fmt.Sprintf(\"dns:///host:%s\", port), ...) // \"dns:///host:\" -> err\n\n// after\nport := os.Getenv(\"BACKEND_PORT\")\nif port == \"\" { port = \"443\" }\nconn, _ := grpc.Dial(fmt.Sprintf(\"dns:///host:%s\", port), ...)","handlingStrategy":"validation","validationCode":"host, port := targetHost, targetPort\nif strings.HasSuffix(host, \":\") {\n    return errors.New(\"target host ends with ':' but no port\")\n}\nif port == \"\" {\n    return errors.New(\"target port is empty\")\n}","typeGuard":null,"tryCatchPattern":"conn, err := grpc.Dial(target, ...)\nif err != nil {\n    if errors.Is(err, internal.ErrEndsWithColon) {\n        log.Fatal(\"dns target missing port after ':'\")\n    }\n    log.Fatal(err)\n}","preventionTips":["Build host:port only when port is non-empty; default the port otherwise.","For IPv6, use bracketed form [::1]:443 with an explicit port."],"tags":["go","grpc","resolver","dns","naming","config-validation"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}