From 363e7146031249663d42d98e4ae0346c63bdfa84 Mon Sep 17 00:00:00 2001 From: sagitchu Date: Sat, 28 Mar 2026 10:35:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=AF=E6=8C=81=E8=B7=A8=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E9=9A=A7=E9=81=93=E9=93=BE=E8=B7=AF=20(v6=E5=85=A5v4?= =?UTF-8?q?=E5=87=BA=20/=20v4=E5=85=A5v6=E5=87=BA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:selectTunnelDialHost 只检查同版本兼容 (v4->v4, v6->v6), 导致 v6-only 入口节点连接 v4-only 出口节点时报错: "节点链路不兼容" 修复:在 default 分支增加跨版本支持: - fromV6 && toV4 → 返回出口 v4 地址 - fromV4 && toV6 → 返回出口 v6 地址 更新测试用例以反映新行为 --- .../internal/http/handler/dual_stack_test.go | 30 +++++++++++++++---- go-backend/internal/http/handler/mutations.go | 12 ++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/go-backend/internal/http/handler/dual_stack_test.go b/go-backend/internal/http/handler/dual_stack_test.go index b7cb3e4..34c3736 100644 --- a/go-backend/internal/http/handler/dual_stack_test.go +++ b/go-backend/internal/http/handler/dual_stack_test.go @@ -345,21 +345,39 @@ func TestSelectTunnelDialHost_V6Only_PreferV4Fallback(t *testing.T) { } } -func TestSelectTunnelDialHost_Incompatible(t *testing.T) { +func TestSelectTunnelDialHost_CrossVersion_V4ToV6(t *testing.T) { + // v4-only -> v6-only: 跨版本支持,应成功返回 v6 地址 from := v4OnlyNode("from", "10.0.0.1") to := v6OnlyNode("to", "2001:db8::2") - _, err := selectTunnelDialHost(from, to, "", "") - if err == nil { - t.Fatal("expected error for incompatible nodes (v4-only -> v6-only)") + host, err := selectTunnelDialHost(from, to, "", "") + if err != nil { + t.Fatalf("unexpected error for cross-version (v4-only -> v6-only): %v", err) + } + if host != "2001:db8::2" { + t.Fatalf("expected v6 address for cross-version, got %q", host) } } -func TestSelectTunnelDialHost_Incompatible_Reverse(t *testing.T) { +func TestSelectTunnelDialHost_CrossVersion_V6ToV4(t *testing.T) { + // v6-only -> v4-only: 跨版本支持,应成功返回 v4 地址 from := v6OnlyNode("from", "2001:db8::1") to := v4OnlyNode("to", "10.0.0.2") + host, err := selectTunnelDialHost(from, to, "", "") + if err != nil { + t.Fatalf("unexpected error for cross-version (v6-only -> v4-only): %v", err) + } + if host != "10.0.0.2" { + t.Fatalf("expected v4 address for cross-version, got %q", host) + } +} + +func TestSelectTunnelDialHost_TrulyIncompatible(t *testing.T) { + // 真正不兼容:两个节点都没有任何 IP + from := &nodeRecord{Name: "empty-from", ServerIPv4: "", ServerIPv6: "", ServerIP: ""} + to := &nodeRecord{Name: "empty-to", ServerIPv4: "", ServerIPv6: "", ServerIP: ""} _, err := selectTunnelDialHost(from, to, "", "") if err == nil { - t.Fatal("expected error for incompatible nodes (v6-only -> v4-only)") + t.Fatal("expected error for nodes with no IP addresses") } } diff --git a/go-backend/internal/http/handler/mutations.go b/go-backend/internal/http/handler/mutations.go index 4b22426..e899430 100644 --- a/go-backend/internal/http/handler/mutations.go +++ b/go-backend/internal/http/handler/mutations.go @@ -3464,6 +3464,7 @@ func selectTunnelDialHost(fromNode, toNode *nodeRecord, ipPreference string, con } } default: + // 同版本优先 if fromV4 && toV4 { if host := pickNodeAddressV4(toNode); host != "" { return host, nil @@ -3474,6 +3475,17 @@ func selectTunnelDialHost(fromNode, toNode *nodeRecord, ipPreference string, con return host, nil } } + // 跨版本支持:v6入v4出 / v4入v6出 + if fromV6 && toV4 { + if host := pickNodeAddressV4(toNode); host != "" { + return host, nil + } + } + if fromV4 && toV6 { + if host := pickNodeAddressV6(toNode); host != "" { + return host, nil + } + } } return "", fmt.Errorf("节点链路不兼容:%s(v4=%t,v6=%t) -> %s(v4=%t,v6=%t)", nodeDisplayName(fromNode), fromV4, fromV6, nodeDisplayName(toNode), toV4, toV6) }