Merge remote-tracking branch 'origin/main' into opencode/shiny-falcon

# Conflicts:
#	vite-frontend/src/pages/node.tsx
#	vite-frontend/src/pages/user.tsx
This commit is contained in:
sagitchu
2026-03-17 15:18:25 +08:00
175 changed files with 2403 additions and 168577 deletions
+33
View File
@@ -0,0 +1,33 @@
# 037 Tunnel Chain Failover Repair
## Checklist
- [x] Analyze middle-hop primary/backup failover across backend runtime generation and agent route selection.
- [x] Add regression coverage for a tunnel relay chain where a same-hop `fifo` primary is down and the backup must take over.
- [x] Update tunnel runtime generation so chain services retry route selection when the next hop has multiple candidates.
- [x] Harden agent-side chain failover if backend-configured retries alone does not cover all relay/chain paths.
- N/A: Router retry loop (`go-gost/x/chain/router.go:91`) rebuilds route on each iteration, so FailFilter applies to failed nodes.
- [x] Revalidate diagnosis output so tunnel/forward tests reflect failover behavior instead of looking fully broken.
- N/A: Diagnosis tests individual legs (A→next, B→next) which is correct. Failover is for actual traffic, not diagnosis.
- [x] Run targeted backend and agent test suites.
## Findings
- Backend already emits hop selectors for tunnel chains with `strategy`, `maxFails=1`, and `failTimeout=10m` in `go-backend/internal/http/handler/mutations.go:3243`, so the control plane is not dropping the primary/backup mode itself.
- Agent route construction selects one node per hop up front in `go-gost/x/chain/chain.go:92`. If the chosen primary node is offline, the dial fails inside `go-gost/x/chain/route.go:220` and the node gets marked failed, but that mark only matters on a later route build.
- Tunnel chain services are generated without handler retry settings in `go-backend/internal/http/handler/mutations.go:3274`, while the router only rebuilds a route when `cfg.Handler.Retries` is greater than zero in `go-gost/x/config/parsing/service/parse.go:319`.
- Because the default retry count is effectively one attempt, a relay request never gets a second route selection after the primary middle-hop node is marked down, so traffic does not switch to the backup node.
- The forward handlers already have explicit retry/exclude-node loops in `go-gost/x/handler/forward/local/handler.go:179` and `go-gost/x/handler/forward/remote/handler.go:207`, which explains why failover logic exists in the codebase but is missing on the tunnel relay chain path.
## Repair Direction
- In backend tunnel runtime generation, compute the downstream candidate count for each chain service and set handler `retries` to at least `len(nextTargets) - 1` when a hop has multiple selectable nodes. That gives the router another dial cycle so `FailFilter` can skip the failed primary and pick the backup.
- Keep the retry value scoped to tunnel relay services built from `buildTunnelChainServiceConfig` so single-node hops do not incur unnecessary extra attempts.
- Add an agent-side regression test around relay + chain routing that simulates an offline primary node and asserts the second attempt lands on the backup node after the first node is marked failed.
- Add a backend regression test covering a tunnel definition with two nodes on the same middle hop in `fifo` mode, verifying the generated service config carries the retry budget needed for failover.
- Recheck tunnel/forward diagnosis behavior after the runtime fix. The current diagnosis model probes individual branch legs, so it may need an aggregated result or clearer messaging to avoid reading a partial branch failure as total failover failure.
## Validation
- `cd go-backend && go test ./internal/http/handler/... ./tests/contract/...`
- `cd go-gost/x && go test ./chain/... ./handler/relay/... ./config/parsing/service/...`
@@ -0,0 +1,28 @@
# 038 Federation Middle-Hop Retry Parity
## Checklist
- [x] Reproduce and document the parity gap between local tunnel middle-hop runtime generation and federation-applied middle roles.
- [x] Update federation runtime apply logic so remote middle-hop services set handler `retries` when the next hop has multiple candidates.
- [x] Add regression coverage for federated middle-hop runtime generation or contract behavior, including multi-target `fifo` scenarios.
- [x] Verify release / cleanup paths remain correct when the federated middle service carries retry settings.
- [x] Run targeted backend tests for handler and federation contract coverage.
## Findings
- Local tunnel runtime generation now sets `handler.retries` for middle-hop services based on downstream candidate count in `go-backend/internal/http/handler/mutations.go`, which enables router-level re-selection after a failed primary node.
- Federation runtime apply still creates remote middle-hop services without `handler.retries` in `go-backend/internal/http/handler/federation.go`, even though the remote chain hop itself uses the same selector failover settings (`strategy`, `maxFails=1`, `failTimeout=10m`).
- Because `go-gost/x/config/parsing/service/parse.go` only enables router retries when `cfg.Handler.Retries > 0`, federated middle-hop services can still fail hard on the first offline primary target instead of switching to backup.
- The gap creates inconsistent behavior: identical tunnel topologies can fail over correctly on local middle nodes but not on federated / remote middle nodes.
## Repair Direction
- In `go-backend/internal/http/handler/federation.go`, compute retry budget for `req.Role == "middle"` from `len(req.Targets)` and set `service["handler"]["retries"]` to at least `len(req.Targets) - 1` when there is more than one target.
- Keep retry injection scoped to federated middle roles only; exit roles should continue to omit retries because they do not rebuild downstream chain selection.
- Add regression coverage that proves federated middle runtime application preserves local parity, ideally by asserting the generated remote service config or by exercising a dual-panel contract path with multi-target middle nodes.
- Recheck federation release behavior to ensure added retry fields do not affect idempotent cleanup, service deletion, or re-apply flows.
## Validation
- `cd go-backend && go test ./internal/http/handler/... -count=1`
- `cd go-backend && go test ./tests/contract/... -count=1`
@@ -0,0 +1,62 @@
# 恢复 PR #322 移除的功能
**状态**: ✅ 已完成
## 背景
PR #322 (https://github.com/Sagit-chu/flvx/pull/322) 原本移除了三个功能,用户要求**加回**这些被移除的功能:
1. 批量操作失败详情弹窗(`BatchOperationFailure` 类型及相关处理)
2. 节点到期提醒关闭功能(`dismissNodeExpiryReminder` API)
3. 更新通道选择功能(稳定版/开发版切换)
用户要求**保留**的改动:
- 版本显示简化(移除 "v" 前缀和更新可用徽章)
## 任务清单
- [x] 检出 PR #322 到本地分支 `pr-322`
- [x] 恢复 `api/types.ts` 中的 `expiryReminderDismissed` 字段
- [x] 恢复 `api/types.ts` 中的 `BatchOperationFailure` 类型和 `failures` 字段
- [x] 恢复 `api/error-message.ts` 中的批量操作失败处理函数
- [x] 恢复 `api/index.ts` 中的 `dismissNodeExpiryReminder` API
- [x] 恢复 `config.tsx` 中的更新通道选择功能
- [x] 恢复 `use-dashboard-data.ts` 中的 `expiryReminderDismissed` 过滤逻辑
- [x] 恢复 `batch-actions.ts` 中的 `BatchOperationFailure` 相关处理
- [x] 恢复 `forward.tsx` 中的 `BatchActionResultModal` 使用
- [x] 恢复 `tunnel.tsx` 中的 `BatchActionResultModal` 使用
- [x] 提交并推送修改
## 修改的文件
- `vite-frontend/src/api/types.ts` - 添加 `expiryReminderDismissed` 和 `BatchOperationFailure`
- `vite-frontend/src/api/error-message.ts` - 添加批量操作失败处理函数
- `vite-frontend/src/api/index.ts` - 添加 `dismissNodeExpiryReminder` API
- `vite-frontend/src/pages/config.tsx` - 添加更新通道选择功能
- `vite-frontend/src/pages/dashboard/use-dashboard-data.ts` - 恢复 `expiryReminderDismissed` 过滤逻辑
- `vite-frontend/src/pages/forward/batch-actions.ts` - 恢复批量操作失败处理
- `vite-frontend/src/pages/forward.tsx` - 恢复 `BatchActionResultModal` 组件使用
- `vite-frontend/src/pages/tunnel.tsx` - 恢复 `BatchActionResultModal` 组件使用
- `vite-frontend/src/pages/node.tsx` - 恢复 `expiryReminderDismissed` 功能和 "关闭提醒" 按钮
## 保留的 UI 改进
- Modal 样式优化(group.tsx, limit.tsx, panel-sharing.tsx)
- 按钮文本简化
- 用户页面隧道列表下拉展开
## forward.tsx 重构审查结果
PR #322 对 forward.tsx 进行了大规模重构(~2200 行 diff),经审查决定**保留**以下改动:
| 改动 | 说明 |
|------|------|
| DnD 碰撞检测 | `closestCenter` → `pointerWithin`,更适合嵌套拖拽 |
| 高级筛选模态框 | 从 SearchBar 改为五合一筛选(名称/用户/隧道/端口/目标地址) |
| 始终显示复选框 | 移除 selectMode 状态,用户无需切换模式即可选择 |
| 组件位置移动 | Sortable 组件移到组件顶部,代码组织更好 |
| UI 改进 | 表头全选、端口独立列、倍率显示优化、Modal 样式、"落地地址"文案 |
## 注意事项
- `version-footer.tsx` 保持简化版本显示(不恢复)
- `batch-action-result-modal.tsx` 组件文件未被 PR 删除,无需恢复(只需恢复 forward.tsx 中的使用)
+178
View File
@@ -0,0 +1,178 @@
# GOST UDP vs Realm UDP Analysis
**Goal:** Compare GOST UDP forwarding through tunnels with Realm's UDP implementation to understand why users report GOST UDP forwarding has problems while Realm works correctly.
## Task Checklist
- [x] Analyze GOST UDP tunnel architecture
- [x] Analyze Realm UDP relay architecture
- [x] Identify architectural differences
- [x] Identify potential issues in GOST implementation
- [ ] Document findings and recommendations
---
## GOST UDP Architecture
### Core Components
1. **UDP Relay** (`x/internal/net/udp/relay.go`)
- Simple bidirectional packet copying between two `net.PacketConn` interfaces
- Uses two goroutines: one for each direction
- Sequential packet processing (no batching)
- No idle timeout or association tracking
2. **UDP over Tunnel** (`x/handler/relay/bind.go`, `x/handler/socks/v5/udp_tun.go`)
- Wraps UDP data with SOCKS5-style framing via `UDPTunServerConn()`
- Adds address headers to each packet
- Uses smux multiplexing for tunnel connections
3. **SOCKS5 UDP Framing** (`x/internal/util/socks/conn.go`, `x/internal/util/relay/conn.go`)
- `udpTunConn.ReadFrom()`: Parses SOCKS5 UDP header to extract target address
- `udpTunConn.WriteTo()`: Wraps data with SOCKS5 UDP header including:
- RSV (data length, 2 bytes)
- Frag (0xff for tunnel relay, 1 byte)
- Address (4/16/1+n bytes for IPv4/IPv6/domain)
4. **Multiplexing** (`x/internal/util/mux/mux.go`)
- Uses smux v1.5.31 for connection multiplexing
- Adds stream framing and flow control overhead
### Data Flow (UDP over Tunnel)
```
Client UDP packet
↓
[Local GOST] SOCKS5 UDP framing (add ~10-26 bytes)
↓
smux stream (add framing, flow control)
↓
TCP tunnel to remote
↓
[Remote GOST] smux demux
↓
SOCKS5 UDP deframing
↓
Forward to target
```
---
## Realm UDP Architecture
### Core Components
1. **Association Model** (`realm_core/src/udp/middle.rs`)
- Per-client socket associations stored in `SockMap`
- Creates dedicated remote socket per client address
- Spawns `send_back` task for return path
- Association timeout for cleanup
2. **Batched I/O** (`realm_core/src/udp/batched.rs`)
- Uses `recvmmsg/sendmmsg` on Linux
- Up to 128 packets per batch
- Significantly higher throughput for high-PPS traffic
3. **No Protocol Overhead**
- Plain UDP forwarding without adding protocol headers
- No SOCKS5 framing or mux layer
### Data Flow
```
Client UDP packet
↓
[Realm] Direct relay via association socket
↓
Target server
```
---
## Key Architectural Differences
| Aspect | GOST | Realm |
|--------|------|-------|
| **UDP over Tunnel** | SOCKS5 framing + smux multiplexing | N/A (direct relay only) |
| **Protocol Overhead** | Extra ~10-26 bytes per packet | None |
| **I/O Model** | Standard Go net.PacketConn | Batched I/O (recvmmsg/sendmmsg) |
| **Connection Tracking** | Via mux session | SockMap with timeout |
| **Association Model** | None (bidirectional copy only) | Per-client socket association |
| **Idle Timeout** | None on UDP relay | Association timeout |
| **Throughput** | Single packet per syscall | Up to 128 packets per syscall |
---
## Identified Potential Issues in GOST
### 1. No Batch I/O Support
- **Impact**: High PPS (packets per second) traffic incurs syscall overhead
- **Realm advantage**: `recvmmsg/sendmmsg` batches up to 128 packets
- **Evidence**: Realm's `batched.rs` implements this; GOST uses standard `ReadFrom/WriteTo`
### 2. Protocol Overhead Per Packet
- **Impact**: Bandwidth waste, extra processing for framing/deframing
- **Overhead**: SOCKS5 UDP header adds ~10 bytes (IPv4) to ~26 bytes (IPv6) per packet
- **Evidence**: `x/internal/util/socks/conn.go:63-78` shows framing overhead
### 3. Mux Layer Overhead
- **Impact**: Latency and throughput degradation
- **smux adds**: Stream framing, flow control, potential backpressure
- **Evidence**: `x/internal/util/mux/mux.go` wraps all tunnel connections
### 4. No Association Tracking
- **Impact**: Cannot properly handle NAT translation for return traffic
- **Realm approach**: `SockMap` tracks client→remote socket mapping
- **Evidence**: GOST's `udp.Relay` just copies packets bidirectionally
### 5. No Idle Timeout on UDP Relay
- **Impact**: Stale connections may persist indefinitely
- **Evidence**: `udp.Relay.Run()` blocks until error or context cancel
- **Contrast**: Realm has association timeout for cleanup
### 6. Sequential Packet Processing
- **Impact**: Cannot pipeline multiple packets
- **Evidence**: `relay.go:44-77` shows sequential `ReadFrom` → `WriteTo` loop
- **Contrast**: Realm's batched I/O handles multiple packets concurrently
### 7. Read Deadline on Underlying TCP
- **Impact**: Could cause unexpected connection termination
- **Default**: 15-second read timeout set on connections
- **Evidence**: `x/handler/relay/metadata.go:readTimeout` defaults to 15s
- **Issue**: UDP relay may not handle deadline properly
---
## Recommendations
1. **Consider Direct UDP Mode**: For scenarios where tunnel is not required, use direct UDP relay (no SOCKS5 framing)
2. **Add Association Tracking**: Implement client→remote socket mapping with timeout
3. **Investigate Batch I/O**: Consider using `recvmmsg/sendmmsg` equivalent in Go (via `x/net` or raw syscalls)
4. **Add Idle Timeout**: Implement timeout-based cleanup for UDP associations
5. **Reduce Framing Overhead**: Consider more compact framing for tunnel UDP
---
## Files Analyzed
### GOST Files
- `go-gost/x/internal/net/udp/relay.go` - Core UDP relay logic
- `go-gost/x/internal/util/mux/mux.go` - smux multiplexing
- `go-gost/x/internal/util/socks/conn.go` - SOCKS5 UDP framing
- `go-gost/x/internal/util/relay/conn.go` - Relay UDP framing
- `go-gost/x/handler/relay/bind.go` - Relay BIND handler
- `go-gost/x/handler/socks/v5/udp_tun.go` - SOCKS5 UDP tunnel handler
- `go-gost/x/handler/tunnel/bind.go` - Tunnel BIND handler
- `go-gost/x/connector/tunnel/bind.go` - Tunnel connector BIND
- `go-gost/x/connector/tunnel/conn.go` - Tunnel connection types
- `go-gost/x/connector/tunnel/listener.go` - Tunnel bind listener
### Realm Files (fetched from GitHub)
- `realm_core/src/udp/mod.rs` - UDP relay entry point
- `realm_core/src/udp/middle.rs` - Association and relay logic with SockMap
- `realm_core/src/udp/socket.rs` - UDP socket binding and association
- `realm_core/src/udp/batched.rs` - Batched I/O implementation
+18
View File
@@ -0,0 +1,18 @@
# Node Card Info Popover
## Goal
Restore node card's remark and renewal info display to the 2.1.8-beta9 style: an info button (ℹ️) in the CardHeader that shows a hover popover with the info, instead of the current inline display in the CardBody.
## Status: Completed
PR #327 merged
## Tasks
- [x] Add `infoPopoverPlacement` state and `updateInfoPopoverPlacement` callback
- [x] Add info button with popover to CardHeader
- [x] Restore drag handle with touch support
- [x] Remove inline info display from CardBody
- [x] Fix build errors (JSX structure and unused variables)
- [x] Create PR and merge