mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 05:56:38 +08:00
fix(relay): prioritize IPv4 in dual-stack outbound IP detection
修复 Relay 节点在双栈网络环境下可能上报 IPv6 地址,导致 Tunnel frpc 客户端无法连接 frps 的问题。 根因: - HTTPOutboundIPStrategy 首次尝试使用 tcp4 强制 IPv4 连接 - 失败时回退到双栈 tcp 客户端,此时可能通过 IPv6 连接并返回 IPv6 地址 - Relay 心跳将 IPv6 地址上报给 Server - Tunnel frpc 尝试连接该 IPv6 地址失败 修复: - 在回退到双栈客户端后,仍优先返回 IPv4 地址(通过 ip.To4() 转换) - 仅在完全无 IPv4 路由时才返回 IPv6 - 确保 Relay 心跳上报的 IP 与 frpc 连接兼容 影响范围: - pkg/geoip.HTTPOutboundIPStrategy.GetOutboundIP - internal/apps/relay/heartbeat.Service(通过 nodeip.DetectWithContext 调用) 测试: - 新增 TestHTTPOutboundIPStrategyPrioritizesIPv4 验证 IPv4 优先逻辑 - 所有现有测试保持通过
This commit is contained in:
@@ -25,6 +25,7 @@ sidebar: false
|
||||
|
||||
- 修复 openflared(Tunnel Client)WebSocket 连接在 Cloudflare 代理环境下频繁收到 EOF 断连的问题。根本原因:服务端 `read_pump` 仅在收到 WebSocket 协议层 Pong 帧时刷新读超时,而客户端(`golang.org/x/net/websocket`)以 JSON 应用层 `{"type":"pong"}` 响应 ping,服务端 90s 读超时到期后主动关闭连接,客户端收到 EOF 并进入无限重连循环。修复方式:在 `clientPongType` 分支中同步调用 `conn.SetReadDeadline` 刷新超时。
|
||||
- 修复 openflared frpc 子进程异常退出(`exit status 1`)时缺乏详细诊断信息的问题。现捕获 frpc stderr 并在进程退出时将其输出记录到结构化日志 `stderr` 字段,便于排查配置格式错误、Auth Token 鉴权失败、relay 端不可达等具体原因。
|
||||
- 修复 Relay 节点启动时在双栈网络环境可能上报 IPv6 地址,导致 Tunnel frpc 客户端无法连接 frps 的问题。强化 `pkg/geoip.HTTPOutboundIPStrategy` 在回退到双栈客户端后仍优先返回 IPv4 地址,确保 Relay 心跳上报的 IP 与 frpc 连接兼容。
|
||||
|
||||
### 变更
|
||||
|
||||
|
||||
+10
-1
@@ -109,7 +109,16 @@ func (s *HTTPOutboundIPStrategy) GetOutboundIP(ctx context.Context) (net.IP, err
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
},
|
||||
}
|
||||
return s.query(ctx, fallbackClient)
|
||||
ip, err = s.query(ctx, fallbackClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Always prioritize IPv4 for relay compatibility
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
return ipv4, nil
|
||||
}
|
||||
// Return IPv6 only if no IPv4 is available
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func (s *HTTPOutboundIPStrategy) query(ctx context.Context, client *http.Client) (net.IP, error) {
|
||||
|
||||
@@ -80,3 +80,45 @@ func TestHTTPOutboundIPStrategyRejectsPrivateIP(t *testing.T) {
|
||||
t.Fatal("expected private IP to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPOutboundIPStrategyPrioritizesIPv4(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
response string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "IPv4 address",
|
||||
response: `{"ip":"8.8.8.8"}`,
|
||||
want: "8.8.8.8",
|
||||
},
|
||||
{
|
||||
name: "IPv6 address normalized to IPv4",
|
||||
response: `{"ip":"::ffff:8.8.8.8"}`,
|
||||
want: "8.8.8.8",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(tt.response))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
strategy := NewHTTPOutboundIPStrategy(RealIPCCAdapter{URL: server.URL}, server.Client())
|
||||
ip, err := strategy.GetOutboundIP(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("GetOutboundIP failed: %v", err)
|
||||
}
|
||||
if ip.String() != tt.want {
|
||||
t.Errorf("GetOutboundIP() = %v, want %v", ip.String(), tt.want)
|
||||
}
|
||||
// Ensure it's an IPv4 address (4 bytes)
|
||||
if ipv4 := ip.To4(); ipv4 == nil {
|
||||
t.Errorf("expected IPv4 address, got %v", ip)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user