Use net.JoinHostPort() for IPv6-safe host:port formatting#11959
Conversation
Go 1.26 tightened net/url parsing to reject bare IPv6 addresses in URLs (issue #75223). URLs like http://2001:db8::1:9099/path were silently accepted in Go 1.25 but now correctly return a parse error. This broke all IPv6 BPF FV tests which construct health check URLs with unbracketed IPv6 addresses. Replace string concatenation with net.JoinHostPort() which correctly brackets IPv6 addresses ([::1]:9099) and leaves IPv4 unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates several Calico components and FV helpers to use net.JoinHostPort() instead of manual host + ":" + port formatting, ensuring IPv6 addresses are correctly bracketed when constructing host:port strings and URLs under stricter Go net/url parsing.
Changes:
- Replace manual
host:portstring concatenation/formatting withnet.JoinHostPort()in multiple packages. - Fix Felix/node health and FV metrics/readiness URL construction to be IPv6-safe.
- Update server listen address formatting to be IPv6-safe.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| whisker-backend/pkg/config/api.go | Use net.JoinHostPort() for HostAddr() to support IPv6-safe bind/address formatting. |
| node/pkg/health/health.go | Build Felix liveness/readiness endpoints with net.JoinHostPort() for IPv6-safe health URLs. |
| guardian/pkg/server/server.go | Use net.JoinHostPort() when binding the cluster listener to support IPv6 hosts. |
| felix/fv/metrics/metrics.go | Use net.JoinHostPort() when constructing the metrics URL to support IPv6 Felix IPs. |
| felix/fv/infrastructure/felix.go | Use net.JoinHostPort() for Felix readiness URL construction in FV infrastructure helper. |
| app-policy/cmd/dikastes/dikastes.go | Use net.JoinHostPort() for the HTTP termination server address to support IPv6 binds. |
| logrus.Infof("Listening on %s:%s for connections to proxy to voltron", srv.listenHost, srv.listenPort) | ||
| if err := srv.tunnel.Connect(srv.shutdownCtx); err != nil { | ||
| return fmt.Errorf("failed to connect to tunnel: %w", err) | ||
| } | ||
|
|
||
| listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", srv.listenHost, srv.listenPort)) | ||
| listener, err := net.Listen("tcp", net.JoinHostPort(srv.listenHost, srv.listenPort)) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to listen on %s:%s: %w", srv.listenHost, srv.listenPort, err) |
There was a problem hiding this comment.
The log/error messages still format the bind address as "%s:%s", which will be misleading for IPv6 hosts (e.g., "::" becomes ":::8080") even though the listener now uses net.JoinHostPort(). Consider using net.JoinHostPort(srv.listenHost, srv.listenPort) (or listener.Addr().String()) in the Infof and error wrapping so IPv6 addresses are rendered unambiguously.
|
|
||
| url := "http://" + healthAddr + ":9099/readiness" | ||
| url := "http://" + net.JoinHostPort(healthAddr, "9099") + "/readiness" | ||
| ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) |
There was a problem hiding this comment.
PR description says IPv6 BPF FV tests broke due to unbracketed IPv6 in URLs and that this PR replaces string concatenation with JoinHostPort(), but there are still unbracketed host:port URL constructions elsewhere (e.g., felix/fv/health_test.go:429 uses "http://"+felix.IP+":9099/..." and felix/k8sfv/metric.go:28 uses "http://"+felixIP+":9091/...", plus felix/fv/utils/utils.go:203 for etcd endpoints). Either expand this PR to cover these remaining cases or narrow the description/scope accordingly; otherwise IPv6 test coverage may still fail under the stricter net/url parsing.
| func (cfg *Config) HostAddr() string { | ||
| return fmt.Sprintf("%s:%s", cfg.Host, cfg.Port) | ||
| return net.JoinHostPort(cfg.Host, cfg.Port) | ||
| } |
There was a problem hiding this comment.
The PR body still contains a release-note block with "TBD". The repo's release-note validation workflow fails when release notes are required and the block is left as "TBD", so this may need to be updated to a one-line user-visible note (or set to "None" if appropriate).
Go 1.26 tightened net/url parsing to reject bare IPv6 addresses in URLs (issue #75223). URLs like http://2001:db8::1:9099/path were silently accepted in Go 1.25 but now correctly return a parse error. This broke all IPv6 BPF FV tests which construct health check URLs with unbracketed IPv6 addresses.
Replace string concatenation with net.JoinHostPort() which correctly brackets IPv6 addresses ([::1]:9099) and leaves IPv4 unchanged.
Release note: