📚 第四章 IP协议
📄 一句话概括

路径 MTU 是「整条路上最窄那一段的 MTU」,源端只能靠试出来。 方法是「设 DF 发出去,被拒就调小」,靠 ICMP 的「需要分片」消息反馈。

📝 本节和前面几节的关系

4.5.2 讲分片怎么切,这一节讲「怎么避免切」。 4.5.1 的 MTU 差异是它的动机,这里给出解决办法。

一、源端为什么不知道

因为路由是逐跳决定的,源端看不到整条路。 它只知道「我这块网卡是 1500」,不知道中间哪一段是 1400、哪一段是 1280。 如果硬按 1500 发,中间设备有两个选择:切(违反发包人的意愿)或者丢(回一个错误)。 IPv4 允许前者,IPv6 只允许后者。

版本 中间设备过不去时 源端怎么知道
IPv4 可以偷偷分片 往往不知道,性能悄悄变差
IPv6 只能回 ICMPv6「包太大」 一定知道

第一行那个「偷偷分片」是 IPv4 的一个设计缺陷: 源端以为自己在发 1500 的包,实际上路上被切成了三片,丢包率和延迟都由源端「不知情地」承担。 所以现代系统默认开 DF,宁可报错也不让中间切。

二、探测的流程

逻辑简单到三句话: 第一步,按本机 MTU 发一个 DF=1 的包。 第二步,如果收到「需要分片」的 ICMP,就把包调小再发。 第三步,直到能通为止,这个尺寸就是路径 MTU。

第几次 包长 结果
1 1500 被拒
2 1450 被拒
3 1400 通过 → 定为 1400

实际实现不是「一次减 50」这么笨: Linux 是在「已知能通的下界」和「已知不通的上界」之间二分,几次就能收敛。 但无论怎么优化,本质都是「试」。 试出来的结果会缓存一段时间, 所以同一个目的地只有第一次慢一点——这也是「新连接第一下特别慢」的一个可能原因。

三、MSS 是怎么定下来的

TCP 不直接说「我要发多大的包」,而是说「我能收多大的段」: 这个值叫 MSS,等于 MTU 减两个头。

场景 MTU MSS
以太网 + IPv4 1500 1460
PPPoE + IPv4 1492 1452
以太网 + IPv6 1500 1440

第三行比第一行少 20,只因为 IPv6 的头是 40 字节: 同样的链路,跑 IPv6 每个包少装 20 字节数据。 这是 IPv6 的一个「隐性成本」,但换来的是「路由器不用再算校验和」。 握手时双方各报一个 MSS,取小的那个:所以「客户端 1460、服务器 1440」时,实际用的是 1440。 这一步在 SYN 包里完成,用户感觉不到。

四、PMTU 黑洞

这是最经典的故障之一:中间设备把 ICMP 拦了,源端收不到「需要分片」的消息。 结果就是:小包能通(不用分片),大包永远卡住(发了没回应)。 症状是「能上网、打不开网页」「能 ping 通、传不了文件」。

包的大小 要不要分片 结果
小(< 1400) 不用 通
大(1500) 要 被丢,且没有反馈

第二行那个「没有反馈」是关键: 不是「被拒绝」,是「静默丢弃」——所以抓包只能看到「发出去了,没回来」。 解决办法有两个: 一是放行 ICMP 的「需要分片」类型(type 3 code 4);二是直接把 MSS 钳到保守值(比如 1400)。 前者正确,后者粗暴但有效——很多 VPN 默认就这么干。

五、动手 Demo:路径 MTU 是「试」出来的

模拟一次探测:从本机 MTU 往下试,直到通过为止。 最后一张表是「同一个 MTU 在不同封装下的 MSS」。

mtu, path = 1500, 1400
while mtu >= 1200:
    print(mtu, '通过' if mtu <= path else '被拒')
    if mtu <= path:
        break
    mtu -= 50

实跑输出:

--------------------------------------------------------------------
路径 MTU 是「试」出来的
--------------------------------------------------------------------
**源端并不知道整条路上最小的 MTU 是多少**:**它只能「设 DF 位发出去,被拒了就调小,再试」。**

**假设本机 MTU 1500,路径上最窄的一段是 1400**:
  第几次     试探的包长     结果
  ---------------------------------------
  1          1500           被拒(过大)
  2          1450           被拒(过大)
  3          1400           通过

**一共试了 3 次,最后定在 1400**——
**这就是「路径 MTU 发现」(PMTUD)的全部逻辑,简单到不像个协议。**

**定下来以后,MSS 是算出来的**:
  组合                    对应 MTU     MSS
  ------------------------------------------
  以太网 + IPv4 + TCP     1500         1460
  PPPoE + IPv4 + TCP      1492         1452
  以太网 + IPv6 + TCP     1500         1440
  PPPoE 里再套一层 IP     1472         1432

**第一行 1460 是「标准值」**:**1500 减 20 字节 IP 头、减 20 字节 TCP 头。**
**第三行 MTU 同样是 1500,MSS 却少 20**——**因为 IPv6 的头固定 40 字节,不是 20。**

**第四行是「隧道」的坑**:**外面再套一层 IP(比如 VPN),MTU 就从 1492 掉到 1472。**
**如果没跟着调 MSS,就会出现「小包能通、大包卡死」的经典故障**——
**因为小包不用分片,大包被中间设备默默丢了。**

三次就试出来了:1500 被拒、1450 被拒、1400 通过。 真实实现会比这更聪明(二分),但「被拒就降」这个循环是一样的。 「一共试了 3 次」这句话里藏着代价:每次试探都要一个往返, 如果路径长(比如跨洋 200 毫秒),三次就是 600 毫秒的等待。 所以缓存很重要——不然每个新连接都要付这笔钱。 最后那张 MSS 表最值得记的是第四行: PPPoE 里再套一层 IP,MTU 从 1492 掉到 1472,MSS 只剩 1432。 这就是「VPN 之后网速变慢」的一个具体原因——不是带宽小了,是每个包能装的数据少了。

30 秒实验

  1. 算一下:路径 MTU 是 1400 时,TCP 的 MSS 是多少?
  2. 想一下:为什么 PMTU 黑洞的症状是「小包通、大包不通」?
  3. 想一下:为什么 IPv6 不允许中间路由器分片?

常见误区

  1. 以为「路径 MTU 就是本机 MTU」。 不是,是整条路上最窄那一段的值。
  2. 以为「PMTU 发现是协议规定的必须行为」。 是靠 ICMP 反馈实现的,ICMP 被拦就失效。
  3. 以为「MSS 就是 MTU」。 差 40 或 60 字节,要减掉 IP 头和 TCP 头。
自测三问(点开对答案) 1. 路径 MTU 发现靠什么机制实现? —— **发 DF=1 的包,被拒后靠 ICMP「需要分片」消息调小**,直到能通为止。 2. 什么是 PMTU 黑洞? —— **中间设备拦掉了 ICMP 反馈**,源端不知道包太大,于是一直发、一直丢,表现为「小包通、 大包不通」。 3. 以太网上跑 IPv6 的 TCP,MSS 是多少? —— **1440**,等于 1500 减 40 字节 IPv6 头再减 20 字节 TCP 头。

下一节进入 IPv6:地址不够用之后,协议被重新设计了一遍。

相关笔记