mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-04 01:06:36 +08:00
fix(backend): respect tunnel IP preference in forward diagnosis (#160)
Forward diagnosis chain-hop probes now inherit the tunnel ipPreference so v6-priority tunnels test IPv6 targets instead of defaulting to IPv4. Add a contract test to lock IPv6 target selection for entry->chain and chain->exit diagnostics.
This commit is contained in:
@@ -360,6 +360,8 @@ func (h *Handler) diagnoseForwardRuntime(forward *forwardRecord) (map[string]int
|
|||||||
return nil, errors.New("隧道配置不完整")
|
return nil, errors.New("隧道配置不完整")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipPreference := h.repo.GetTunnelIPPreference(forward.TunnelID)
|
||||||
|
|
||||||
inNodes, chainHops, outNodes := splitChainNodeGroups(chainRows)
|
inNodes, chainHops, outNodes := splitChainNodeGroups(chainRows)
|
||||||
results := make([]map[string]interface{}, 0, len(chainRows)*2+len(targets))
|
results := make([]map[string]interface{}, 0, len(chainRows)*2+len(targets))
|
||||||
nodeCache := map[int64]*nodeRecord{}
|
nodeCache := map[int64]*nodeRecord{}
|
||||||
@@ -383,7 +385,7 @@ func (h *Handler) diagnoseForwardRuntime(forward *forwardRecord) (map[string]int
|
|||||||
"fromChainType": 1,
|
"fromChainType": 1,
|
||||||
"toChainType": 2,
|
"toChainType": 2,
|
||||||
"toInx": firstNode.Inx,
|
"toInx": firstNode.Inx,
|
||||||
}, "")
|
}, ipPreference)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, outNode := range outNodes {
|
for _, outNode := range outNodes {
|
||||||
@@ -391,7 +393,7 @@ func (h *Handler) diagnoseForwardRuntime(forward *forwardRecord) (map[string]int
|
|||||||
h.appendChainHopDiagnosis(&results, nodeCache, inNode.NodeID, outNode, description, map[string]interface{}{
|
h.appendChainHopDiagnosis(&results, nodeCache, inNode.NodeID, outNode, description, map[string]interface{}{
|
||||||
"fromChainType": 1,
|
"fromChainType": 1,
|
||||||
"toChainType": 3,
|
"toChainType": 3,
|
||||||
}, "")
|
}, ipPreference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -406,7 +408,7 @@ func (h *Handler) diagnoseForwardRuntime(forward *forwardRecord) (map[string]int
|
|||||||
"fromInx": currentNode.Inx,
|
"fromInx": currentNode.Inx,
|
||||||
"toChainType": 2,
|
"toChainType": 2,
|
||||||
"toInx": nextNode.Inx,
|
"toInx": nextNode.Inx,
|
||||||
}, "")
|
}, ipPreference)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, outNode := range outNodes {
|
for _, outNode := range outNodes {
|
||||||
@@ -415,7 +417,7 @@ func (h *Handler) diagnoseForwardRuntime(forward *forwardRecord) (map[string]int
|
|||||||
"fromChainType": 2,
|
"fromChainType": 2,
|
||||||
"fromInx": currentNode.Inx,
|
"fromInx": currentNode.Inx,
|
||||||
"toChainType": 3,
|
"toChainType": 3,
|
||||||
}, "")
|
}, ipPreference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,6 +193,126 @@ func TestDiagnosisChainCoverageContracts(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestForwardDiagnosisRespectsTunnelIPPreferenceContract(t *testing.T) {
|
||||||
|
secret := "contract-jwt-secret"
|
||||||
|
router, r := setupDiagnosisContractRouter(t, secret)
|
||||||
|
now := time.Now().UnixMilli()
|
||||||
|
|
||||||
|
if err := r.DB().Exec(`
|
||||||
|
INSERT INTO user(id, user, pwd, role_id, exp_time, flow, in_flow, out_flow, flow_reset_time, num, created_time, updated_time, status)
|
||||||
|
VALUES(2, 'normal_user', '3c85cdebade1c51cf64ca9f3c09d182d', 1, 2727251700000, 99999, 0, 0, 1, 99999, ?, ?, 1)
|
||||||
|
`, now, now).Error; err != nil {
|
||||||
|
t.Fatalf("insert user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.DB().Exec(`
|
||||||
|
INSERT INTO tunnel(name, traffic_ratio, type, protocol, flow, created_time, updated_time, status, in_ip, inx, ip_preference)
|
||||||
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`, "diagnose-ip-pref-forward", 1.0, 2, "tls", 99999, now, now, 1, nil, 0, "v6").Error; err != nil {
|
||||||
|
t.Fatalf("insert tunnel: %v", err)
|
||||||
|
}
|
||||||
|
tunnelID := mustLastInsertID(t, r, "diagnose-ip-pref-forward")
|
||||||
|
|
||||||
|
insertNode := func(name, v4, v6 string) int64 {
|
||||||
|
if err := r.DB().Exec(`
|
||||||
|
INSERT INTO node(name, secret, server_ip, server_ip_v4, server_ip_v6, port, interface_name, version, http, tls, socks, created_time, updated_time, status, tcp_listen_addr, udp_listen_addr, inx)
|
||||||
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`, name, name+"-secret", v4, v4, v6, "30000-30010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil {
|
||||||
|
t.Fatalf("insert node %s: %v", name, err)
|
||||||
|
}
|
||||||
|
return mustLastInsertID(t, r, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
entryNodeID := insertNode("entry-node-v6", "10.10.1.10", "2001:db8:10::10")
|
||||||
|
chainNodeID := insertNode("chain-node-v6", "10.10.1.20", "2001:db8:10::20")
|
||||||
|
exitNodeID := insertNode("exit-node-v6", "10.10.1.30", "2001:db8:10::30")
|
||||||
|
|
||||||
|
if err := r.DB().Exec(`
|
||||||
|
INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol)
|
||||||
|
VALUES(?, 1, ?, 30001, 'round', 1, 'tls')
|
||||||
|
`, tunnelID, entryNodeID).Error; err != nil {
|
||||||
|
t.Fatalf("insert entry chain: %v", err)
|
||||||
|
}
|
||||||
|
if err := r.DB().Exec(`
|
||||||
|
INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol)
|
||||||
|
VALUES(?, 2, ?, 30002, 'round', 1, 'tls')
|
||||||
|
`, tunnelID, chainNodeID).Error; err != nil {
|
||||||
|
t.Fatalf("insert middle chain: %v", err)
|
||||||
|
}
|
||||||
|
if err := r.DB().Exec(`
|
||||||
|
INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol)
|
||||||
|
VALUES(?, 3, ?, 30003, 'round', 1, 'tls')
|
||||||
|
`, tunnelID, exitNodeID).Error; err != nil {
|
||||||
|
t.Fatalf("insert exit chain: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.DB().Exec(`
|
||||||
|
INSERT INTO forward(user_id, user_name, name, tunnel_id, remote_addr, strategy, in_flow, out_flow, created_time, updated_time, status, inx)
|
||||||
|
VALUES(?, ?, ?, ?, ?, ?, 0, 0, ?, ?, 1, ?)
|
||||||
|
`, 2, "normal_user", "ip-pref-forward", tunnelID, "8.8.8.8:53", "fifo", now, now, 0).Error; err != nil {
|
||||||
|
t.Fatalf("insert forward: %v", err)
|
||||||
|
}
|
||||||
|
forwardID := mustLastInsertID(t, r, "ip-pref-forward")
|
||||||
|
|
||||||
|
userToken, err := auth.GenerateToken(2, "normal_user", 1, secret)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("generate user token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/forward/diagnose", bytes.NewBufferString(`{"forwardId":`+strconv.FormatInt(forwardID, 10)+`}`))
|
||||||
|
req.Header.Set("Authorization", userToken)
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
|
||||||
|
router.ServeHTTP(res, req)
|
||||||
|
|
||||||
|
var out response.R
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&out); err != nil {
|
||||||
|
t.Fatalf("decode response: %v", err)
|
||||||
|
}
|
||||||
|
if out.Code != 0 {
|
||||||
|
t.Fatalf("expected code 0, got %d (%s)", out.Code, out.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, ok := out.Data.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected object payload, got %T", out.Data)
|
||||||
|
}
|
||||||
|
results, ok := payload["results"].([]interface{})
|
||||||
|
if !ok || len(results) == 0 {
|
||||||
|
t.Fatalf("expected non-empty results, got %v", payload["results"])
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEntryToChain := false
|
||||||
|
hasChainToExit := false
|
||||||
|
for _, raw := range results {
|
||||||
|
item, ok := raw.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
from := valueAsInt(item["fromChainType"])
|
||||||
|
to := valueAsInt(item["toChainType"])
|
||||||
|
targetIP := strings.TrimSpace(valueAsString(item["targetIp"]))
|
||||||
|
|
||||||
|
if from == 1 && to == 2 {
|
||||||
|
hasEntryToChain = true
|
||||||
|
if targetIP != "2001:db8:10::20" {
|
||||||
|
t.Fatalf("expected entry->chain diagnosis target to use IPv6, got %q", targetIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if from == 2 && to == 3 {
|
||||||
|
hasChainToExit = true
|
||||||
|
if targetIP != "2001:db8:10::30" {
|
||||||
|
t.Fatalf("expected chain->exit diagnosis target to use IPv6, got %q", targetIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasEntryToChain || !hasChainToExit {
|
||||||
|
t.Fatalf("expected entry->chain and chain->exit steps, got entry=%v chain=%v", hasEntryToChain, hasChainToExit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDiagnosisUsesFederationRuntimeForRemoteNodes(t *testing.T) {
|
func TestDiagnosisUsesFederationRuntimeForRemoteNodes(t *testing.T) {
|
||||||
secret := "contract-jwt-secret"
|
secret := "contract-jwt-secret"
|
||||||
router, r := setupDiagnosisContractRouter(t, secret)
|
router, r := setupDiagnosisContractRouter(t, secret)
|
||||||
|
|||||||
Reference in New Issue
Block a user