|
| 1 | +# Mesh e2e 测试(基于 Docker)— 设计 Spec |
| 2 | + |
| 3 | +> 状态:等待用户 review |
| 4 | +> 日期:2026-07-08 |
| 5 | +> 范围:仅设计,不含实施 |
| 6 | +
|
| 7 | +## 1. 背景 |
| 8 | + |
| 9 | +当前 `tests/integration_test.go` 是单进程 Go 集成测试(HTTP API、SQLite、设备表),不验证: |
| 10 | + |
| 11 | +- 真实 TUN 设备的创建与路由注入 |
| 12 | +- 真实跨主机(容器间)包转发 |
| 13 | +- 性能指标(RTT、吞吐、丢包) |
| 14 | +- 链路恶化(延迟/抖动/丢包)下的行为 |
| 15 | +- server / client 故障下的重连与优雅退出 |
| 16 | + |
| 17 | +为补足这些维度,本 spec 设计一套基于 Docker 的 e2e 测试,输出可观测的指标与明确的 pass/fail。 |
| 18 | + |
| 19 | +## 2. 目标 |
| 20 | + |
| 21 | +- 验证 mesh VPN 的端到端正确性:TUN 路由、跨 client 转发、server 中继、设备上下线、ACME/自签证书下连通。 |
| 22 | +- 量化核心性能:RTT p50/p95/p99、TCP 吞吐、UDP 丢包率。 |
| 23 | +- 验证故障路径:server 重启、client 失联、SIGTERM 优雅退出。 |
| 24 | +- 提供 CI 可消费的 PASS/FAIL 报告。 |
| 25 | +- 与现有 `tests/integration_test.go` 解耦:后者负责快速集成验证,e2e 负责深度验证。 |
| 26 | + |
| 27 | +## 3. 非目标 |
| 28 | + |
| 29 | +- 不覆盖安全/加密协议的负面测试(无效 token 拒绝等)—— 留给单元测试。 |
| 30 | +- 不做 macOS host 上的 TUN e2e —— macOS Docker 不支持 utun,跨平台 TUN 验证统一在 Linux 容器内进行。 |
| 31 | +- 不做大规模(>50 client)负载测试 —— 本 spec 关注 2 client + 1 server 的核心场景。 |
| 32 | +- 不在 spec 内实现,只描述场景与接口。 |
| 33 | + |
| 34 | +## 4. 运行模型 |
| 35 | + |
| 36 | +### 4.1 容器拓扑 |
| 37 | + |
| 38 | +```yaml |
| 39 | +# tests/e2e/docker-compose.yml |
| 40 | +version: "3.9" |
| 41 | +services: |
| 42 | + server: |
| 43 | + build: { context: ../.., dockerfile: tests/e2e/Dockerfile.server } |
| 44 | + image: mesh-e2e/server:dev |
| 45 | + privileged: true |
| 46 | + cap_add: [NET_ADMIN, NET_RAW] |
| 47 | + networks: [meshnet] |
| 48 | + |
| 49 | + client-a: |
| 50 | + build: { context: ../.., dockerfile: tests/e2e/Dockerfile.client } |
| 51 | + image: mesh-e2e/client:dev |
| 52 | + privileged: true |
| 53 | + cap_add: [NET_ADMIN, NET_RAW] |
| 54 | + devices: ["/dev/net/tun:/dev/net/tun"] |
| 55 | + networks: [meshnet] |
| 56 | + depends_on: [server] |
| 57 | + |
| 58 | + client-b: |
| 59 | + 同 client-a |
| 60 | + depends_on: [server] |
| 61 | + |
| 62 | +networks: |
| 63 | + meshnet: |
| 64 | + driver: bridge |
| 65 | +``` |
| 66 | +
|
| 67 | +要点: |
| 68 | +
|
| 69 | +- 全部 `privileged: true` + `NET_ADMIN/RAW`,TUN + tc 才能用。 |
| 70 | +- 通过 Docker bridge 网络通信,模拟"跨主机"。 |
| 71 | +- server / client / iperf3/nuttcp 都跑在容器内,host 不需装额外工具。 |
| 72 | +- host 是 macOS / Linux / CI ubuntu-runner 都能跑同一份 compose。 |
| 73 | + |
| 74 | +### 4.2 镜像基线 |
| 75 | + |
| 76 | +- 基础镜像:`ubuntu:24.04`。 |
| 77 | +- server 镜像:`meshd` 二进制 + bash + curl + jq + ca-certificates。 |
| 78 | +- client 镜像:`mesh` 二进制 + bash + iperf3 + nuttcp + iputils-ping + fping + iproute2 + jq + ca-certificates。 |
| 79 | + |
| 80 | +### 4.3 TUN 处理 |
| 81 | + |
| 82 | +- Linux 容器用 `/dev/net/tun` 创建 `mesh0`,`internal/tun/tun_linux.go` 已支持,无需 build tag。 |
| 83 | +- macOS host 不直接 e2e,但代码路径保留(`tun_darwin.go`)。 |
| 84 | + |
| 85 | +## 5. 目录结构 |
| 86 | + |
| 87 | +```text |
| 88 | +tests/e2e/ |
| 89 | +├── docker-compose.yml |
| 90 | +├── Dockerfile.client |
| 91 | +├── Dockerfile.server |
| 92 | +├── run.sh # 一键启动 / 收尾 |
| 93 | +├── lib/ |
| 94 | +│ ├── helpers.sh # wait_ready / register_device / show_logs |
| 95 | +│ └── metrics.sh # 收集 RTT/吞吐/丢包 |
| 96 | +├── scenarios/ |
| 97 | +│ ├── 01-connectivity.sh # P0 |
| 98 | +│ ├── 02-performance.sh # P0 |
| 99 | +│ └── 03-failure.sh # P1 |
| 100 | +├── fixtures/ |
| 101 | +│ ├── meshd.yaml # server 配置 |
| 102 | +│ └── netem.sh # tc netem 封装 |
| 103 | +└── results/ |
| 104 | + └── <timestamp>/ # JSON + log 输出 |
| 105 | +``` |
| 106 | + |
| 107 | +驱动:bash + 简单 helper。 |
| 108 | +理由:直接调 iperf3 / tc / ss / curl,跨平台问题少,CI 友好。 |
| 109 | + |
| 110 | +## 6. 关键设计选择 |
| 111 | + |
| 112 | +### 6.1 TLS / 证书 |
| 113 | + |
| 114 | +- 容器内 server 拿不到 Let's Encrypt 证书。 |
| 115 | +- 引入 `MESH_TEST_TLS=off` 开关(推荐在 `internal/config` 落地): |
| 116 | + - server 跳过 `acme/autocert`,改用自签证书。 |
| 117 | + - client `mesh join` 端允许自签(`InsecureSkipVerify`,已在 `internal/client/peers.go` 使用过)。 |
| 118 | +- 退出测试时无需清理证书目录,容器销毁即可。 |
| 119 | + |
| 120 | +### 6.2 网络模拟(tc netem) |
| 121 | + |
| 122 | +`fixtures/netem.sh` 封装: |
| 123 | + |
| 124 | +```bash |
| 125 | +netem clean <iface> |
| 126 | +netem baseline <iface> # 0 干扰 |
| 127 | +netem wan <iface> # 80ms ± 10ms, 1% loss |
| 128 | +netem bad <iface> # 200ms ± 50ms, 5% loss |
| 129 | +netem satellite <iface> # 600ms ± 100ms, 2% loss |
| 130 | +``` |
| 131 | + |
| 132 | +### 6.3 性能工具 |
| 133 | + |
| 134 | +- `iperf3`:TCP 吞吐(`1 stream`、`4 stream`)、UDP 模式。 |
| 135 | +- `nuttcp`:备用,覆盖长流 + 小包。 |
| 136 | +- `ping -c 200 -i 0.01`:RTT 分布。 |
| 137 | +- `fping -p 20 -c 50`:并行 ping 抖动。 |
| 138 | +- `ss -ti` / `netstat -s`:重传统计。 |
| 139 | + |
| 140 | +### 6.4 mesh 启动流程 |
| 141 | + |
| 142 | +server 容器: |
| 143 | + |
| 144 | +```text |
| 145 | +1. /usr/local/bin/meshd init |
| 146 | +2. /usr/local/bin/meshd run |
| 147 | +3. 等 :443 可达 / `/api/devices` 200 |
| 148 | +``` |
| 149 | + |
| 150 | +client 容器: |
| 151 | + |
| 152 | +```text |
| 153 | +1. wait_for_server (curl https://server:443/api/devices) |
| 154 | +2. mesh join <server-domain> --token <tok> |
| 155 | +3. mesh up |
| 156 | +4. ip route show | grep 10.100.0.0/24 |
| 157 | +5. ping 10.100.0.1 |
| 158 | +``` |
| 159 | + |
| 160 | +### 6.5 失败注入 |
| 161 | + |
| 162 | +- server 容器:`docker compose kill server` / `docker compose restart server`。 |
| 163 | +- 链路:`tc qdisc change ... loss 50%`。 |
| 164 | +- client:`docker compose kill client-a`。 |
| 165 | + |
| 166 | +## 7. 场景拆分 |
| 167 | + |
| 168 | +### 7.1 场景 01:连通性与路由(P0) |
| 169 | + |
| 170 | +```text |
| 171 | +01.1 启动 server,等 /api/devices 可达 |
| 172 | +01.2 client-a join + up;client-b join + up |
| 173 | +01.3 ip route 校验:10.100.0.0/24 dev mesh0 |
| 174 | +01.4 ping 10.100.0.1(server)必须通 |
| 175 | +01.5 ping client-b(10.100.0.3)必须通 |
| 176 | +01.6 ping 不存在的 10.100.0.99 必须 100% 丢包 |
| 177 | +01.7 kill client-b;client-a ping 10.100.0.3 应超时 |
| 178 | +01.8 server route table 移除 |
| 179 | +01.9 restart client-b;重新 join;client-a ping 恢复 |
| 180 | +01.10 fping -p 20 -c 50 不丢 |
| 181 | +``` |
| 182 | + |
| 183 | +判定:每个 case 必须 100% 符合预期,任意 fail → 整体 fail。 |
| 184 | + |
| 185 | +### 7.2 场景 02:性能与抖动(P0) |
| 186 | + |
| 187 | +```text |
| 188 | +02.1 baseline iperf3 -c 10.100.0.3 -t 30 -P 1 |
| 189 | +02.2 iperf3 -c 10.100.0.3 -t 30 -P 4 |
| 190 | +02.3 iperf3 -u -b 100M -t 30(UDP 100Mbps) |
| 191 | +02.4 加 wan netem 后重测 02.1 / 02.3 |
| 192 | +02.5 加 bad netem 后重测 02.1 |
| 193 | +02.6 ping -c 200 -i 0.01 收集 RTT |
| 194 | +02.7 5 分钟长流,验证 Tx/Rx 计数不漂移 |
| 195 | +``` |
| 196 | + |
| 197 | +输出 `02-performance.json`: |
| 198 | + |
| 199 | +```json |
| 200 | +{ |
| 201 | + "tcp_1stream_mbps": 92.3, |
| 202 | + "tcp_4stream_mbps": 110.5, |
| 203 | + "udp_100m_loss_pct": 0.7, |
| 204 | + "wan_tcp_mbps": 78.4, |
| 205 | + "wan_udp_loss_pct": 1.4, |
| 206 | + "rtt_p50_ms": 81.2, |
| 207 | + "rtt_p95_ms": 95.0, |
| 208 | + "rtt_p99_ms": 110.4 |
| 209 | +} |
| 210 | +``` |
| 211 | + |
| 212 | +判定:**软门槛**默认只报告;`--strict` 触发硬门槛: |
| 213 | + |
| 214 | +```text |
| 215 | +rtt_p95_ms < 200 |
| 216 | +tcp_1stream_mbps > 30 |
| 217 | +wan_udp_loss_pct < 5 |
| 218 | +``` |
| 219 | + |
| 220 | +### 7.3 场景 03:故障 / 重连 / 优雅退出(P1) |
| 221 | + |
| 222 | +```text |
| 223 | +03.1 docker kill server;观察 client 错误日志;2-5s 内重连尝试 |
| 224 | +03.2 server restart;client 自动恢复 |
| 225 | +03.3 重建连接后 ping / iperf 复测 |
| 226 | +03.4 长流中短暂 server 中断,client 重建,iperf 重新建立 |
| 227 | +03.5 满队列抗压:iperf3 + tc 丢包 30%,观察 drop 计数与吞吐变化 |
| 228 | +03.6 SIGTERM client:必须 <=2s 退出,不留 zombie goroutine |
| 229 | +03.7 client 连续 join 10 次,server 必须正确处理 |
| 230 | +``` |
| 231 | + |
| 232 | +## 8. 结果收集与判定 |
| 233 | + |
| 234 | +```text |
| 235 | +results/ |
| 236 | +└── 2026-07-08T10-30-00/ |
| 237 | + ├── 01-connectivity.log |
| 238 | + ├── 01-connectivity.json |
| 239 | + ├── 02-performance.log |
| 240 | + ├── 02-performance.json |
| 241 | + ├── 03-failure.log |
| 242 | + ├── 03-failure.json |
| 243 | + └── summary.txt |
| 244 | +``` |
| 245 | + |
| 246 | +`summary.txt`: |
| 247 | + |
| 248 | +```text |
| 249 | +[P0] 01-connectivity: PASS (9/9) |
| 250 | +[P0] 02-performance: PASS (soft); tcp=92Mbps rtt_p95=95ms |
| 251 | +[P1] 03-failure: PASS (6/6) |
| 252 | +Overall: PASS |
| 253 | +``` |
| 254 | + |
| 255 | +## 9. CI 集成 |
| 256 | + |
| 257 | +GitHub Actions(在 `.github/workflows/` 下新增 `e2e.yml`): |
| 258 | + |
| 259 | +```yaml |
| 260 | +e2e: |
| 261 | + runs-on: ubuntu-latest |
| 262 | + steps: |
| 263 | + - uses: actions/checkout@v4 |
| 264 | + - run: docker compose -f tests/e2e/docker-compose.yml build |
| 265 | + - run: ./tests/e2e/run.sh --all |
| 266 | + - run: ./tests/e2e/run.sh --report | tee junit.xml |
| 267 | + - uses: actions/upload-artifact@v4 |
| 268 | + with: { name: e2e-report, path: tests/e2e/results/ } |
| 269 | +``` |
| 270 | +
|
| 271 | +- push 触发软门槛(默认)。 |
| 272 | +- merge to master 触发硬门槛(`--strict`)。 |
| 273 | +- release tag 触发全量 + 严格。 |
| 274 | + |
| 275 | +## 10. 与现有 `tests/integration_test.go` 的边界 |
| 276 | + |
| 277 | +```text |
| 278 | +tests/integration_test.go 单元 + HTTP API 集成(无 TUN / 无网络) |
| 279 | +tests/e2e/ 完整 e2e(容器 + TUN + tc + 性能) |
| 280 | +``` |
| 281 | + |
| 282 | +不重叠。前者跑得快(CI 默认每次 push),后者跑得慢(合并前 / release 前)。 |
| 283 | + |
| 284 | +## 11. 实施 TODO |
| 285 | + |
| 286 | +实施时建议拆为以下 TODO(落到 `docs/todo/testing/`): |
| 287 | + |
| 288 | +- T00:MESH_TEST_TLS 开关 + server 自签支持。 |
| 289 | +- T01:Dockerfile.server / Dockerfile.client 与 docker-compose.yml。 |
| 290 | +- T02:lib/helpers.sh + lib/metrics.sh。 |
| 291 | +- T03:fixtures/netem.sh。 |
| 292 | +- T04:scenario 01-connectivity。 |
| 293 | +- T05:scenario 02-performance。 |
| 294 | +- T06:scenario 03-failure。 |
| 295 | +- T07:run.sh + 结果聚合 + summary.txt。 |
| 296 | +- T08:CI workflow。 |
| 297 | + |
| 298 | +## 12. 风险与缓解 |
| 299 | + |
| 300 | +| 风险 | 缓解 | |
| 301 | +|------|------| |
| 302 | +| `tc` 在 macOS host 不可用 | e2e 全在 Linux 容器内 | |
| 303 | +| Docker privileged 模式安全风险 | 仅 CI/本地开发,文档明示 | |
| 304 | +| 性能数据受 host 负载影响 | 软门槛 + 历史趋势对比 | |
| 305 | +| server 拿不到 ACME 证书 | 引入 `MESH_TEST_TLS=off` 开关 | |
| 306 | +| `mesh join` 在 TUN 起来前需要 DNS | 容器内 `/etc/hosts` 注入 server 别名 | |
| 307 | +| 长时间 iperf 占用 CI 资源 | 默认 30s 短流,CI 用 `--quick` 模式 | |
| 308 | + |
| 309 | +## 13. 参考资料 |
| 310 | + |
| 311 | +- 当前 `tests/integration_test.go` |
| 312 | +- `internal/tunnel/router.go`、`internal/tunnel/server.go`(P02 已落地) |
| 313 | +- `docs/todo/performance/performance.md`(性能目标基线) |
| 314 | +- Tailscale / ZeroTier 的 e2e 思路(控制面与数据面分离 + 容器化拓扑) |
0 commit comments