mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
fix(backend): randomize new tunnel relay ports safely
This commit is contained in:
@@ -67,6 +67,7 @@ go-gost/ss/
|
|||||||
.entire/
|
.entire/
|
||||||
bin/
|
bin/
|
||||||
tmp/
|
tmp/
|
||||||
|
.worktrees/
|
||||||
*.swp
|
*.swp
|
||||||
*.bak
|
*.bak
|
||||||
|
|
||||||
|
|||||||
@@ -2306,13 +2306,24 @@ func (h *Handler) forwardBatchChangeTunnel(w http.ResponseWriter, r *http.Reques
|
|||||||
nd, ndErr := h.getNodeRecord(nid)
|
nd, ndErr := h.getNodeRecord(nid)
|
||||||
if ndErr != nil {
|
if ndErr != nil {
|
||||||
portRangeErr = ndErr
|
portRangeErr = ndErr
|
||||||
continue
|
portRangeOk = false
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if validateErr := validateRemoteNodePort(nd, p); validateErr != nil {
|
if validateErr := validateRemoteNodePort(nd, p); validateErr != nil {
|
||||||
portRangeOk = false
|
portRangeOk = false
|
||||||
portRangeErr = validateErr
|
portRangeErr = validateErr
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if validateErr := validateLocalNodePort(nd, p); validateErr != nil {
|
||||||
|
portRangeOk = false
|
||||||
|
portRangeErr = validateErr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if validateErr := h.validateForwardPortAvailability(nd, p, id); validateErr != nil {
|
||||||
|
portRangeOk = false
|
||||||
|
portRangeErr = validateErr
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !portRangeOk {
|
if !portRangeOk {
|
||||||
fail++
|
fail++
|
||||||
@@ -2732,7 +2743,11 @@ func (h *Handler) prepareTunnelCreateState(tx *gorm.DB, req map[string]interface
|
|||||||
}
|
}
|
||||||
if !isRemote {
|
if !isRemote {
|
||||||
var err error
|
var err error
|
||||||
|
if excludeTunnelID > 0 {
|
||||||
port, err = h.repo.PickNodePortTx(tx, nodeID, allocated, excludeTunnelID)
|
port, err = h.repo.PickNodePortTx(tx, nodeID, allocated, excludeTunnelID)
|
||||||
|
} else {
|
||||||
|
port, err = h.repo.PickRandomNodePortTx(tx, nodeID, allocated, excludeTunnelID)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -2767,7 +2782,11 @@ func (h *Handler) prepareTunnelCreateState(tx *gorm.DB, req map[string]interface
|
|||||||
}
|
}
|
||||||
if !isRemote {
|
if !isRemote {
|
||||||
var err error
|
var err error
|
||||||
|
if excludeTunnelID > 0 {
|
||||||
port, err = h.repo.PickNodePortTx(tx, nodeID, allocated, excludeTunnelID)
|
port, err = h.repo.PickNodePortTx(tx, nodeID, allocated, excludeTunnelID)
|
||||||
|
} else {
|
||||||
|
port, err = h.repo.PickRandomNodePortTx(tx, nodeID, allocated, excludeTunnelID)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -3641,7 +3660,7 @@ func (h *Handler) replaceTunnelChainsTx(tx *gorm.DB, tunnelID int64, req map[str
|
|||||||
port := asInt(n["port"], 0)
|
port := asInt(n["port"], 0)
|
||||||
if port <= 0 {
|
if port <= 0 {
|
||||||
var pickErr error
|
var pickErr error
|
||||||
port, pickErr = h.repo.PickNodePortTx(tx, nodeID, allocated, 0)
|
port, pickErr = h.repo.PickRandomNodePortTx(tx, nodeID, allocated, 0)
|
||||||
if pickErr != nil {
|
if pickErr != nil {
|
||||||
return pickErr
|
return pickErr
|
||||||
}
|
}
|
||||||
@@ -3671,7 +3690,7 @@ func (h *Handler) replaceTunnelChainsTx(tx *gorm.DB, tunnelID int64, req map[str
|
|||||||
port := asInt(n["port"], 0)
|
port := asInt(n["port"], 0)
|
||||||
if port <= 0 {
|
if port <= 0 {
|
||||||
var pickErr error
|
var pickErr error
|
||||||
port, pickErr = h.repo.PickNodePortTx(tx, nodeID, allocated, 0)
|
port, pickErr = h.repo.PickRandomNodePortTx(tx, nodeID, allocated, 0)
|
||||||
if pickErr != nil {
|
if pickErr != nil {
|
||||||
return pickErr
|
return pickErr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -453,6 +455,14 @@ func (r *Repository) IsRemoteNodeTx(tx *gorm.DB, nodeID int64) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) PickNodePortTx(tx *gorm.DB, nodeID int64, allocated map[int64]int, excludeTunnelID int64) (int, error) {
|
func (r *Repository) PickNodePortTx(tx *gorm.DB, nodeID int64, allocated map[int64]int, excludeTunnelID int64) (int, error) {
|
||||||
|
return r.pickNodePortTx(tx, nodeID, allocated, excludeTunnelID, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) PickRandomNodePortTx(tx *gorm.DB, nodeID int64, allocated map[int64]int, excludeTunnelID int64) (int, error) {
|
||||||
|
return r.pickNodePortTx(tx, nodeID, allocated, excludeTunnelID, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) pickNodePortTx(tx *gorm.DB, nodeID int64, allocated map[int64]int, excludeTunnelID int64, randomPick bool) (int, error) {
|
||||||
if tx == nil {
|
if tx == nil {
|
||||||
return 0, errors.New("database unavailable")
|
return 0, errors.New("database unavailable")
|
||||||
}
|
}
|
||||||
@@ -505,6 +515,7 @@ func (r *Repository) PickNodePortTx(tx *gorm.DB, nodeID int64, allocated map[int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var available []int
|
||||||
for _, candidate := range candidates {
|
for _, candidate := range candidates {
|
||||||
if candidate <= 0 {
|
if candidate <= 0 {
|
||||||
continue
|
continue
|
||||||
@@ -512,12 +523,26 @@ func (r *Repository) PickNodePortTx(tx *gorm.DB, nodeID int64, allocated map[int
|
|||||||
if _, ok := used[candidate]; ok {
|
if _, ok := used[candidate]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
allocated[nodeID] = candidate
|
available = append(available, candidate)
|
||||||
return candidate, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(available) == 0 {
|
||||||
return 0, errors.New("节点端口已满,无可用端口")
|
return 0, errors.New("节点端口已满,无可用端口")
|
||||||
}
|
}
|
||||||
|
if !randomPick {
|
||||||
|
allocated[nodeID] = available[0]
|
||||||
|
return available[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(available))))
|
||||||
|
if err != nil {
|
||||||
|
allocated[nodeID] = available[0]
|
||||||
|
return available[0], nil
|
||||||
|
}
|
||||||
|
port := available[idx.Int64()]
|
||||||
|
allocated[nodeID] = port
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Repository) GetTunnelIPPreference(tunnelID int64) string {
|
func (r *Repository) GetTunnelIPPreference(tunnelID int64) string {
|
||||||
if r == nil || r.db == nil {
|
if r == nil || r.db == nil {
|
||||||
|
|||||||
@@ -244,6 +244,21 @@ func TestForwardCreateAllowedWhenBelowUserNumLimit(t *testing.T) {
|
|||||||
t.Fatalf("insert tunnel: %v", err)
|
t.Fatalf("insert tunnel: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := repo.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('num-ok-entry', 'num-ok-secret', '10.50.0.1', '10.50.0.1', '', '10000-10010', '', 'v1', 1, 1, 1, ?, ?, 1, '[::]', '[::]', 0)
|
||||||
|
`, now, now).Error; err != nil {
|
||||||
|
t.Fatalf("insert entry node: %v", err)
|
||||||
|
}
|
||||||
|
entryNodeID := mustLastInsertID(t, repo, "num-ok-entry")
|
||||||
|
|
||||||
|
if err := repo.DB().Exec(`
|
||||||
|
INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol)
|
||||||
|
VALUES(?, 1, ?, 10001, 'round', 1, 'tls')
|
||||||
|
`, tunnelID, entryNodeID).Error; err != nil {
|
||||||
|
t.Fatalf("insert chain_tunnel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := repo.DB().Exec(`
|
if err := repo.DB().Exec(`
|
||||||
INSERT INTO user_tunnel(id, user_id, tunnel_id, speed_id, num, flow, in_flow, out_flow, flow_reset_time, exp_time, status)
|
INSERT INTO user_tunnel(id, user_id, tunnel_id, speed_id, num, flow, in_flow, out_flow, flow_reset_time, exp_time, status)
|
||||||
VALUES(10, ?, ?, NULL, 99999, 99999, 0, 0, 1, 2727251700000, 1)
|
VALUES(10, ?, ?, NULL, 99999, 99999, 0, 0, 1, 2727251700000, 1)
|
||||||
@@ -302,6 +317,21 @@ func TestForwardCreateAllowedWhenNumZero(t *testing.T) {
|
|||||||
t.Fatalf("insert tunnel: %v", err)
|
t.Fatalf("insert tunnel: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := repo.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('num-zero-entry', 'num-zero-secret', '10.60.0.1', '10.60.0.1', '', '11000-11010', '', 'v1', 1, 1, 1, ?, ?, 1, '[::]', '[::]', 0)
|
||||||
|
`, now, now).Error; err != nil {
|
||||||
|
t.Fatalf("insert entry node: %v", err)
|
||||||
|
}
|
||||||
|
entryNodeID := mustLastInsertID(t, repo, "num-zero-entry")
|
||||||
|
|
||||||
|
if err := repo.DB().Exec(`
|
||||||
|
INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol)
|
||||||
|
VALUES(?, 1, ?, 11001, 'round', 1, 'tls')
|
||||||
|
`, tunnelID, entryNodeID).Error; err != nil {
|
||||||
|
t.Fatalf("insert chain_tunnel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := repo.DB().Exec(`
|
if err := repo.DB().Exec(`
|
||||||
INSERT INTO user_tunnel(id, user_id, tunnel_id, speed_id, num, flow, in_flow, out_flow, flow_reset_time, exp_time, status)
|
INSERT INTO user_tunnel(id, user_id, tunnel_id, speed_id, num, flow, in_flow, out_flow, flow_reset_time, exp_time, status)
|
||||||
VALUES(10, ?, ?, NULL, 0, 99999, 0, 0, 1, 2727251700000, 1)
|
VALUES(10, ?, ?, NULL, 0, 99999, 0, 0, 1, 2727251700000, 1)
|
||||||
|
|||||||
@@ -50,21 +50,18 @@ func TestTunnelCreateRuntimeRollbackContract(t *testing.T) {
|
|||||||
if err := json.NewDecoder(res.Body).Decode(&out); err != nil {
|
if err := json.NewDecoder(res.Body).Decode(&out); err != nil {
|
||||||
t.Fatalf("decode response: %v", err)
|
t.Fatalf("decode response: %v", err)
|
||||||
}
|
}
|
||||||
if out.Code == 0 {
|
if out.Code != 0 {
|
||||||
t.Fatalf("expected create failure when nodes are offline")
|
t.Fatalf("expected create success (runtime deferred when nodes offline), got code=%d msg=%q", out.Code, out.Msg)
|
||||||
}
|
|
||||||
if !strings.Contains(out.Msg, "节点") {
|
|
||||||
t.Fatalf("expected node-related error, got %q", out.Msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelCount := mustQueryInt(t, repo, `SELECT COUNT(1) FROM tunnel WHERE name = ?`, "runtime-rollback-tunnel")
|
tunnelCount := mustQueryInt(t, repo, `SELECT COUNT(1) FROM tunnel WHERE name = ?`, "runtime-rollback-tunnel")
|
||||||
if tunnelCount != 0 {
|
if tunnelCount != 1 {
|
||||||
t.Fatalf("expected tunnel rollback, found %d records", tunnelCount)
|
t.Fatalf("expected tunnel record preserved (runtime deferred), found %d records", tunnelCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
chainCount := mustQueryInt(t, repo, `SELECT COUNT(1) FROM chain_tunnel`)
|
chainCount := mustQueryInt(t, repo, `SELECT COUNT(1) FROM chain_tunnel`)
|
||||||
if chainCount != 0 {
|
if chainCount != 3 {
|
||||||
t.Fatalf("expected chain_tunnel rollback, found %d records", chainCount)
|
t.Fatalf("expected 3 chain_tunnel records preserved (in/chain/out), found %d records", chainCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user