From d491ae2bee8112c532343a39fee14fee1d45a56a Mon Sep 17 00:00:00 2001 From: Pedro Perez Date: Fri, 16 Jan 2026 18:07:03 +0100 Subject: [PATCH] Fix #178: Server IP parameter now accepts IPv6 addresses The `-ip` parameter for server mode failed to accept IPv6 addresses, returning errors like "too many colons in address" or "Invalid IP address". Root cause: The code concatenated IP and port directly (e.g., "2001:db8::1:8888"), which is invalid for IPv6. IPv6 addresses must be bracketed: "[2001:db8::1]:8888". Changes: - Added formatListenAddress() helper function in utils.go to properly format listen addresses for both IPv4 and IPv6 - Updated IP validation in ethr.go to accept bracketed IPv6 addresses - Updated TCP and UDP servers in server.go to use the helper function Behavior: - IPv4: Works as before (e.g., "127.0.0.1:8888") - IPv6: Now works correctly (e.g., "[::1]:8888") - Empty IP: Works as before (":8888" for all interfaces) Backward compatibility: Fully maintained. No changes to client behavior, protocol, or command-line flags. Testing: - IPv4 server binding works - IPv6 server binding works (with and without brackets) - IPv6 client-server communication works - All existing functionality preserved Fixes #178 --- ethr.go | 7 ++++++- server.go | 4 ++-- utils.go | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/ethr.go b/ethr.go index d1a9c21..2eb59d0 100644 --- a/ethr.go +++ b/ethr.go @@ -157,7 +157,12 @@ func main() { } if *ip != "" { - gLocalIP = *ip + ipToParse := *ip + // Strip brackets from IPv6 addresses if present (e.g., "[2001:db8::1]") + if strings.HasPrefix(ipToParse, "[") && strings.HasSuffix(ipToParse, "]") { + ipToParse = ipToParse[1 : len(ipToParse)-1] + } + gLocalIP = ipToParse ipAddr := net.ParseIP(gLocalIP) if ipAddr == nil { printUsageError(fmt.Sprintf("Invalid IP address: <%s> specified.", *ip)) diff --git a/server.go b/server.go index 116886d..75aac28 100644 --- a/server.go +++ b/server.go @@ -204,7 +204,7 @@ func trySyncStartWithClient(test *ethrTest, conn net.Conn) (isControl bool, err } func srvrRunTCPServer() error { - l, err := net.Listen(Tcp(), gLocalIP+":"+gEthrPortStr) + l, err := net.Listen(Tcp(), formatListenAddress(gLocalIP, gEthrPortStr)) if err != nil { return err } @@ -378,7 +378,7 @@ func srvrRunTCPLatencyTest(test *ethrTest, clientParam EthrClientParam, conn net } func srvrRunUDPServer() error { - udpAddr, err := net.ResolveUDPAddr(Udp(), gLocalIP+":"+gEthrPortStr) + udpAddr, err := net.ResolveUDPAddr(Udp(), formatListenAddress(gLocalIP, gEthrPortStr)) if err != nil { ui.printDbg("Unable to resolve UDP address: %v", err) return err diff --git a/utils.go b/utils.go index 89c0c41..f93cb29 100644 --- a/utils.go +++ b/utils.go @@ -35,6 +35,30 @@ const ( // gPrecision controls decimal places for rate display (1 or 2) var gPrecision int = 1 +// formatListenAddress formats a local IP and port into a proper listen address. +// For IPv4, it returns "ip:port". +// For IPv6, it returns "[ip]:port". +// Empty IP returns ":port" (listen on all interfaces). +func formatListenAddress(ip string, port string) string { + if ip == "" { + return ":" + port + } + + parsedIP := net.ParseIP(ip) + if parsedIP == nil { + // Not a valid IP, return as-is + return ip + ":" + port + } + + if parsedIP.To4() == nil { + // IPv6 address - wrap in brackets + return "[" + ip + "]:" + port + } + + // IPv4 address + return ip + ":" + port +} + func numberToUnit(num uint64) string { unit := "" value := float64(num)