跳到内容

8.3 MTU、IP fragmentation 与 Path MTU Discovery

一批大包总在某段驿道上消失,小包却能通过;阿花沿途核对每一跳允许承载的最大尺寸。

MTU 是某个 link/interface 一次可承载的 network-layer packet 上限。Path MTU(PMTU)是 source 到 destination 路径上各 hop MTU 的 minimum。两者不是 application message size,也不是 TCP send() 每次必须使用的 chunk size。

1. 先分开 frame、IP packet、TCP segment 和 application write

text
application bytes
    ↓ stream/message framing
TCP segment or UDP datagram
    ↓ IP header
IP packet
    ↓ link header/trailer
Ethernet/Wi-Fi/tunnel frame

Ethernet 常见 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 在 final destination 重组,不在中间 router 逐 hop 重组。丢一个 fragment 会让整个 original datagram 无法重组,而 middlebox、NAT 和 firewall 对 fragment 的处理也更复杂。因此 modern transport 通常尽量避免依赖 in-path IPv4 fragmentation。

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:

  1. Source 发了 oversized non-fragmentable packet;
  2. Router/tunnel endpoint 丢 packet 并生成 ICMP Too Big/Fragmentation Needed;
  3. ICMP 被中间 policy 丢弃,或 sender 无法将它关联到 flow;
  4. 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”就完成。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 中声明 receiver 希望接收的 maximum TCP payload size。Typical IPv4 no-options upper bound 是 IP MTU - 20-byte IPv4 header - 20-byte TCP header,但 IPv4 options、IPv6 extension headers、TCP options 和 tunnel overhead 会改变 budget。所以“MSS 永远等于 MTU - 40”不是普遍公式。

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:

python
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 < 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

Calculator 不会自动知道 NIC offload、nested tunnel 或 provider network limit,result 要通过 end-to-end observation 验证。

9. Linux 诊断工具

bash
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 -s 1472 203.0.113.10

Command option 和 privilege 因 OS 而异,ping 成功也不证明 TCP/UDP 使用同一 path/policy。IPv4 header 有 options 时 1472 计算也要改变。实验只对自己有权测试的 endpoint 进行,并与 packet capture/counter 结合。

10. 验收问题

  1. IPv4 DF=0 和 DF=1 遇到小 outgoing MTU 时有何不同?
  2. IPv6 router 为什么不做 in-path fragmentation?
  3. 区分 classic PMTUD 与 PLPMTUD 的 feedback source。
  4. 为什么 MSS clamping 不能修复 UDP MTU black hole?
  5. 给出能支持 MTU-black-hole 结论的至少三项证据。

参考

Built with VitePress | Software Systems Atlas