From a590c38d8b1f4429535f49ff5cac9655a29483a7 Mon Sep 17 00:00:00 2001 From: eason <85663565+mango766@users.noreply.github.com> Date: Tue, 17 Mar 2026 13:27:47 +0800 Subject: [PATCH] [client] Fix IPv6 address formatting in DNS address construction (#5603) Replace fmt.Sprintf("%s:%d", ip, port) with net.JoinHostPort() to properly handle IPv6 addresses that need bracket wrapping (e.g., [2606:4700:4700::1111]:53 instead of 2606:4700:4700::1111:53). Without this fix, configuring IPv6 nameservers causes "too many colons in address" errors because Go's net.Dial cannot parse the malformed address string. Fixes #5601 Related to #4074 Co-authored-by: easonysliu --- client/internal/dns/service_listener.go | 5 +++-- client/internal/routemanager/client/client.go | 4 +++- client/internal/routemanager/dnsinterceptor/handler.go | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/client/internal/dns/service_listener.go b/client/internal/dns/service_listener.go index 806559444..f7ddfd40f 100644 --- a/client/internal/dns/service_listener.go +++ b/client/internal/dns/service_listener.go @@ -6,6 +6,7 @@ import ( "net" "net/netip" "runtime" + "strconv" "sync" "time" @@ -69,7 +70,7 @@ func (s *serviceViaListener) Listen() error { return fmt.Errorf("eval listen address: %w", err) } s.listenIP = s.listenIP.Unmap() - s.server.Addr = fmt.Sprintf("%s:%d", s.listenIP, s.listenPort) + s.server.Addr = net.JoinHostPort(s.listenIP.String(), strconv.Itoa(int(s.listenPort))) log.Debugf("starting dns on %s", s.server.Addr) go func() { s.setListenerStatus(true) @@ -186,7 +187,7 @@ func (s *serviceViaListener) testFreePort(port int) (netip.Addr, bool) { } func (s *serviceViaListener) tryToBind(ip netip.Addr, port int) bool { - addrString := fmt.Sprintf("%s:%d", ip, port) + addrString := net.JoinHostPort(ip.String(), strconv.Itoa(port)) udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort(addrString)) probeListener, err := net.ListenUDP("udp", udpAddr) if err != nil { diff --git a/client/internal/routemanager/client/client.go b/client/internal/routemanager/client/client.go index bad616271..e6ef8b876 100644 --- a/client/internal/routemanager/client/client.go +++ b/client/internal/routemanager/client/client.go @@ -3,7 +3,9 @@ package client import ( "context" "fmt" + "net" "reflect" + "strconv" "time" log "github.com/sirupsen/logrus" @@ -564,7 +566,7 @@ func HandlerFromRoute(params common.HandlerParams) RouteHandler { return dnsinterceptor.New(params) case handlerTypeDynamic: dns := nbdns.NewServiceViaMemory(params.WgInterface) - dnsAddr := fmt.Sprintf("%s:%d", dns.RuntimeIP(), dns.RuntimePort()) + dnsAddr := net.JoinHostPort(dns.RuntimeIP().String(), strconv.Itoa(dns.RuntimePort())) return dynamic.NewRoute(params, dnsAddr) default: return static.NewRoute(params) diff --git a/client/internal/routemanager/dnsinterceptor/handler.go b/client/internal/routemanager/dnsinterceptor/handler.go index 4bf0d5476..64f2a8789 100644 --- a/client/internal/routemanager/dnsinterceptor/handler.go +++ b/client/internal/routemanager/dnsinterceptor/handler.go @@ -4,8 +4,10 @@ import ( "context" "errors" "fmt" + "net" "net/netip" "runtime" + "strconv" "strings" "sync" "sync/atomic" @@ -249,7 +251,7 @@ func (d *DnsInterceptor) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { r.MsgHdr.AuthenticatedData = true } - upstream := fmt.Sprintf("%s:%d", upstreamIP.String(), uint16(d.forwarderPort.Load())) + upstream := net.JoinHostPort(upstreamIP.String(), strconv.FormatUint(uint64(d.forwarderPort.Load()), 10)) ctx, cancel := context.WithTimeout(context.Background(), dnsTimeout) defer cancel()