8.3 MTU、IP fragmentation 与 Path MTU Discovery
最大传输单元(MTU)表示链路或接口一次可承载的网络层数据包上限。路径 MTU(PMTU)取源端到目的端沿途各跳 MTU 的最小值;反向路径可能不同,需要分别判断。应用消息可以跨越多个数据包,因此不能把 MTU 当成消息长度限制,也不能据此规定每次 TCP send() 的写入大小。
1. 先分开 frame、IP packet、TCP segment 和 application write
application bytes
↓ stream/message framing
TCP segment or UDP datagram
↓ IP header
IP packet
↓ link header/trailer
Ethernet/Wi-Fi/tunnel frameEthernet 常见 IP MTU 为 1500,但 jumbo frame、PPPoE、VPN/overlay 和 cloud encapsulation 都可改变可用 MTU。不能用某类 Wi-Fi 帧“frame body 最大 2304”推断 host 的 IP MTU 就是 2304:802.11 MAC frame 开销、LLC/SNAP、aggregation 和 bridge conversion 与 IP interface MTU 是不同边界。
TCP 可将 byte stream 切成 segment,segmentation offload 还可使 packet capture 在 host 某一侧看到大于 wire MTU 的 logical packet。观察时要注明 capture point 和 NIC offload state。
2. IPv4 fragmentation 取决于 DF 与 packet size
IPv4 router 收到大于 outgoing-link MTU 的 packet 时:
- DF(Don't Fragment)为 0:router 可 fragment;
- DF 为 1:router 不能 fragment,丢 packet 并应返回 ICMP Destination Unreachable / Fragmentation Needed(Type 3, Code 4),包含 next-hop MTU information。
Fragment 在原 IP datagram 的目的端重组,不在普通转发路由器上逐跳重组;隧道外层目的端可能正好是一台路由器,要区分内外层 IP。丢一个 fragment 会让整个 original datagram 无法重组,而 middlebox、NAT 和 firewall 对 fragment 的处理也更复杂。因此现代传输协议通常尽量避免依赖途中 IPv4 分片。
例如一个总长 4000 bytes、无 IP options 的 IPv4 datagram,经 MTU 1500 的链路分片。原载荷为 3980 bytes;非末片载荷须是 8 bytes 的倍数,Fragment Offset 也以 8 bytes 为单位:
| 分片 | IP 载荷 | 含 20-byte 首部的总长 | Offset / MF |
|---|---|---|---|
| 1 | 1480 | 1500 | 0 / 1 |
| 2 | 1480 | 1500 | 185 / 1 |
| 3 | 1020 | 1040 | 370 / 0 |
三个分片使用相同 Identification,MF=0 标明末片。Offset 185 指的是原载荷第 1480 字节,不能当成字节偏移直接拼接。
3. IPv6 router 不做 in-path fragmentation
IPv6 router 遇到 oversized packet 时丢弃并返回 ICMPv6 Packet Too Big(Type 2)。只有 source 可使用 Fragment extension header 发 fragments,router 不会像 IPv4 DF=0 那样现场切 packet。
IPv6 要求每条 link 支持 1280-byte minimum MTU。如果 link 无法 native 承载,link layer 必须在 IPv6 之下提供 fragmentation/reassembly。“IPv6 minimum MTU 1280”不等于所有 IPv6 path 的 PMTU 永远只能用 1280。
4. Classic PMTUD 依赖 ICMP feedback
IPv4 PMTUD 通常发 DF packet,根据 ICMP Fragmentation Needed 降低 PMTU estimate。IPv6 PMTUD 根据 ICMPv6 Packet Too Big 更新 estimate。Estimate 需要有 lifetime 并允许 path 变化后重新提高,否则一次旧 bottleneck 可让连接长期停在过小 packet size。
Equal-cost multipath、route change 和 tunnel 可让不同 flow/time 的 PMTU 不同。PMTU 不应被当作 host pair 的永久常量。
ICMP message 要做 validity check,rate limit 与 policy filtering,但粗暴丢弃所有 ICMP/ICMPv6 会破坏 PMTUD。
5. MTU black hole 是“小包通,大包沉默”的候选原因
Typical failure chain:
- Source 发了 oversized non-fragmentable packet;
- Router/tunnel endpoint 丢 packet 并生成 ICMP Too Big/Fragmentation Needed;
- ICMP 被中间 policy 丢弃,或 sender 无法将它关联到 flow;
- Sender 不降低 packet size,重传继续失败。
Symptom 可是 TCP handshake 成功、small request 成功,一到 certificate chain、large response 或 upload 就 timeout。但相同 symptom 也可来自 application timeout、proxy body limit、packet loss 或 flow-control bug,不能只靠“大 file 不通”就定性为 MTU black hole。
Evidence 应包含:
- Interface/tunnel MTU 和 route;
- Packet capture 中的 packet size、DF/IPv6、retransmission;
- ICMP too-big message 是否生成/到达;
- 不同 probe size 的结果;
- 调低 MTU/MSS 后问题是否可重现地消失。
6. PLPMTUD 不把 ICMP 当成唯一信号
Packetization Layer PMTUD(PLPMTUD)由 transport/application packetization layer 发送不同大小的 probe,用 acknowledgement/loss behavior 推断 supported size,不必完全依赖 ICMP。
但 probe loss 可能只是 congestion,不一定是 MTU limit。Algorithm 需要 search state、confirmation、fallback base size 和 black-hole detection,不是“对 packet size 做一次 binary search”就完成。RFC 4821 给出了适用于 TCP 的 PLPMTUD guidance,Datagram transport/application 可按 RFC 8899 设计 Datagram PLPMTUD。QUIC 也有自己的 datagram size / path validation requirement。
7. TCP MSS 与 MSS clamping
TCP MSS option 出现在 SYN(包括 SYN-ACK)中,声明发送此选项的一端能接收的最大 TCP 数据长度,不包含 IP/TCP 首部。通告值通常从有效 MTU 只扣固定首部:IPv4 扣 20+20 bytes,IPv6 扣 40+20 bytes。根据 RFC 6691,不应预先在 MSS 中再扣可能出现的 options;实际发送者要为自己加入的 options/扩展首部减少数据长度。
例如 MTU 1500 的普通 IPv4 路径可通告 MSS 1460;若某个数据段的 TCP options 占 12 bytes,该包最多放 1448 bytes 的 TCP 数据。对端 MSS 和已发现的 PMTU 都会限制实际分段,MSS 也不是一次 send() 必须写满的长度。
MSS clamping 在路由器/firewall 经过 SYN 时降低 advertised MSS,可作为 tunnel/PMTUD black-hole 的 mitigation。它只影响 TCP,不修复 UDP/ICMP/other IP traffic,也不应代替正确 link MTU 和 ICMP policy。随手把 MSS clamp 到极小值会增加 packet/CPU overhead。
8. Tunnel overhead 要做显式 budget
Inner packet 进入 tunnel 后多出 outer IP、UDP/GRE/ESP 和 tunnel-specific header/tag。精确 overhead 取决于 IPv4/IPv6、encryption mode、option 和 implementation,不应用“VPN 都是 50–70 bytes”作为配置依据。
一个简单 budget calculator 可迫使 configuration 显式列出每层 overhead:
def inner_mtu(outer_mtu: int, overheads: list[tuple[str, int]]) -> int:
if outer_mtu <= 0:
raise ValueError("outer_mtu must be positive")
total = 0
for name, size in overheads:
if size < 0:
raise ValueError(f"negative overhead for {name}")
total += size
result = outer_mtu - total
if result <= 0:
raise ValueError("overhead leaves no room for an inner packet")
if result < 1280:
print("warning: result is below the IPv6 minimum link MTU")
return result
# 数值必须来自你实际使用的 tunnel format,下面只是算法示例。
headers = [("outer IP", 20), ("UDP", 8), ("tunnel data header/tag", 32)]
assert inner_mtu(1500, headers) == 1440这里的 outer_mtu 从外层 IP 开始计数,不再扣已经排除的外层 Ethernet 首部。载荷类型也要写清:承载完整 Ethernet frame 的隧道还需给内层 Ethernet 首部留空间。变长 padding、嵌套隧道与运营商限制要按实际格式补入,必要时取最大开销。NIC offload 可能改变本机抓包形态,但不会凭空省掉线上封装字节。
低于 1280 的结果只是预算告警,不能直接当作可用的 IPv6 link MTU 配置;需要支持 IPv6 以下的适配机制,或调整封装/外层路径。最终预算仍须端到端验证。
9. Linux 诊断工具
ip link show
ip route get 203.0.113.10
tracepath 203.0.113.10
# IPv4:不允许 fragment,ICMP payload 1472 + IPv4 20 + ICMP 8 = 1500
ping -4 -M do -c 3 -W 2 -s 1472 203.0.113.10先把 203.0.113.10 替换为你控制的实际端点。此处 ping -M do 会受本机已知 PMTU 限制,可能在本地就返回 “message too long”,并不代表探针已经到达远端。Command option 和 privilege 因 OS 而异,ping 成功也不证明 TCP/UDP 使用同一 path/policy。IPv4 header 有 options 时 1472 计算也要改变。实验只对自己有权测试的 endpoint 进行,并与 packet capture/counter 结合。
10. 验收问题
- IPv4 DF=0 和 DF=1 遇到小 outgoing MTU 时有何不同?
- IPv6 router 为什么不做 in-path fragmentation?
- 区分 classic PMTUD 与 PLPMTUD 的 feedback source。
- 为什么 MSS clamping 不能修复 UDP MTU black hole?
- 给出能支持 MTU-black-hole 结论的至少三项证据。