换了一条专线,接口 90 % 的请求正常,少数请求卡住直到超时。抓包看得很清楚:客户端把 1,4xx 字节的报文发了,服务端应"给我回个 ICMP 说太大",但这条 ICMP 从来没到——中间有一个设备的 ACL 把 ICMP 全丢了。双方于是各自重传,卡在那个永远送不到的报文上。
这类故障有个固定形状:小请求全好、大请求必坏、且坏的方式是"超时"不是"报错"。它不属于应用问题,也不属于带宽问题,是"链路允许的报文上限"与"两端默认假设"对不上,而发现并协商这个上限的机制又被静默地阻断了。
这篇拆三层:分片发生在哪、PMTUD 靠什么商、以及为什么最后大家都改用"直接钳小 MSS"。
1. 分片发生在 IP 层,代价由谁付
以太网默认 MTU 1500,减去 20 字节 IP 头与 20 字节 TCP 头,可得 MSS 1460。超过这个数的 IP 报文必须分片;而分片只在 IPv4 允许,IPv6 明确禁止中间设备分片。
| 层 | 上限 | 谁负责遵守 | 超了会怎样 |
|---|---|---|---|
| 链路 MTU | 1500(以太网) | 下一跳接口 | 丢弃或分片 |
| IP 报文 | 受 MTU 约束 | 发送端 | IPv4 可分片,IPv6 直接丢并回 ICMP |
| TCP MSS | MTU − 40 | 两端协商 | 由 MSS 保证不分片 |
| 应用报文 | 受 MSS 与缓冲约束 | 应用 | 粘包、拆包 |
| 隧道封装 | MTU − 封装头 | 隧道端点 | 需要更小的 MSS |
第三行是真正省钱的一层:只要 MSS 协商正确,IP 层根本不需要分片。所以"分片"这件事,本质是"MSS 没协商对时的兜底",而兜底一失败,就变成一包都送不过去。
// 同一条 1500 MTU 链路上,一次 3,000 字节的 HTTP 体
不分片(MSS 1460):1500 + 1500 + 80 三段,正常
分片(MSS 1500): 1500 + 1480 + 20 末片只有 20 字节载荷
// 后者危险:任何一片丢失,整个 IP 报文都要重传(TCP 也救不了)
2. PMTUD:靠 ICMP 商量出来的上限
路径 MTU 发现(PMTUD)的流程只有三步:发送端把 DF(Don't Fragment)置位,中间设备发现报文超过自己的 MTU 就回一条 ICMP Fragmentation Needed(IPv6 是 Packet Too Big),发送端据此调小 MTU 重发。
| 环节 | 期望行为 | 现实里的失效方式 |
|---|---|---|
| 发送端 | 置 DF 位,收到 ICMP 后降 MTU | 内核未开 PMTUD,或忽略 ICMP |
| 中间设备 | 超限时回 ICMP | ACL 丢 ICMP、限速、只放 echo |
| 隧道端点 | 正确计算并回填 MTU | 封装后未扣头,仍按 1500 转发 |
| 负载均衡 | 透传 ICMP 或代为生成 | 不回 ICMP,静默丢弃 |
四个环节任一断开,就是 PMTUD 黑洞:发送端永远不知道上限在哪,永远重发同一个过大的报文,用户看到的是"页面卡住然后超时"。
// 三步确认是不是黑洞:DF 置位、逐步加大、看在哪一档断
$ ping -M do -s 1472 -c 2 <target> # 1472 + 28 = 1500,通 → MTU 至少 1500
$ ping -M do -s 1400 -c 2 <target> # 通
$ ping -M do -s 1472 -c 2 <tunnel_hop> # 断,而 1400 通 → 黑洞在 tunnel_hop
// IPv6 下没有 -M do(禁止分片),直接看 Packet Too Big 是否回来
$ ping6 -s 1452 -c 2 <target>
3. 隧道/VPN 为什么必然踩这个坑
任何封装都会吃掉 MTU:IPsec 约 50–70 字节、GRE 24、VXLAN 50、WireGuard 60。1500 的链路,套一层 VXLAN 就只剩 1450,再套 IPsec 只剩 1380 左右。而两端如果不重新协商 MSS,仍然按 1460 发,超出的部分全靠 PMTUD 救——偏偏隧道端点的 ICMP 生成经常没配。
| 封装 | 典型额外开销 | 建议内层 MTU | 常见症状 |
|---|---|---|---|
| GRE | 24 字节 | 1476 | 大响应卡住 |
| VXLAN | 50 字节 | 1450 | 容器间大包超时 |
| IPsec(ESP) | 50–73 字节 | 1400–1420 | 只有大请求坏 |
| WireGuard | 60 字节 | 1420 | SSH 卡在登录后 |
| 双层隧道 | 100+ 字节 | 1350 以下 | 几乎不可用 |
第三列就是"改小 MTU 能修好"的全部秘密:不是让链路变宽,是让两端从一开始就不发过大的包,把 PMTUD 这条不可靠的协商路径整个绕开。
4. MSS clamping:把"协商"变成"强制"
生产上最稳的做法不在终端改 MTU(改不全:容器、LB、sidecar 各有各的),而在路径上的第一个可配置点做 MSS clamping:把 SYN 里的 MSS 选项改写成你想要的值。
| 位置 | 能否 clamp | 说明 |
|---|---|---|
| 客户端主机 | 能(路由 MTU) | 只能管自己,管不了服务端 |
| 服务端主机 | 能 | 同上 |
| 隧道端点(GRE/VXLAN) | 能,首选 | 一次配置覆盖整条隧道 |
| 云 LB / 网关 | 多数能 | 需确认是否改写 TCP 选项 |
| CDN / 反代 | 部分能 | 通常在回源侧生效 |
// 隧道端点做 MSS clamping(iptables),把过大 MSS 改写为 1360
$ iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
// 或显式指定,避免依赖 PMTU 探测结果
$ iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss 1360
// nftables 等价写法
$ nft add rule inet filter forward tcp flags syn tcp option maxseg size set 1360
--clamp-mss-to-pmtu 看起来更聪明,但它依赖内核对路由 MTU 的认知——在容器与 overlay 网络里这个值经常是错的。显式 --set-mss 更稳,代价是你要自己算出安全值。
5. 什么现象指向 MTU,什么现象不指向
这是本篇最实用的一张表。MTU 问题的形状太特殊,认错方向会浪费一整天。
| 现象 | 指向 MTU? | 理由 |
|---|---|---|
| 小请求正常,大响应超时 | 是 | 典型的单向超限 |
| TLS 握手成功、首个大数据包卡住 | 是 | 证书链大包被丢 |
| 所有请求都慢但都成功 | 否 | 更可能是拥塞或带宽 |
| 连接直接 RST | 否 | 端口/策略/重置,不是分片 |
curl 小文件正常、大文件 0 字节 |
是 | 典型 PMTUD 黑洞 |
| DNS 解析偶尔超时 | 可能 | 大响应 + EDNS 缓冲 |
SSH 能登录、ls 卡住 |
是 | 登录包小、目录列表大 |
// 三问排除法,比翻参数快
1 报文大小是唯一变量? 否 → 查拥塞与带宽,结束
2 只有单向坏? 是 → 只 clamp 出口方向,别两端各改一半
3 有隧道 / overlay? 是 → 先改隧道端点 MTU,其次 clamp MSS
// 三问都不成立 → 看 ICMP 是否被丢,而不是继续改 MTU
第六行值得单独说:DNS 的 UDP 响应超过 512 字节会走截断/EDNS,链路把大 UDP 丢了,就表现为"偶尔解析失败"。这类问题在 VPN 里非常常见,而它看起来完全不像网络层问题。
6. 排错:五种"以为是带宽"的失败
| 症状 | 八成原因 | 处理方向 |
|---|---|---|
| 大 POST 超时、小 GET 正常 | PMTUD 黑洞 | 隧道端点 clamp MSS |
| 改 MTU 后别的服务坏了 | 只改了一端/一处 | 在路径公共点统一改 |
| 容器里通、宿主不通 | overlay MTU 未扣封装头 | 调 CNI 的 mtu 参数 |
| 偶发超时、重传率高 | 分片末片丢失 | 消除分片(clamp MSS) |
| IPv6 全不通、IPv4 正常 | 中间设备不回 Packet Too Big | 降 announced MTU 或修 ICMP |
// 容器与 overlay 里最常见的一处:CNI 的 mtu 没扣封装头
$ ip link show vxlan0 | head -2
$ docker network inspect bridge -f '{{.Options}}'
$ kubectl get cm -n kube-system kube-flannel-cfg -o yaml | grep -i mtu
// VXLAN 在 1500 链路上应配 1450;配成 1500 就是 100% 黑洞
第二行是最常见的二次事故:只把客户端 MTU 调到 1400,服务端仍按 1460 发,于是反方向仍然黑洞。MTU 是双向的,改一半等于没改。
7. 边界:什么情况不该动 MTU
| 场景 | 为什么不该动 | 该做什么 |
|---|---|---|
| 链路本身丢包严重 | 问题是丢包,不是上限 | 查重传与拥塞窗口 |
| 单条流慢、多条流正常 | 是拥塞控制,不是分片 | 查 RTT 与 BDP |
| 应用侧报文本来就很碎 | 分片不是瓶颈 | 查应用层缓冲 |
| 云厂商托管 LB 后 | 改不了路径中间点 | 用其 MTU 建议值 |
| 只有 UDP 大包失败 | 常见于 EDNS/QUIC | 单独看 UDP 的处理链 |
判断口径一句话:当"报文大小"是唯一能区分成功与失败的变量时,才轮到 MTU;如果缩小报文也不改善,那从来不是分片问题。这条判据能省掉大量无用功——因为它把"大包失败"与"大包恰好赶上拥塞"区分开了,而后者才是多数"看起来像 MTU"的现场。
8. 自测
- MSS 与 MTU 的数值关系是什么?为什么"MSS 协商正确时 IP 层不需要分片"?
- PMTUD 的三步流程里,哪一步被 ACL 丢弃会造成黑洞?表现为什么现象?
- 为什么隧道必然会踩这个坑?举两个封装的开销数字。
- MSS clamping 放在哪一层最有效?
--clamp-mss-to-pmtu为什么不如显式值稳? - "DNS 偶尔解析失败"与 MTU 的关系是什么?
- 什么判据能让你快速排除 MTU 方向?
9. 一句话总结
MTU 故障的形状是"小包全好、大包必坏、坏法是超时",根因是两端的默认假设(1500/1460)与链路上真实上限不一致,而发现不一致的机制(PMTUD 的 ICMP)又被静默丢弃。修法不是把链路变宽,而是在路径的公共点强制钳小 MSS,让过大的报文根本不被发出。判据很朴素:报文大小是唯一能区分成败的变量时才是 MTU,否则去查拥塞。