Files
flvx/.entire/metadata/ses_35212171dffd9vra00U17BWVxP/summary.txt
T
2026-03-03 14:19:18 +08:00

173 lines
5.2 KiB
Plaintext

根据我的分析,以下是隧道链路构建时下一跳连接地址选择流程的完整报告:
---
## 隧道链路构建流程分析报告
### 1. 隧道链路配置发送到节点的代码位置
**主函数**: `applyTunnelRuntime()`
- 文件: `go-backend/internal/http/handler/mutations.go`
- 行号: 2454-2521
**核心流程**:
```
applyTunnelRuntime (L2454)
├── 入口节点: 发送 AddChains 命令 (L2474)
├── 跳转节点: 发送 AddChains (L2496) + AddService (L2502) 命令
└── 出口节点: 发送 AddService 命令 (L2514)
```
**配置构建函数**:
| 函数 | 位置 | 作用 |
|------|------|------|
| `buildTunnelChainConfig()` | L2567-2624 | 构建转发链配置 |
| `buildTunnelChainServiceConfig()` | L2626-2652 | 构建服务配置 |
| `selectTunnelDialHost()` | L2654-2699 | **选择下一跳连接地址** |
---
### 2. 下一跳连接地址是如何确定的
**地址选择核心函数**: `selectTunnelDialHost()` (L2654-2699)
```go
// 关键逻辑 (简化版)
func selectTunnelDialHost(fromNode, toNode *nodeRecord, ipPreference string) (string, error) {
// 1. 检测源节点和目标节点的 IPv4/IPv6 支持
fromV4 := nodeSupportsV4(fromNode)
fromV6 := nodeSupportsV6(fromNode)
toV4 := nodeSupportsV4(toNode)
toV6 := nodeSupportsV6(toNode)
// 2. 根据偏好选择地址
switch ipPreference {
case "v6": // 优先 IPv6
if fromV6 && toV6 { return pickNodeAddressV6(toNode) }
if fromV4 && toV4 { return pickNodeAddressV4(toNode) }
case "v4": // 优先 IPv4
if fromV4 && toV4 { return pickNodeAddressV4(toNode) }
if fromV6 && toV6 { return pickNodeAddressV6(toNode) }
default: // 默认优先 IPv4
if fromV4 && toV4 { return pickNodeAddressV4(toNode) }
if fromV6 && toV6 { return pickNodeAddressV6(toNode) }
}
// 3. 不兼容时报错
return "", fmt.Errorf("节点链路不兼容")
}
```
**地址选择优先级** (在 `pickNodeAddressV4/V6` 中, L2755-2773):
- **IPv4**: `ServerIPv4` > `ServerIP` (旧字段)
- **IPv6**: `ServerIPv6` > `ServerIP` (旧字段)
**端口来源** (在 `buildTunnelChainConfig` 中, L2585-2588):
```go
port := target.Port // 来自 ChainTunnel.Port 字段
if port <= 0 {
return nil, errors.New("节点端口不能为空")
}
```
---
### 3. ChainTunnel.Port 字段的使用流程
**数据模型** (`model.go` L127-138):
```go
type ChainTunnel struct {
ID int64
TunnelID int64
ChainType string // "1"=入口, "2"=跳转, "3"=出口
NodeID int64
Port sql.NullInt64 // ★ 连接端口
Strategy sql.NullString
Inx sql.NullInt64 // 跳转序号
Protocol sql.NullString
}
```
**Port 字段流转**:
```
创建隧道时
│
▼
prepareTunnelCreateState (L1970)
├── 出口节点: 从请求读取 port,若为空则调用 PickNodePortTx 自动分配
├── 跳转节点: 从请求读取 port,若为空则调用 PickNodePortTx 自动分配
│
▼
replaceTunnelChainsTx (L2775)
└── CreateChainTunnelTx 写入数据库 (L2809, L2837)
│
▼
读取/重建状态时
│
▼
ListChainNodesForTunnel (repository_control.go L280)
└── 从 chain_tunnel 表读取 Port 字段 (L295)
│
▼
reconstructTunnelState (L845)
└── 将 Port 复制到 tunnelRuntimeNode.Port (L887, L901)
│
▼
buildTunnelChainConfig (L2567)
└── 使用 target.Port 构建连接地址 (L2585)
```
---
### 4. 修改连接地址需要改动的关键位置
如果要修改下一跳连接地址的选择逻辑,需要关注以下位置:
| 修改目标 | 文件 | 行号 | 函数 |
|---------|------|------|------|
| **IP地址选择策略** | `mutations.go` | L2654-2699 | `selectTunnelDialHost()` |
| **IPv4地址提取** | `mutations.go` | L2755-2763 | `pickNodeAddressV4()` |
| **IPv6地址提取** | `mutations.go` | L2765-2773 | `pickNodeAddressV6()` |
| **端口获取** | `mutations.go` | L2585-2588 | `buildTunnelChainConfig()` |
| **端口分配逻辑** | `repository_mutations.go` | ~L459 | `PickNodePortTx()` |
| **端口写入数据库** | `repository_mutations.go` | L398-406 | `CreateChainTunnelTx()` |
| **端口从数据库读取** | `repository_control.go` | L280-343 | `ListChainNodesForTunnel()` |
**关键配置结构**:
```go
// mutations.go L1950-1957
type tunnelRuntimeNode struct {
NodeID int64
Protocol string // 连接协议
Strategy string // 选择策略
Inx int // 跳转序号
ChainType int // 1=入口, 2=跳转, 3=出口
Port int // ★ 连接端口
}
```
---
### 5. 调用链总结
```
用户创建隧道请求
│
▼
tunnelAdd (mutations.go L450)
│
├── prepareTunnelCreateState() ──► 分配端口,构建 state
│
├── replaceTunnelChainsTx() ──► 写入 chain_tunnel 表
│
└── applyTunnelRuntime()
│
├── buildTunnelChainConfig()
│ │
│ ├── selectTunnelDialHost() ──► 选择 IP 地址
│ │
│ └── 使用 target.Port ──► 端口
│
└── sendNodeCommand("AddChains/AddService") ──► 发送到节点
```