mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
179 lines
5.3 KiB
Markdown
179 lines
5.3 KiB
Markdown
# Proxy Protocol 传输分析报告
|
|
|
|
**日期**: 2026-05-07
|
|
**测试环境**: 20.118.172.127 (Server 1) ↔ 108.181.90.137 (Server 2)
|
|
|
|
---
|
|
|
|
## 1. 代码流程分析
|
|
|
|
### 完整数据链路
|
|
|
|
```
|
|
前端 (proxyProtocol: 0|1|2)
|
|
→ 后端 handler mutations.go:1936
|
|
→ 数据库存储 forward.proxy_protocol (model.go:50)
|
|
→ 控制面 buildForwardServiceConfigs (control_plane.go:1791-1796)
|
|
→ handler metadata: {"proxyProtocol": 2}
|
|
→ Agent metadata 解析 (metadata.go:42)
|
|
→ handler.go:256 WrapClientConn()
|
|
→ conn.go:14 HeaderProxyFromAddrs(byte(ppv), src, dst)
|
|
→ conn.go:15 header.WriteTo(c)
|
|
→ 目标服务器收到 PROXY protocol header
|
|
```
|
|
|
|
### 关键代码
|
|
|
|
**写入 PROXY header** (`go-gost/x/internal/net/proxyproto/conn.go`):
|
|
```go
|
|
func WrapClientConn(ppv int, src, dst net.Addr, c net.Conn) net.Conn {
|
|
if ppv <= 0 {
|
|
return c
|
|
}
|
|
header := proxyproto.HeaderProxyFromrs(byte(ppv), src, dst)
|
|
header.WriteTo(c)
|
|
return c
|
|
}
|
|
```
|
|
|
|
**Handler 调用** (`go-gost/x/handler/forward/local/handler.go:256`):
|
|
```go
|
|
cc = proxyproto.WrapClientConn(h.md.proxyProtocol, conn.RemoteAddr(), conn.LocalAddr(), cc)
|
|
```
|
|
|
|
- `src` = `conn.RemoteAddr()` → 客户端真实 IP ✅
|
|
- `dst` = `conn.LocalAddr()` → agent 监听地址 ✅
|
|
- `ppv` = 1 或 2 → 版本号正确 ✅
|
|
|
|
---
|
|
|
|
## 2. 实际传输测试结果
|
|
|
|
### 测试方法
|
|
|
|
1. 在 Server 2 启动 Python TCP 监听器,解析 PROXY protocol header
|
|
2. 在 Server 1 用当前代码编译 gost,配置 `proxyProtocol: 2` 转发到 Server 2
|
|
3. 通过 `nc` 发送测试数据,验证 Server 2 是否收到正确的 PROXY header
|
|
|
|
### 测试结果
|
|
|
|
| 版本 | 状态 | 接收到的 Header |
|
|
|------|------|----------------|
|
|
| **PPv2** | ✅ 成功 | `PP2 family=1 alen=12 SRC=127.0.0.1:45410 DST=127.0.0.1:20001` |
|
|
| **PPv1** | ✅ 成功 | `PROXY TCP4 127.0.0.1 127.0.0.1 43816 20001` |
|
|
|
|
### 测试详情
|
|
|
|
**PPv2 原始数据**:
|
|
```
|
|
Got 28 bytes
|
|
PP2 family=1 alen=12
|
|
SRC=127.0.0.1:45410 DST=127.0.0.1:20001
|
|
```
|
|
|
|
**PPv1 原始数据** (hex):
|
|
```
|
|
50524f58592054435034203132372e302e302e31203132372e302e302e312034333831362032303030310d0a
|
|
```
|
|
解码: `PROXY TCP4 127.0.0.1 127.0.0.1 43816 20001`
|
|
|
|
---
|
|
|
|
## 3. 单元测试结果
|
|
|
|
```
|
|
go-gost/x/handler/forward/local/ → TestLocalForwardHandlerSendsProxyProtocolToTarget ✅
|
|
go-backend/internal/http/handler/ → TestBuildForwardServiceConfigsSendsProxyProtocolToForwardHandler ✅
|
|
go-backend/internal/store/repo/ → TestGetForwardRecordIncludesProxyProtocol ✅
|
|
```
|
|
|
|
全部通过 (3/3)。
|
|
|
|
---
|
|
|
|
## 4. 发现的问题
|
|
|
|
### 问题 1: `WriteTo` 错误未检查
|
|
|
|
**位置**: `go-gost/x/internal/net/proxyproto/conn.go:15`
|
|
|
|
```go
|
|
header.WriteTo(c) // 返回 (int64, error) 被忽略
|
|
```
|
|
|
|
**影响**: 如果写入失败(连接已断开、网络错误等),后续数据传输会在没有 PROXY header 的情况下继续,目标服务器可能解析出错。
|
|
|
|
**建议**:
|
|
```go
|
|
if _, err := header.WriteTo(c); err != nil {
|
|
return c // 或包装一个带错误的 conn
|
|
}
|
|
```
|
|
|
|
**严重程度**: 低(实际场景中,写入失败后 `Transport` 也会很快失败)
|
|
|
|
---
|
|
|
|
### 问题 2: 部署版本过旧
|
|
|
|
**服务器状态**:
|
|
|
|
| 服务器 | 组件 | 版本 | 状态 |
|
|
|--------|------|------|------|
|
|
| 20.118.172.127 | flux_agent | UPX 压缩,无法读取版本 | ✅ 运行中 |
|
|
| 20.118.172.127 | paneld | `/app/paneld` | ✅ 运行中 |
|
|
| 20.118.172.127 | /usr/local/bin/gost | v3.0.0 (go1.23.4) | 旧版,不支持 handler metadata 中的 proxyProtocol |
|
|
| 108.181.90.137 | flux_agent | 8.8MB | ✅ 运行中 |
|
|
|
|
**影响**: 旧版 gost 二进制不识别 handler metadata 中的 `proxyProtocol` 字段,PROXY protocol 功能在生产环境不可用。
|
|
|
|
**验证**: 用旧版 gost 测试时,目标服务器收到的原始数据为空,无 PROXY header。
|
|
|
|
---
|
|
|
|
### 问题 3: 数据库 Schema 缺失
|
|
|
|
**位置**: 20.118.172.127 的 `/app/data/gost.db`
|
|
|
|
**当前 forward 表 schema**:
|
|
```sql
|
|
CREATE TABLE `forward` (
|
|
`id` integer PRIMARY KEY AUTOINCREMENT,
|
|
`user_id` integer NOT NULL,
|
|
`user_name` varchar(100) NOT NULL,
|
|
`name` varchar(100) NOT NULL,
|
|
`tunnel_id` integer NOT NULL,
|
|
`remote_addr` text NOT NULL,
|
|
`strategy` varchar(100) NOT NULL DEFAULT "fifo",
|
|
`in_flow` integer NOT NULL DEFAULT 0,
|
|
`out_flow` integer NOT NULL DEFAULT 0,
|
|
`created_time` integer NOT NULL,
|
|
`updated_time` integer NOT NULL,
|
|
`status` integer NOT NULL,
|
|
`inx` integer NOT NULL DEFAULT 0,
|
|
`speed_id` integer
|
|
);
|
|
```
|
|
|
|
**缺失字段**:
|
|
- `proxy_protocol` — PROXY protocol 版本
|
|
- `max_conn` — 最大连接数
|
|
- `ip_max_conn` — 每 IP 最大连接数
|
|
- `ip_speed_id` — 每 IP 限速 ID
|
|
|
|
**影响**: 后端无法存储和读取 proxy_protocol 配置,前端设置不会生效。
|
|
|
|
---
|
|
|
|
## 5. 结论
|
|
|
|
| 维度 | 状态 | 说明 |
|
|
|------|------|------|
|
|
| **代码实现** | ✅ 正确 | 完整的写入链路,版本/地址正确 |
|
|
| **单元测试** | ✅ 通过 | 3/3 测试覆盖 handler、repo、控制面 |
|
|
| **实际传输 (新编译版)** | ✅ 成功 | PPv1 和 PPv2 均正确传输 |
|
|
| **实际传输 (部署版)** | ❌ 不工作 | 旧版不支持 handler metadata 中的 proxyProtocol |
|
|
| **数据库 Schema** | ❌ 缺字段 | 需要迁移添加 proxy_protocol 等列 |
|
|
|
|
**总结**: 代码实现正确,PROXY protocol 传输逻辑无误。但生产服务器运行的是旧版本,需要升级 backend 和 agent 才能启用此功能。
|