From 521fe79b15a7fe86f85ab20cb793cafbc4e84089 Mon Sep 17 00:00:00 2001 From: sagitchu Date: Fri, 1 May 2026 23:10:47 +0800 Subject: [PATCH] fix: validate tunnel probe target before cleanup --- go-backend/internal/http/handler/mutations.go | 18 +++---- .../handler/tunnel_probe_target_api_test.go | 47 +++++++++++++++++++ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/go-backend/internal/http/handler/mutations.go b/go-backend/internal/http/handler/mutations.go index 21ed68d..632d260 100644 --- a/go-backend/internal/http/handler/mutations.go +++ b/go-backend/internal/http/handler/mutations.go @@ -884,16 +884,7 @@ func (h *Handler) tunnelUpdate(w http.ResponseWriter, r *http.Request) { response.WriteJSON(w, response.ErrDefault("隧道ID不能为空")) return } - oldEntryNodeIDs, _ := h.tunnelEntryNodeIDs(id) typeVal := asInt(req["type"], 1) - oldTunnel, _ := h.getTunnelRecord(id) - oldChainRows, _ := h.listChainNodesForTunnel(id) - if oldTunnel != nil && oldTunnel.Type == 2 && typeVal != 2 { - h.cleanupTunnelRuntime(id) - } - h.cleanupFederationRuntime(id) - - now := time.Now().UnixMilli() ipPreference := asString(req["ipPreference"]) probeTarget, probeTargetConfigured, err := parseTunnelProbeTargetFromRequest(req) if err != nil { @@ -906,6 +897,15 @@ func (h *Handler) tunnelUpdate(w http.ResponseWriter, r *http.Request) { probeTargetHost = probeTarget.Host probeTargetPort = probeTarget.Port } + oldEntryNodeIDs, _ := h.tunnelEntryNodeIDs(id) + oldTunnel, _ := h.getTunnelRecord(id) + oldChainRows, _ := h.listChainNodesForTunnel(id) + if oldTunnel != nil && oldTunnel.Type == 2 && typeVal != 2 { + h.cleanupTunnelRuntime(id) + } + h.cleanupFederationRuntime(id) + + now := time.Now().UnixMilli() localDomain := h.federationLocalDomain() runtimeState, err := h.prepareTunnelCreateState(h.repo.DB(), req, typeVal, id) diff --git a/go-backend/internal/http/handler/tunnel_probe_target_api_test.go b/go-backend/internal/http/handler/tunnel_probe_target_api_test.go index ef1cd56..3072006 100644 --- a/go-backend/internal/http/handler/tunnel_probe_target_api_test.go +++ b/go-backend/internal/http/handler/tunnel_probe_target_api_test.go @@ -99,6 +99,42 @@ func TestTunnelCreateRejectsInvalidProbeTarget(t *testing.T) { } } +func TestTunnelUpdateInvalidProbeTargetDoesNotCleanFederationBindings(t *testing.T) { + h := setupProbeTargetTunnelHandler(t) + seedProbeTargetTunnel(t, h, 88, "existing", "old.example.com", 9443) + seedProbeTargetFederationBinding(t, h, 88) + body := bytes.NewReader([]byte(`{ + "id":88, + "name":"existing", + "type":1, + "flow":1, + "trafficRatio":1, + "status":1, + "inNodeId":[{"nodeId":10,"protocol":"tls"}], + "probeTargetHost":"https://example.com", + "probeTargetPort":443 + }`)) + + res := httptest.NewRecorder() + h.tunnelUpdate(res, httptest.NewRequest(http.MethodPost, "/api/v1/tunnel/update", body)) + var payload struct { + Code int `json:"code"` + Msg string `json:"msg"` + } + decodeProbeTargetResponse(t, res, &payload) + if payload.Code == 0 || payload.Msg == "" { + t.Fatalf("expected validation failure, got %+v", payload) + } + + bindings, err := h.repo.ListActiveFederationTunnelBindingsByTunnel(88) + if err != nil { + t.Fatalf("list federation bindings: %v", err) + } + if len(bindings) != 1 { + t.Fatalf("expected federation binding to remain after invalid update, got %d", len(bindings)) + } +} + func setupProbeTargetTunnelHandler(t *testing.T) *Handler { t.Helper() r, err := repo.Open(filepath.Join(t.TempDir(), "panel.db")) @@ -134,6 +170,17 @@ func seedProbeTargetTunnel(t *testing.T, h *Handler, id int64, name string, host } } +func seedProbeTargetFederationBinding(t *testing.T, h *Handler, tunnelID int64) { + t.Helper() + now := time.Now().UnixMilli() + if err := h.repo.DB().Exec(` + INSERT INTO federation_tunnel_binding(tunnel_id, node_id, chain_type, hop_inx, remote_url, resource_key, remote_binding_id, allocated_port, status, created_time, updated_time) + VALUES(?, 10, 1, 0, 'http://peer.example', ?, 'remote-binding', 30001, 1, ?, ?) + `, tunnelID, "probe-target-test-binding", now, now).Error; err != nil { + t.Fatalf("insert federation binding: %v", err) + } +} + func assertProbeTargetSuccess(t *testing.T, res *httptest.ResponseRecorder) { t.Helper() var payload struct {