From 841d43344a1a14d32fdcc15bdc84714304386619 Mon Sep 17 00:00:00 2001 From: qimaoww <90671723+qimaoww@users.noreply.github.com> Date: Tue, 31 Mar 2026 11:24:56 +0800 Subject: [PATCH] =?UTF-8?q?fix(backend):=20=E4=BF=AE=E5=A4=8D=E8=BD=AC?= =?UTF-8?q?=E5=8F=91=E7=9B=91=E5=90=AC=20IP=20=E4=B8=BA=20IPv6=20=E6=97=B6?= =?UTF-8?q?=E6=8A=A5missing=20port=20in=20address=20=20(#397)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): handle IPv6 forward bind IP ports * test(handler): add IPv6 bindIP test cases for forward service config --------- Co-authored-by: sagit <36596628+Sagit-chu@users.noreply.github.com> Co-authored-by: sagitchu --- .../internal/http/handler/control_plane.go | 7 ++- .../http/handler/control_plane_test.go | 57 +++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/go-backend/internal/http/handler/control_plane.go b/go-backend/internal/http/handler/control_plane.go index be8057c..85f3332 100644 --- a/go-backend/internal/http/handler/control_plane.go +++ b/go-backend/internal/http/handler/control_plane.go @@ -1577,10 +1577,11 @@ func buildForwardServiceConfigs(baseName string, forward *forwardRecord, tunnel } var serviceAddr string if bindIP != "" { - if strings.Contains(bindIP, ":") { - serviceAddr = processServerAddress(bindIP) + trimmedBindIP := strings.TrimSpace(bindIP) + if _, _, err := net.SplitHostPort(trimmedBindIP); err == nil { + serviceAddr = processServerAddress(trimmedBindIP) } else { - serviceAddr = processServerAddress(fmt.Sprintf("%s:%d", bindIP, port)) + serviceAddr = processServerAddress(net.JoinHostPort(strings.Trim(trimmedBindIP, "[]"), strconv.Itoa(port))) } } else { serviceAddr = processServerAddress(fmt.Sprintf("%s:%d", listenerAddr, port)) diff --git a/go-backend/internal/http/handler/control_plane_test.go b/go-backend/internal/http/handler/control_plane_test.go index d751868..3d1128b 100644 --- a/go-backend/internal/http/handler/control_plane_test.go +++ b/go-backend/internal/http/handler/control_plane_test.go @@ -421,6 +421,63 @@ func TestBuildForwardServiceConfigs_BindIPAlreadyContainsPort(t *testing.T) { } } +func TestBuildForwardServiceConfigs_IPv6BindIP(t *testing.T) { + tests := []struct { + name string + bindIP string + port int + wantAddr string + }{ + { + name: "pure ipv6 without port", + bindIP: "2001:db8::1", + port: 22000, + wantAddr: "[2001:db8::1]:22000", + }, + { + name: "bracketed ipv6 without port", + bindIP: "[2001:db8::2]", + port: 22001, + wantAddr: "[2001:db8::2]:22001", + }, + { + name: "bracketed ipv6 with port", + bindIP: "[2001:db8::3]:8080", + port: 55555, + wantAddr: "[2001:db8::3]:8080", + }, + { + name: "ipv6 link-local with zone", + bindIP: "fe80::1%eth0", + port: 22002, + wantAddr: "[fe80::1%eth0]:22002", + }, + { + name: "ipv6 localhost", + bindIP: "::1", + port: 22003, + wantAddr: "[::1]:22003", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + forward := &forwardRecord{RemoteAddr: "1.2.3.4:80", Strategy: "fifo", TunnelID: 7} + node := &nodeRecord{TCPListenAddr: "[::]", UDPListenAddr: "[::]"} + services := buildForwardServiceConfigs("1_2_0", forward, nil, node, tt.port, tt.bindIP, nil, false) + if len(services) != 2 { + t.Fatalf("expected 2 services, got %d", len(services)) + } + for _, svc := range services { + addr, _ := svc["addr"].(string) + if addr != tt.wantAddr { + t.Fatalf("expected addr %q, got %q", tt.wantAddr, addr) + } + } + }) + } +} + func TestProcessServerAddress_StripsURLSchemeAndPath(t *testing.T) { tests := []struct { name string