release: 2.1.9-rc8 (#380)

Release 2.1.9-rc8
This commit is contained in:
sagit
2026-03-24 09:46:39 +08:00
committed by GitHub
5 changed files with 112 additions and 50 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
**Generated:** Tue Mar 24 2026
**Commit:** 8ebde9d
**Branch:** main
**Tag:** 2.1.9-rc7
**Tag:** 2.1.9-rc8
## OVERVIEW
FLVX (formerly Flux Panel) is a traffic forwarding management system built on a forked GOST v3 stack. It ships as a Go-based admin API (SQLite/PostgreSQL) + Vite/React UI + Go forwarding agent, with optional mobile WebView wrappers.
+90 -29
View File
@@ -1102,50 +1102,111 @@ func (h *Handler) syncTunnelForwardsEntryPorts(tunnelID int64, entryNodeIDs []in
if err != nil {
continue
}
port := pickForwardPortFromRecords(oldPorts)
if port <= 0 {
referencePort := pickForwardPortFromRecords(oldPorts)
if referencePort <= 0 {
continue
}
// If the existing port is outside any entry node's allowed range,
// pick a new random port that satisfies all entry nodes.
if !h.isPortValidForAllEntryNodes(port, entryNodeIDs) {
newPort := h.pickTunnelPort(tunnelID)
if newPort > 0 {
port = newPort
// Build a map of existing node → port/inIP from old records.
oldPortByNode := make(map[int64]forwardPortRecord)
for _, fp := range oldPorts {
if fp.NodeID > 0 {
oldPortByNode[fp.NodeID] = fp
}
}
var entries []forwardPortReplaceEntry
if allowInIP {
entries = buildForwardPortEntriesWithPreservedInIP(entryNodeIDs, oldPorts, port)
} else {
entries = make([]forwardPortReplaceEntry, 0, len(entryNodeIDs))
for _, nid := range entryNodeIDs {
entries = append(entries, forwardPortReplaceEntry{NodeID: nid, Port: port, InIP: ""})
entries := make([]forwardPortReplaceEntry, 0, len(entryNodeIDs))
for _, nid := range entryNodeIDs {
if existing, ok := oldPortByNode[nid]; ok && existing.Port > 0 {
// Existing entry node: keep its current port.
inIP := existing.InIP
if !allowInIP {
inIP = ""
}
entries = append(entries, forwardPortReplaceEntry{NodeID: nid, Port: existing.Port, InIP: inIP})
continue
}
// New entry node: try to follow the reference port.
port := h.resolvePortForNewEntryNode(nid, referencePort, f.ID)
inIP := ""
if allowInIP {
// For single-entry tunnels, try to preserve inIP from old records.
for _, fp := range oldPorts {
if strings.TrimSpace(fp.InIP) != "" {
inIP = fp.InIP
break
}
}
}
entries = append(entries, forwardPortReplaceEntry{NodeID: nid, Port: port, InIP: inIP})
}
_ = h.repo.ReplaceForwardPorts(f.ID, entries)
}
}
func (h *Handler) isPortValidForAllEntryNodes(port int, entryNodeIDs []int64) bool {
if port <= 0 {
return false
// resolvePortForNewEntryNode determines the port for a forward on a newly added
// entry node. It tries to reuse referencePort (from existing entries); if that
// port is out of range or already occupied, it picks a random available port
// for this specific node.
func (h *Handler) resolvePortForNewEntryNode(nodeID int64, referencePort int, forwardID int64) int {
node, err := h.getNodeRecord(nodeID)
if err != nil {
return referencePort
}
for _, nodeID := range entryNodeIDs {
node, err := h.getNodeRecord(nodeID)
if err != nil {
continue
}
if validateLocalNodePort(node, port) != nil {
return false
}
if validateRemoteNodePort(node, port) != nil {
return false
// Check if referencePort is within the node's allowed range.
if validateLocalNodePort(node, referencePort) == nil &&
validateRemoteNodePort(node, referencePort) == nil {
// In range — check availability.
occupied, occErr := h.repo.HasOtherForwardOnNodePort(nodeID, referencePort, forwardID)
if occErr == nil && !occupied {
return referencePort
}
}
return true
// referencePort doesn't work for this node; pick a random one.
newPort := h.pickRandomPortForNode(nodeID)
if newPort > 0 {
return newPort
}
return referencePort // last resort fallback
}
// pickRandomPortForNode picks a random available port from a single node's
// port range, excluding ports already occupied by other forwards or chains.
func (h *Handler) pickRandomPortForNode(nodeID int64) int {
portRange, err := h.repo.GetNodePortRange(nodeID)
if err != nil {
return 0
}
if portRange == "" {
portRange = "1000-65535"
}
nodePorts, err := parsePorts(portRange)
if err != nil || len(nodePorts) == 0 {
return 0
}
used, err := h.getUsedPorts(nodeID)
if err != nil {
return 0
}
var available []int
for _, p := range nodePorts {
if !used[p] {
available = append(available, p)
}
}
if len(available) == 0 {
return 0
}
idx, _ := rand.Int(rand.Reader, big.NewInt(int64(len(available))))
return available[idx.Int64()]
}
func (h *Handler) tunnelDelete(w http.ResponseWriter, r *http.Request) {
+2 -3
View File
@@ -3348,7 +3348,7 @@ func (r *Repository) GetNodeMetrics(nodeID int64, startMs, endMs int64) ([]model
err := r.db.Model(&model.NodeMetric{}).
Select(
fmt.Sprintf(
"? AS node_id, "+
"%d AS node_id, "+
"CAST(%s AS INTEGER) AS timestamp, "+
"AVG(cpu_usage) AS cpu_usage, "+
"AVG(mem_usage) AS mem_usage, "+
@@ -3363,9 +3363,8 @@ func (r *Repository) GetNodeMetrics(nodeID int64, startMs, endMs int64) ([]model
"CAST(AVG(tcp_conns) AS INTEGER) AS tcp_conns, "+
"CAST(AVG(udp_conns) AS INTEGER) AS udp_conns, "+
"CAST(MAX(uptime) AS INTEGER) AS uptime",
bucketExpr,
nodeID, bucketExpr,
),
nodeID,
).
Where("node_id = ? AND timestamp >= ? AND timestamp <= ?", nodeID, startMs, endMs).
Group(groupExpr).
+14 -12
View File
@@ -2,18 +2,20 @@
## Issue
- GitHub Issue: [#373](https://github.com/Sagit-chu/flvx/issues/373)
- 添加隧道入口时,端口校验不严格 - 默认端口可能不在新入口设置范围内
## 问题分析
当已有隧道上添加新入口节点时,系统会使用既有转发的端口部署到新入口节点上,
但该端口可能不在新入口节点设置的端口范围内,且没有校验提示。
### 根因
`syncTunnelForwardsEntryPorts` 函数在同步入口端口时,直接复用了原有端口,
没有检查该端口是否在新入口节点的允许范围内。
## 修复方案
- [x] 1. 新增 `isPortValidForAllEntryNodes` 辅助方法,校验端口是否在各节点范围内
- [x] 2. 在 `syncTunnelForwardsEntryPorts` 中检测端口超范围时,通过 `pickTunnelPort` 自动随机分配新端口
- [x] 3. 构建通过 + 全量测试通过
在 `syncTunnelForwardsEntryPorts` 中实现逐节点端口分配:
- **旧入口节点**:保留原端口不变
- **新入口节点**:通过 `resolvePortForNewEntryNode` 决策:
- 参考端口在范围内且未被占用 → 跟随设置一样的端口
- 参考端口超出范围或被占用 → 通过 `pickRandomPortForNode` 为该节点单独随机分配
## 任务清单
- [x] 1. 实现 `pickRandomPortForNode` 辅助方法(单节点端口随机分配)
- [x] 2. 实现 `resolvePortForNewEntryNode` 方法(端口决策逻辑)
- [x] 3. 重写 `syncTunnelForwardsEntryPorts` 为逐节点分配
- [x] 4. 移除不再需要的 `isPortValidForAllEntryNodes`
- [x] 5. 构建通过 + 全量测试通过
+5 -5
View File
@@ -5,8 +5,8 @@ Sync all changes, bump version to `2.1.9-rc7`, create PR, merge, and publish tag
## Tasks
- [x] Update `AGENTS.md` with new tag (`2.1.9-rc7`) and today's date (`Tue Mar 24 2026`).
- [ ] Commit all changes to branch `chore/rc7-bump`.
- [ ] Push branch to remote.
- [ ] Create Pull Request using `gh`.
- [ ] Merge Pull Request using `gh`.
- [ ] Create and push tag `2.1.9-rc7`.
- [x] Commit all changes to branch `chore/rc7-bump`.
- [x] Push branch to remote.
- [x] Create Pull Request using `gh`.
- [x] Merge Pull Request using `gh`.
- [x] Create and push tag `2.1.9-rc7`.