上海 9929 优选低延迟路径: GOST MTCP + ECMP 启动预热

发布于

zcp19970603:

## 0. 优选后测试结果截图

RTT 延迟稳定不抖动,不掉包,单线程测速也能跑起来。

![image](http://pic.zhso.org/2026/08/11/637369b82588.png)

![image](http://pic.zhso.org/2026/08/11/b08e335c3b9d.png)

![image](http://pic.zhso.org/2026/08/11/244c2251e95f.png)

## 1. 问题背景

部分跨境或跨运营商线路(这里主要说上海 9929 宽带,存在多条 4837 和 9929 混合 ECMP):据说联通将这项业务外包给第三方,公司后端接入了多条 AS4837(普通联通骨干网)和 AS9929(联通精品网)线路。

这种 ECMP(等价多路径)导致 ICMP ping 很稳定,但不同 TCP 连接的 RTT 会落到几个明显不同的档位,比如一部分约 33ms,另一部分约 51ms。每次新建 TCP 连接时,会重新参与 ECMP 哈希,导致不同业务连接分别经历快路或慢路,延迟因此波动。

## 2. 核心思路

***启动 GOST MTCP 阶段,选出一条低延迟的外层 TCP,后续所有业务都复用这条连接***,而不是在运行过程中持续探测或踢慢连接。

```
任意 TCP 客户端 → 前置 GOST :12000 → 单个长期 MTCP session(4 MiB Stream/32 MiB Receive)
→ 启动阶段检测并只保留低延迟外层 TCP → 远端 GOST :11000 → 目标 TCP 服务
```

GOST MTCP 将多个逻辑 TCP 流复用到同一条外层 TCP 上,因此 ECMP 抽路的影响从“每个业务连接抽一次”收敛到“MTCP 外层连接建立时抽一次”。

我之前试过维持多条 MTCP 连接,定期探测 RTT,结合 `ss -K` 命令踢掉慢路,但存在两个问题:一是 `ss -K` 踢掉后会影响该 session 下所有逻辑流;二是踢掉重建的连接又要重新参与 ECMP,有可能落到慢路,导致“抽路-清理”死循环。最终改成只配置一个 MTCP 节点,正常运行只允许一个外层 TCP,只在启动阶段做 ECMP 预热,命中快路后不再干预,运行时不做周期检测、不踢连接也不主动重建。

## 3. MTCP 缓冲区参数

单个流缓冲区太小会限制吞吐,需根据 BDP(带宽延迟积)估算。例如 RTT 大约 33ms,目标带宽约 400Mbps,BDP ≈ 400Mbps × 0.033秒 ≈ 1.65MiB,4 MiB 的流缓冲较为充足:

```yaml
mux.maxFrameSize: 32768 # 32 KiB
mux.maxReceiveBuffer: 33554432 # 32 MiB
mux.maxStreamBuffer: 4194304 # 4 MiB
```

## 4. 远端 GOST 配置

远端只需监听 `:11000`,具体转发目标由前置 GOST 的 `forwarder` 指定。

配置示例 `/root/gost/remote.yaml`:

```yaml
services:
- name: mtcp-server
addr: :11000
handler:
type: relay
listener:
type: mtcp
metadata:
mux.version: 2
mux.keepaliveDisabled: false
mux.keepaliveInterval: 10s
mux.keepaliveTimeout: 30s
mux.maxFrameSize: 32768
mux.maxReceiveBuffer: 33554432
mux.maxStreamBuffer: 4194304
```

## 5. 前置 GOST 配置

前置监听 `:12000`,通过 `chain-mtcp` 使用 MTCP dialer 连接远端。

```yaml
services:
- name: tcp-entry
addr: :12000
handler:
type: tcp
chain: chain-mtcp
listener:
type: tcp
forwarder:
nodes:
- name: target
addr: 127.0.0.1:2345

chains:
- name: chain-mtcp
hops:
- name: remote
nodes:
- name: remote-mtcp
addr: 203.0.113.20:11000
connector:
type: relay
dialer:
type: mtcp
metadata:
mux.version: 2
mux.keepaliveDisabled: false
mux.keepaliveInterval: 10s
mux.keepaliveTimeout: 30s
mux.maxFrameSize: 32768
mux.maxReceiveBuffer: 33554432
mux.maxStreamBuffer: 4194304
```

最终链路大致是:

```
TCP 客户端 → 前置:12000 → GOST TCP Handler → GOST MTCP Dialer → 203.0.113.20:11000 → 远端 GOST Relay → 127.0.0.1:2345
```

示例目标地址,可以替换成任意内网/本机地址,本质是 TCP 到 MTCP 隧道,再到 TCP。

## 6. 判断 ECMP 快路 / 慢路

使用命令查看连接:

```bash
ss -tin "dst 203.0.113.20:11000"
```

重点看 `minrtt:` 字段,而不是当前 RTT,因为当前 RTT 可能受排队、突发流量、重传、ACK 延迟等影响,`minrtt` 更能反映这条 TCP 连接最初选中的路径延迟。

举例:

- 快路 RTT 32~34ms
- 慢路 RTT 50~52ms

阈值可设 `THRESH=40`,`minrtt < THRESH`判为快路,否则算慢路。

## 7. 启动预热脚本 `mtcp-prewarm.sh`

脚本只负责在启动阶段做选路,核心逻辑:

- 目标地址上不存在 ESTAB 连接时,主动 connect 触发 GOST 建立 MTCP,等待连接建立。
- ESTAB 连接数为 1 时,读取 `minrtt`,若小于阈值连续确认多次,即判定预热成功;若不满足,则用 `ss -K` 踢掉慢连接,等待它彻底断开,重新触发新连接加入 ECMP。
- ESTAB 超过1条时,认为状态异常,直接报错退出,不自动踢,避免影响正常业务,请人工重启对应 GOST 服务。

脚本支持多目标、自定义触发命令 `PREWARM_CMD`,所有阈值和超时可通过环境变量覆写,详细用法见脚本内说明。

(此处省略脚本全文,详见原文链接)

校验脚本语法:

```bash
chmod +x /root/mtcp-prewarm.sh
bash -n /root/mtcp-prewarm.sh
```

## 8. 使用方法

单目标手动执行:

```bash
THRESH=40 \
PREWARM_ADDR=127.0.0.1:12000 \
/root/mtcp-prewarm.sh 203.0.113.20 11000
```

如果首次选到慢路,脚本会执行 `SLOW → KILL → 等待旧连接消失 → 重新触发`,直到连续两次确认选到快路,输出 `PREWARM SUCCESS`。

多目标一次执行:

```bash
./mtcp-prewarm.sh \
203.0.113.20 11000 \
198.51.100.20 11000
```

不同目标用不同本地触发入口:

```bash
./mtcp-prewarm.sh \
203.0.113.20:11000@127.0.0.1:12000 \
198.51.100.20:11000@127.0.0.1:13000
```

如果普通 TCP connect 无法真正触发业务链,可自定义触发命令:

```bash
PREWARM_CMD="timeout 1 bash -c 'exec 3<>/dev/tcp/127.0.0.1/12000; sleep 0.3'" \
./mtcp-prewarm.sh 203.0.113.20 11000
```

不同线路 RTT 档位不同时,建议为它们创建独立 systemd 服务,分别设置对应的 `THRESH`(例如线路 A 快路 33ms/慢路 51ms 用 `THRESH=40`,线路 B 快路 46ms/慢路 60ms 用 `THRESH=52`)。

## 9. systemd 集成

推荐用 systemd 管理 GOST,包括开机启动、异常拉起、日志统一,并在每次启动后自动做一次 ECMP 预热(通过 `ExecStartPost`)。

前置 GOST(主线路)示例 systemd 单元文件 `/etc/systemd/system/gost-mtcp-main.service`:

```bash
[Unit]
Description=GOST MTCP Main
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/root/gost

ExecStart=/root/gost/gost -D -C /root/gost/cn.yaml

Environment="PREWARM_ADDR=127.0.0.1:12000"
Environment="THRESH=40"

ExecStartPost=/bin/bash -lc '/root/mtcp-prewarm.sh 203.0.113.20 11000 || true'

Restart=on-failure
RestartSec=2
TimeoutStartSec=120

KillSignal=SIGTERM

StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
```

启动及查看状态:

```bash
systemctl daemon-reload
systemctl enable --now gost-mtcp-main
systemctl status gost-mtcp-main --no-pager
journalctl -u gost-mtcp-main -n 100 -l --no-pager
```

`ExecStartPost` 后加上 `|| true`,避免预热失败被 systemd 视为启动失败,导致无限重启循环。预热失败只写日志,主进程正常运行,方便人工检查。

## 10. 验证与常见问题

**验证只有一个 MTCP 外层连接且命中快路后,即完成预热。**

(正文后续未提供,至此为止)

---

原文链接:[点击查看](https://www.v2ex.com/t/1233477)

评论(1)

纯靠硬核手搓脚本来搞定网络选路,太强了,老哥这技术力牛掰。

· 0 个赞

0.053183s