本文由 Gemini 协助编写。
背景
在最近的一次跨地域算力池部署中,我遇到了一个非常有意思的网络“灵异事件”。
我们有两个站点(Site-A 和 Site-B),中间通过两层隧道打通:WireGuard + IPsec(site-to-site)。出于安全合规要求,全站禁 ICMP(等价于:ping/traceroute 都不可用)。
在这种“致盲”状态下,问题出现了:
- 流量方向 A -> B:50Mbps,跑满。
- 流量方向 B -> A:SSH 能连,但一传大文件(
scp)速度就掉到 30KB/s 左右。
这种“单行道”式的带宽坍缩,通常意味着链路没断,但某种“协议层面的天花板”被触碰了。
排查过程:没有 ICMP 怎么排障?
在正常的网络排查中,我会用 ping -s <size> -M do 来探测路径 MTU。但现在 ICMP 被封禁,我收不到任何“包过大(Fragmentation Needed)/ Packet Too Big”的反馈。
既然不能 Ping,我只能通过 TCP 行为指纹 来进行推断。我使用 iperf3 做了一组对比实验(下面给出最小可复现命令):
1) TCP 默认测试(观察重传)
在接收端(例如 Site-A)启动服务:
|
|
在发送端(例如 Site-B)发起测试(看输出里的 Retr/吞吐):
|
|
现象:发送端瞬间堆积大量重传,带宽锁定在 ~0.4Mbps 量级。
2) UDP 对照(验证链路“并未彻底坏掉”)
|
|
现象:在设定 20Mbps 带宽时,通信基本正常(丢包可控)。
3) TCP MSS 步进探测(关键证据)
|
|
现象:
MSS = 1400时,速度坍缩且重传爆炸;MSS = 1350时,吞吐瞬间恢复到 ~49Mbps。
结论呼之欲出:这是一个典型的 PMTUD(Path MTU Discovery)黑洞,而且是在“被致盲”(无 ICMP)环境下发生的。
原理解析:谁在“谋杀”数据包?
1) 先把 MSS/MTU 的换算说清楚
TCP 的 MSS 是“单个 TCP 段里可承载的 payload”,它受路径 MTU 限制。
- IPv4 常见近似:
MSS ≈ MTU - 20(IP) - 20(TCP) - TCP_OPTIONS - 以太网 MTU 1500 时,典型默认 MSS 是 1460(隐含 TCP options 取 0 或被实现细节吸收在近似里;不同抓包/栈实现会略有差异)。
在多层隧道(WireGuard + IPsec)下,有效路径 MTU 会被封装头部持续“吃掉”,于是默认的大 MSS 很容易变成“超载包”。
2) 为什么会变成 PMTUD blackhole?
当发送方按默认 MSS 发大包:
- 中间某个节点发现包太大,本该回发 ICMP(IPv4 的 Fragmentation Needed / IPv6 的 Packet Too Big)提示“兄弟,包太大了,降 MTU/MSS”。
- 但由于策略 全站禁 ICMP,这个关键 ICMP 被防火墙默默丢弃。
- 发送方收不到通知,只能把“包丢了”误判为“链路丢包”,于是反复重传同样大小的包,吞吐被重传拖死。
这就是所谓的 “MTU 黑洞” —— 包丢了,但没人告诉你为什么。
3) 关于 WireGuard / IPsec 的开销(不要迷信固定数字)
我最初用“~80 + ~70”做了一个直觉估算,但更准确的说法是:
- WireGuard 的开销取决于外层 IPv4/IPv6、UDP、以及实现细节;常见量级在 60~80 bytes。
- IPsec ESP 的开销与模式(transport/tunnel)、NAT-T、有无 padding、加密套件(例如 AES-GCM)有关;常见量级在 50~90 bytes。
所以更可靠的做法是:用抓包/文档确认你这条路径的实际封装开销,然后倒推一个安全 MTU/MSS。
解决方案:从“根治”到“权宜”
A. 根治(如果合规允许):只放行“必要的 ICMP”,不是放行 ping
很多环境“禁 ping”其实只是不希望被探测,但 PMTUD 依赖的 ICMP 属于“控制面必要信号”。如果策略允许,建议至少放行:
- IPv4:ICMP Type 3 Code 4(Fragmentation Needed)
- IPv6:ICMPv6 Type 2(Packet Too Big)
这通常是最干净的修复:TCP 能自动收敛到正确 MTU,不需要在网关硬编码 MSS。
B. 权宜 1:直接把隧道/出口 MTU 调到安全值
在 WireGuard/IPsec 的嵌套场景下,保守一点把相关接口 MTU 先降下来,往往就能立刻止血(代价是 payload 利用率略降)。
C. 权宜 2:强制 MSS 钳制(MSS Clamping)
既然无法依赖 ICMP,我选择在流量必经的中转网关上执行“暴力”拦截:改写握手 SYN 上的 MSS,保证后续数据段不超过嵌套隧道的“物理上限”。
说明:也有人用
--clamp-mss-to-pmtu,但在 PMTUD blackhole 环境里,路径 PMTU 信息本身可能不可靠/不可达;固定一个“已验证可用”的 MSS 往往更可控。
优化脚本:vpn-optimize.sh (脱敏版)
|
|
兼容性提示:部分系统的
iptables实际是iptables-nft兼容层;如果你是纯 nftables 环境,需要用等价的 nft 规则实现。
验证闭环(非常重要)
- 确认 MSS 真的被改写:抓 SYN 包查看 MSS option 是否落在你设定的值。
|
|
- 确认规则命中:
|
|
- 用 iperf3/实际 scp 回归:
Retr显著下降、吞吐恢复到预期。
回滚(止血后可随时撤销)
- 删除 mangle 规则:用
iptables -t mangle -D ...按--line-numbers指定行号删除。 - 恢复 MTU/offload:
|
|
经验总结
- MTU 是第一生产力:只要涉及 VPN/Overlay,默认 1500 很容易踩坑。
- 非对称性思维:单向限速通常不是“带宽不够”,而是“包大小不对”。
- 宁缺毋滥:把 MSS 设保守一点,牺牲少量载荷效率,换来稳定性和吞吐的数量级提升。
在“无声”的网络中排障,理解协议头比使用工具更重要。
|
|