From cf6294a77dad635b7b8d135e77f426577845890d Mon Sep 17 00:00:00 2001 From: sagit Date: Fri, 13 Feb 2026 09:42:38 +0000 Subject: [PATCH] fix(backend): normalize strategy data and proxy ip parsing --- .../internal/http/handler/control_plane.go | 4 +-- .../internal/http/handler/federation.go | 14 ++++++-- .../http/handler/federation_share_test.go | 7 ++++ .../internal/http/handler/flow_policy.go | 4 +-- go-backend/internal/http/handler/mutations.go | 4 +-- .../internal/store/sqlite/repository.go | 36 +++++++++++++++++-- .../contract/tunnel_create_contract_test.go | 12 +++++++ 7 files changed, 71 insertions(+), 10 deletions(-) diff --git a/go-backend/internal/http/handler/control_plane.go b/go-backend/internal/http/handler/control_plane.go index 0e3d084..43ea702 100644 --- a/go-backend/internal/http/handler/control_plane.go +++ b/go-backend/internal/http/handler/control_plane.go @@ -114,7 +114,7 @@ func (h *Handler) ensureTunnelPermission(userID int64, roleID int, tunnelID int6 func (h *Handler) getForwardRecord(forwardID int64) (*forwardRecord, error) { row := h.repo.DB().QueryRow(` - SELECT id, user_id, user_name, name, tunnel_id, remote_addr, strategy, status + SELECT id, user_id, user_name, name, tunnel_id, remote_addr, COALESCE(strategy, 'fifo'), status FROM forward WHERE id = ? LIMIT 1 `, forwardID) var fr forwardRecord @@ -152,7 +152,7 @@ func (h *Handler) getTunnelRecord(tunnelID int64) (*tunnelRecord, error) { func (h *Handler) listForwardsByTunnel(tunnelID int64) ([]forwardRecord, error) { rows, err := h.repo.DB().Query(` - SELECT id, user_id, user_name, name, tunnel_id, remote_addr, strategy, status + SELECT id, user_id, user_name, name, tunnel_id, remote_addr, COALESCE(strategy, 'fifo'), status FROM forward WHERE tunnel_id = ? ORDER BY id ASC diff --git a/go-backend/internal/http/handler/federation.go b/go-backend/internal/http/handler/federation.go index 09abef2..cfd639d 100644 --- a/go-backend/internal/http/handler/federation.go +++ b/go-backend/internal/http/handler/federation.go @@ -1356,7 +1356,7 @@ func parseIPLiteral(raw string) net.IP { } if ip := net.ParseIP(value); ip != nil { - return ip + return normalizeIPAddress(ip) } host, _, err := net.SplitHostPort(value) @@ -1368,7 +1368,17 @@ func parseIPLiteral(raw string) net.IP { if host == "" { return nil } - return net.ParseIP(host) + return normalizeIPAddress(net.ParseIP(host)) +} + +func normalizeIPAddress(ip net.IP) net.IP { + if ip == nil { + return nil + } + if v4 := ip.To4(); v4 != nil { + return v4 + } + return ip.To16() } func isTrustedProxyIP(ip net.IP) bool { diff --git a/go-backend/internal/http/handler/federation_share_test.go b/go-backend/internal/http/handler/federation_share_test.go index 37cd8a1..d5e8c95 100644 --- a/go-backend/internal/http/handler/federation_share_test.go +++ b/go-backend/internal/http/handler/federation_share_test.go @@ -567,6 +567,13 @@ func TestAuthPeerAllowedIPs(t *testing.T) { xff: "198.51.100.20, 172.20.0.3", wantAllowed: true, }, + { + name: "ipv4-mapped proxy xff allowed", + allowedIPs: "198.51.100.20", + remoteAddr: "[::ffff:172.20.0.3]:34567", + xff: "198.51.100.20, 172.20.0.3", + wantAllowed: true, + }, { name: "non whitelisted ip denied", allowedIPs: "203.0.113.10", diff --git a/go-backend/internal/http/handler/flow_policy.go b/go-backend/internal/http/handler/flow_policy.go index 0c00e96..e8ca759 100644 --- a/go-backend/internal/http/handler/flow_policy.go +++ b/go-backend/internal/http/handler/flow_policy.go @@ -251,7 +251,7 @@ func (h *Handler) pauseForwardRecords(forwards []forwardRecord, now int64) { func (h *Handler) listActiveForwardsByUser(userID int64) ([]forwardRecord, error) { rows, err := h.repo.DB().Query(` - SELECT id, user_id, user_name, name, tunnel_id, remote_addr, strategy, status + SELECT id, user_id, user_name, name, tunnel_id, remote_addr, COALESCE(strategy, 'fifo'), status FROM forward WHERE user_id = ? AND status = 1 ORDER BY id ASC @@ -266,7 +266,7 @@ func (h *Handler) listActiveForwardsByUser(userID int64) ([]forwardRecord, error func (h *Handler) listActiveForwardsByUserTunnel(userID int64, tunnelID int64) ([]forwardRecord, error) { rows, err := h.repo.DB().Query(` - SELECT id, user_id, user_name, name, tunnel_id, remote_addr, strategy, status + SELECT id, user_id, user_name, name, tunnel_id, remote_addr, COALESCE(strategy, 'fifo'), status FROM forward WHERE user_id = ? AND tunnel_id = ? AND status = 1 ORDER BY id ASC diff --git a/go-backend/internal/http/handler/mutations.go b/go-backend/internal/http/handler/mutations.go index cff0744..2ae9a32 100644 --- a/go-backend/internal/http/handler/mutations.go +++ b/go-backend/internal/http/handler/mutations.go @@ -2863,8 +2863,8 @@ func replaceTunnelChainsTx(tx *store.Tx, tunnelID int64, req map[string]interfac if nodeID <= 0 { continue } - _, err := tx.Exec(`INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol) VALUES(?, '1', ?, NULL, NULL, 0, ?)`, - tunnelID, nodeID, defaultString(asString(n["protocol"]), "tls")) + _, err := tx.Exec(`INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol) VALUES(?, '1', ?, NULL, ?, 0, ?)`, + tunnelID, nodeID, defaultString(asString(n["strategy"]), "round"), defaultString(asString(n["protocol"]), "tls")) if err != nil { return err } diff --git a/go-backend/internal/store/sqlite/repository.go b/go-backend/internal/store/sqlite/repository.go index 0ff75fe..0db5f58 100644 --- a/go-backend/internal/store/sqlite/repository.go +++ b/go-backend/internal/store/sqlite/repository.go @@ -719,7 +719,7 @@ func (r *Repository) ListForwards() ([]map[string]interface{}, error) { } rows, err := r.db.Query(` - SELECT f.id, f.user_id, f.user_name, f.name, f.tunnel_id, COALESCE(t.name, ''), f.remote_addr, f.strategy, + SELECT f.id, f.user_id, f.user_name, f.name, f.tunnel_id, COALESCE(t.name, ''), f.remote_addr, COALESCE(f.strategy, 'fifo'), f.in_flow, f.out_flow, f.created_time, f.status, f.inx FROM forward f LEFT JOIN tunnel t ON t.id = f.tunnel_id @@ -1297,7 +1297,7 @@ func bootstrapSchema(db *store.DB, schemaSQL, seedSQL string) error { return nil } -const currentSchemaVersion = 1 +const currentSchemaVersion = 2 func getSchemaVersion(db *store.DB) int { _, _ = db.Exec(`CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL DEFAULT 0)`) @@ -1367,6 +1367,27 @@ func migrateSchema(db *store.DB) error { } } + normalizeStrategy := func(table, defaultValue string) error { + _, err := db.Exec(fmt.Sprintf("UPDATE %s SET strategy = ? WHERE strategy IS NULL", table), defaultValue) + if err != nil { + if isMissingTableError(db.Dialect(), err) { + return nil + } + return fmt.Errorf("normalize %s.strategy: %w", table, err) + } + return nil + } + + if err := normalizeStrategy("forward", "fifo"); err != nil { + return err + } + if err := normalizeStrategy("chain_tunnel", "round"); err != nil { + return err + } + if err := normalizeStrategy("peer_share_runtime", "round"); err != nil { + return err + } + if db.Dialect() == store.DialectPostgres { if err := ensurePostgresIDDefaults(db); err != nil { return err @@ -1537,6 +1558,17 @@ func isMissingColumnError(dialect store.Dialect, err error) bool { return strings.Contains(msg, "no such column") } +func isMissingTableError(dialect store.Dialect, err error) bool { + if err == nil { + return false + } + msg := strings.ToLower(err.Error()) + if dialect == store.DialectPostgres { + return strings.Contains(msg, "relation") && strings.Contains(msg, "does not exist") + } + return strings.Contains(msg, "no such table") +} + func (r *Repository) CreatePeerShare(share *PeerShare) error { if r == nil || r.db == nil { return errors.New("repository not initialized") diff --git a/go-backend/tests/contract/tunnel_create_contract_test.go b/go-backend/tests/contract/tunnel_create_contract_test.go index 407a744..c1e8d86 100644 --- a/go-backend/tests/contract/tunnel_create_contract_test.go +++ b/go-backend/tests/contract/tunnel_create_contract_test.go @@ -2,6 +2,7 @@ package contract_test import ( "bytes" + "database/sql" "encoding/json" "net/http" "net/http/httptest" @@ -144,6 +145,17 @@ func TestTunnelUpdateAssignsChainPortsContract(t *testing.T) { if outPort <= 0 { t.Fatalf("expected out node port to be assigned, got %d", outPort) } + + var entryStrategy sql.NullString + if err := repo.DB().QueryRow(`SELECT strategy FROM chain_tunnel WHERE tunnel_id = ? AND chain_type = 1 LIMIT 1`, tunnelID).Scan(&entryStrategy); err != nil { + t.Fatalf("query entry strategy: %v", err) + } + if !entryStrategy.Valid || strings.TrimSpace(entryStrategy.String) == "" { + t.Fatalf("expected entry strategy to be non-null and non-empty") + } + if entryStrategy.String != "round" { + t.Fatalf("expected entry strategy round, got %q", entryStrategy.String) + } } func jsonInt(v int64) string {