Gemini 协助编写
问题 #
跨地域算力池部署,Site-A 和 Site-B 之间跑了两层隧道:WireGuard + IPsec。安全合规要求,全站禁 ICMP——ping 和 traceroute 全废。
然后诡异的事情来了:
- A → B:50Mbps,跑满,没毛病
- B → A:SSH 能连,但
scp大文件直接掉到 30KB/s
典型的"单行道"症状。链路没断,但某个协议层面的天花板被撞了。
一开始怀疑运营商限速、偷包,甚至想过是不是 QoS 策略在搞鬼。折腾半天,压根没往 MTU 方向想——典型的专家盲区:手里拿着抓包工具,看什么都像协议问题,反而忽略了最基础的链路参数。就像网络高手帮朋友排查问题,tcpdump、wireshark 一顿操作,最后发现是宽带欠费了。
排查:没 ICMP 怎么玩? #
正常情况用 ping -s <size> -M do 探测路径 MTU。但 ICMP 被封了,收不到任何"包太大"的反馈。
只能靠 TCP 行为指纹 推断。用 iperf3 做对比实验:
1) TCP 默认测试 #
# 接收端
iperf3 -s
# 发送端
iperf3 -c <SITE_A_IP> -t 10现象:Retr 重传爆炸,带宽锁死在 ~0.4Mbps。
2) UDP 对照 #
iperf3 -c <SITE_A_IP> -u -b 20M -t 10现象:20Mbps 基本正常,丢包可控。说明链路本身没挂。
3) TCP MSS 步进探测(关键) #
iperf3 -c <SITE_A_IP> -t 10 -M 1400 # 坍缩,重传爆炸
iperf3 -c <SITE_A_IP> -t 10 -M 1350 # 瞬间恢复 ~49Mbps破案:典型的 PMTUD 黑洞,在"致盲"环境下触发。
原理:谁在"谋杀"数据包? #
MSS/MTU 换算 #
TCP MSS = 单个 TCP 段的 payload 上限,受路径 MTU 限制。
- IPv4 近似:
MSS ≈ MTU - 20(IP) - 20(TCP) - TCP_OPTIONS - 以太网 MTU 1500 时,默认 MSS 约 1460
多层隧道(WireGuard + IPsec)下,封装头部持续"吃掉"有效 MTU,默认大 MSS 就变成"超载包"。
为什么变成黑洞? #
- 发送方按默认 MSS 发大包
- 中间节点发现包太大,本该回 ICMP(Fragmentation Needed / Packet Too Big)
- 但 全站禁 ICMP,这个关键信号被防火墙吃了
- 发送方收不到通知,误判为"链路丢包",疯狂重传同样大小的包
- 吞吐被重传拖死
这就是 MTU 黑洞——包丢了,但没人告诉你为什么。
隧道开销(别迷信固定数字) #
- WireGuard:~60-80 bytes(取决于 IPv4/IPv6、UDP、实现细节)
- IPsec ESP:~50-90 bytes(取决于 transport/tunnel 模式、NAT-T、padding、加密套件)
靠谱做法:抓包确认实际封装开销,倒推安全 MTU/MSS。
解决方案 #
A. 根治(理想情况) #
不是放行 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 钳制(我用的方案) #
既然 ICMP 指望不上,直接在中转网关上暴力改写 SYN 包的 MSS:
#!/bin/bash
set -euo pipefail
# --- 配置 ---
PHY_IF="ens3"
REMOTE_NET="10.0.0.0/24"
# 1. 物理层优化(会增加 CPU 开销,按需开)
ip link set dev "$PHY_IF" mtu 1400
ethtool -K "$PHY_IF" tso off gso off gro off
# 2. MSS 钳制
# 转发流量:1280(保守)
iptables -t mangle -C FORWARD -p tcp -d "$REMOTE_NET" --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1280 2>/dev/null || \
iptables -t mangle -I FORWARD -p tcp -d "$REMOTE_NET" --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1280
# 本地发起:1350(略激进但本案例可用)
iptables -t mangle -C OUTPUT -p tcp -d "$REMOTE_NET" --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1350 2>/dev/null || \
iptables -t mangle -I OUTPUT -p tcp -d "$REMOTE_NET" --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1350纯 nftables 环境需要用等价 nft 规则。
验证 #
1) 确认 MSS 被改写 #
tcpdump -ni <IFACE> -vv 'tcp[tcpflags] & tcp-syn != 0 and host <REMOTE_HOST>'2) 确认规则命中 #
iptables -t mangle -vnL FORWARD --line-numbers
iptables -t mangle -vnL OUTPUT --line-numbers3) 实测 #
iperf3 或 scp 回归,Retr 显著下降,吞吐恢复。
回滚 #
# 删 mangle 规则(按行号)
iptables -t mangle -D FORWARD <行号>
iptables -t mangle -D OUTPUT <行号>
# 恢复 MTU/offload
ip link set dev <IFACE> mtu 1500
ethtool -K <IFACE> tso on gso on gro on总结 #
- MTU 是第一生产力:VPN/Overlay 场景,默认 1500 就是坑
- 非对称性思维:单向限速通常不是带宽不够,而是包大小不对
- 宁缺毋滥:MSS 设保守点,牺牲一点载荷效率,换数量级的稳定性提升
在"无声"的网络里排障,理解协议头比会用工具更重要。
NOTE: I am not responsible for any expired content.
Created at: 2025-12-31T12:09:54+08:00
Updated at: 2026-01-03T06:10:01+08:00
Origin issue: https://github.com/ferstar/blog/issues/93