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

包比链路的 MTU 大,就要切成分片,切在出口,拼在终点。 三个字段管这件事:标识、标志(DF/MF)、片偏移,偏移以 8 字节为单位。

📝 本节和前面几节的关系

4.5.1 讲了 MTU 不一致,这一节讲「不一致时怎么办」。 4.7 的 IPv4 首部会再看到这三个字段,这里先把它们的用途讲透。

一、什么情况下会分片

触发条件只有一个:要发的包比「出接口的 MTU」大。 注意是「出接口」:同一个包从 1500 的网口发出去不用切,从 1492 的 PPPoE 口发就要切。 所以分片是「逐跳」的事,不是「端到端」的事。

包长 出接口 MTU 结果
1400 1500 直接发
1500 1500 直接发(正好)
1501 1500 切两片

第三行那个「多 1 字节就要切」看着很蠢,但规则就是这样: MTU 是硬上限,没有「差不多」的余地。 所以实际部署时会把 MTU 留一点余量,比如隧道场景设 1400。

二、管分片的三个字段

IPv4 首部里有三个字段专门为分片服务:

字段 作用 取值
标识 同一组的编号 同一包的所有片相同
DF 不许分片 1 表示「宁可丢也别切」
MF 后面还有片 1 表示不是最后一片
片偏移 这一片在原包的哪个位置 单位 8 字节

第二行 DF 是「路径 MTU 发现」的关键: 发的时候把 DF 置 1,中间设备过不去就只能报错,不能偷偷切。 这样源端才能知道「路太窄了」。 第一行「标识」为什么必须相同: 因为网络上同时有很多包在飞,接收方要靠它区分「这些片是不是一家的」。 现代实现通常用「源地址 + 目的地址 + 标识 + 协议」四元组来判定。

三、为什么偏移以 8 字节为单位

因为片偏移字段只有 13 位。 13 位最大 8191,如果按字节算,只能表示 8191 字节——连一个 9000 字节的包都描述不了。

单位 13 位能表示的最大偏移 够不够用
1 字节 8191 字节 不够,太小
8 字节 65528 字节 够,覆盖 65535 上限

第二行那个 65528 刚好覆盖 IP 包的最大长度 65535—— 这就是选 8 而不是 16 或 32 的原因:够用,又最细。 代价是「每片的数据长度必须是 8 的倍数」,最后一片除外——因为剩下多少就装多少。

四、重组只在终点做

中间路由器只分片,不重组:它没有「等所有片到齐」的时间,也不该有。 重组发生在目的主机——因为在到达之前,谁也不知道下一段路有多宽。 这个设计有一个直接后果:分片越多,中间设备要转发的「件数」越多,开销越大。 所以工程上更愿意「在源端就切好」,这就是 TCP 的 MSS 协商和 PMTUD 存在的理由。 IPv6 干脆取消了「中间路由器分片」:只有源端能分,路上过不去就回一个「包太大」。 这一刀把路由器最讨厌的活砍掉了——细节在 4.6.8 讲。

五、分片的代价

一句话:掉一片,全包作废。 IP 层没有「重传某一片」的机制,重组超时就整包丢弃,让 TCP 重传整个报文。

片数 单片的丢包率 整包成功率
1 片 1% 99%
3 片 1% 97%
10 片 1% 90.4%

第三行说明「分片对成功率的伤害是指数的」: 10 片各 1% 的丢包,整包成功率只剩 90.4%(0.99 的 10 次方)。 这就是「分片一多,吞吐就塌」的数学解释。 还有一层开销容易被忽略:每片都要带一份 20 字节的首部, 切 3 片就多花 40 字节的「信封」。切得越碎,信封占比越高。

六、动手 Demo:4000 字节的包,被切成什么样

把 4000 字节的包按 MTU 1500 切一遍,看每片的长度、偏移和 MF。 注意偏移的两种写法:一种以 8 字节为单位,一种是真实字节。

payload, mtu = 4000 - 20, 1500
step = (mtu - 20) // 8 * 8
off = 0
while payload > 0:
    take = min(step, payload)
    payload -= take
    print(take, off // 8, 1 if payload else 0)
    off += take

实跑输出:

--------------------------------------------------------------------
4000 字节的包,被切成什么样
--------------------------------------------------------------------
**分片的三个参数都在这张表里**:**每片的数据长度、偏移量,还有 MF 标志。**
**偏移量的单位是 8 字节**,所以每片的数据长度必须是 8 的倍数——**最后一片除外。**

  片号     数据长度     偏移(8 字节单位)     偏移(字节)     MF     整片长度
  ------------------------------------------------------------------------------
  1        1480         0                      0                1      1500
  2        1480         185                    1480             1      1500
  3        1020         370                    2960             0      1040

**加起来对得上**:3980 = 1480 + 1480 + 1020
**三片各自的「整片长度」都不超过 1500**:1500、1500、1040

**MF 那一列是「后面还有没有」**:**前两片是 1,最后一片是 0。**
**接收方就是靠「偏移」把碎片按位置摆好,靠「MF=0」知道拼完了。**

**代价是「一片丢了,整个包都白传」**:
**IP 层没有「重传某一片」的机制**——**重组失败就整包丢弃,让上层(TCP)重传整个报文。**
**所以分片对性能的伤害不是线性的,是「掉一片等于全丢」。**

**这也是为什么「路径 MTU 发现」值得做**:**能在源端就把包切到合适大小,中间就不用分了。**

最后一片 1020 字节不是 8 的倍数,这不是错:规则只要求「非最后一片」的长度是 8 的倍数, 因为只有它们的「下一片偏移」需要被 8 整除。 最后一片没有下一片,剩下多少就装多少。 偏移那一列的两个 1480 和 2960 是「累计字节数」: 第一片从 0 开始,第二片从 1480 开始,第三片从 2960 开始。 除以 8 得到 185 和 370——这就是首部里真正写进去的值。 整片长度 1500、1500、1040 说明「除了最后一片,前面都塞满」: 塞满是为了少切几片,少切一片就少一次「掉一片全丢」的风险。

30 秒实验

  1. 算一下:MTU 1280 时,4000 字节的包切成几片?偏移分别是多少?
  2. 想一下:为什么片偏移字段的单位是 8 字节而不是 1 字节?
  3. 想一下:为什么中间路由器不负责重组?

常见误区

  1. 以为「分片是在源端一次性切好的」。 是逐跳的,每段的出口 MTU 不同,可能被切好几次。
  2. 以为「路由器会等所有片到齐再转发」。 不会,路由器只按片转发,重组在目的主机。
  3. 以为「丢一片只重传那一片」。 整包重传,IP 层没有「补发单片」的机制。
自测三问(点开对答案) 1. 什么时候会触发分片? —— **要发的 IP 包比出接口的 MTU 大**的时候,在出口切。 2. 片偏移的单位是什么?为什么? —— **8 字节**。 因为字段只有 13 位,按字节算最大只能描述 8191 字节,按 8 字节算能到 65528, 刚好覆盖 IP 包的上限。 3. MF 位和 DF 位分别表示什么? —— **MF=1 表示「后面还有片」,DF=1 表示「不许分片」**。DF 是路径 MTU 发现的关键。

下一节讲「怎么在源端就知道该切多大」:路径 MTU 发现。

相关笔记