{"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/0c51461d27177d997e14c642fe18c11668fc09a3/internal/resolver/dns/internal/internal.go#L30-L66","documentation":"ErrEndsWithColon is returned by the DNS resolver builder when the target string ends with a colon, implying a host:port separator was started but no port follows. For example 'host:' has a host but no port. Note that '::' (bare IPv6) is valid as a host-only address, but 'host:' or '[::1]:' are not.","triggerScenarios":"Dialing with a target like 'dns:///my-service:' (trailing colon, no port), or an address string that ends with ':' after the resolver parses host and port. The resolver detects the trailing colon as an incomplete host:port pair.","commonSituations":"String concatenation bug that appends ':' but not a port (e.g., fmt.Sprintf(\"%s:\", host)); config templating that adds a colon delimiter but leaves port empty; user input that omits the port after the colon.","solutions":["Include a port after the colon: 'dns:///my-service:8080'.","If no port is intended, omit the colon entirely (the resolver applies a default port if needed).","Validate the target does not end with ':' before dialing.","Fix string formatting that appends ':' without a port value."],"exampleFix":"// before\naddr := fmt.Sprintf(\"%s:\", hostname) // produces 'host:'\nconn, _ := grpc.Dial(\"dns:///\" + addr)\n// after\naddr := fmt.Sprintf(\"%s:%d\", hostname, port) // produces 'host:8080'\nconn, _ := grpc.Dial(\"dns:///\" + addr)","handlingStrategy":"validation","validationCode":"// Validate target does not end with ':' (incomplete port).\nfunc hasValidPort(target string) bool {\n    // allow bare IPv6 like '::' but reject 'host:' with no port\n    if strings.HasSuffix(target, \":\") {\n        host := strings.TrimSuffix(target, \":\")\n        if !strings.Contains(host, \":\") { // not IPv6\n            return false\n        }\n    }\n    return true\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always include a port after the colon in host:port targets.","Validate dial targets do not end with ':' before dialing.","Use net.SplitHostPort to parse and validate host:port format.","Fix string formatting that appends ':' without a port value."],"tags":["dns","resolver","dial-target","validation","grpc"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}