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

IPv6 首部固定 40 字节、8 个字段,没有长度可变这回事。 关键差异只有三处:长度字段含义变了、校验和没了、选项改成了扩展首部。

📝 本节和前面几节的关系

4.7 刚讲完 IPv4 首部,这一节逐项对照。 4.6.2 提过「首部简化」,这里看到具体的字节布局。

一、40 字节的布局

前 8 字节是控制信息,后 32 字节是两个地址。 没有可选项,长度永远是 40。

字节 内容 长度
0 版本 + 流量类别 + 流标签的高 4 位 4 字节一起
4~5 有效载荷长度 2 字节
6 下一个首部 1 字节
7 跳数限制 1 字节
8~23 源地址 16 字节
24~39 目的地址 16 字节

第一行要注意「三个字段共用 4 个字节」: 版本 4 位、流量类别 8 位、流标签 20 位,加起来 32 位。 所以读的时候要先取一个 32 位整数再拆,不能像 IPv4 那样一个字节一个字节读。 第五、六行占了 32 字节: 所以 IPv6 首部的 80% 是地址—— 这是「地址变长」的直接代价,也是它「看起来没那么简洁」的原因。

二、三处关键差异

只看这三处就够了:

项 IPv4 IPv6
长度字段 总长度(含首部) 有效载荷长度(不含)
校验和 有,每跳重算 没有
选项 首部内,长度可变 扩展首部,可跳过

第一行是最容易算错的地方: 同一个 1500 字节的包,IPv4 里总长度写 1500,IPv6 里有效载荷长度写 1460。 差 40,正好是首部长度。 用抓包工具看的时候,这个差经常让人以为「包被截断了」。 第二行「没有校验和」的连带效果: IPv4 里 TTL 一变就要重算校验和,IPv6 里跳数限制变了什么都不用做。 这就是「路由器少干一次活」的具体含义。

三、扩展首部怎么串起来

每个扩展首部的头一个字节都是「下一个首部」, 它指向下一层的类型——可能还是扩展首部,也可能是 TCP。

下一个首部的值 含义 下一层是什么
0 逐跳选项 还是扩展首部
43 路由 还是扩展首部
44 分片 还是扩展首部
6 TCP 传输层,到头了
17 UDP 传输层,到头了

这张表就是「链」的实现方式:一层层往下走,直到遇到 6 或 17 才停下。 所以「解析 IPv6 包」比「解析 IPv4 包」多了一个循环—— 但循环的次数通常只有 0 到 1 次(大多数包没有扩展首部)。 第四、五行那两个值很关键:它们和 IPv4 的「协议号」用的是同一套编号, 所以「6 是 TCP」这个知识可以直接迁移。

四、这笔交易的得失

换来的是「转发更快」:固定长度、无校验和、字段更少,硬件可以流水线处理。 付出的是「扩展变复杂」:要加功能就得串链,链太长设备就不愿意跟。

维度 IPv4 首部 IPv6 首部
转发速度 慢(要算长度和校验) 快(固定偏移)
扩展能力 靠选项,兼容性差 靠扩展链,灵活
实现难度 两种都要处理 只处理常见的几种

第三行那句「只处理常见的几种」是现实: 很多设备只认「逐跳、路由、分片」这几种扩展首部,遇到不认识的直接丢。 所以 RFC 才建议「扩展首部尽量放在两端处理」。 一句话总结这两节:IPv4 的首部是「功能多但笨重」,IPv6 的首部是「核心小但要求高」。 选哪个不是对错问题,是「愿意把复杂度放在哪儿」的问题。

五、动手 Demo:IPv6 首部固定 40 字节

拼一个 40 字节的 IPv6 首部,按字段读回来,再算地址占了多大比例。 注意「有效载荷长度 1460」和 IPv4 的「总长度 1500」差 40。

import struct, ipaddress
src = ipaddress.IPv6Address('2001:db8::1').packed
dst = ipaddress.IPv6Address('2001:db8::2').packed
hdr = struct.pack('!IHBB', 6 << 28, 1460, 6, 64) + src + dst
print(len(hdr))
print(len(src), len(dst))

实跑输出:

--------------------------------------------------------------------
IPv6 首部固定 40 字节
--------------------------------------------------------------------
**IPv6 的首部没有「长度可变」这回事**:**前 40 字节永远是这 8 个字段,扩展首部接在后面。**

**长度 40 字节,前 8 字节是控制信息**:

**00: 60 00 00 00**
**04: 05 B4 06 40**

  字段             值       说明
  -----------------------------------------------
  版本             6        固定 6
  流量类别         0        类似 IPv4 的区分服务
  流标签           0        给「同一股流」打标
  有效载荷长度     1460     **不含**这 40 字节
  下一个首部       6        6 是 TCP
  跳数限制         64       每过一跳减一

**第四行最容易搞错**:**IPv4 的「总长度」把首部算在里面,IPv6 的「有效载荷长度」不算首部。**
**同一个 1500 字节的包,两个字段的值差 40。**

**地址各占 16 字节**:
**源 2001:db8::1 占 16 字节,目的 2001:db8::2 占 16 字节**
**加起来 8 加 32 正好 40,一分不多一分不少。**

**所以 IPv6 转发的伪代码可以很短**:**读 40 字节、看「下一个首部」、减跳数、查表、换 MAC、发出。**
**没有「首部有多长」的判断,也没有「校验和要重算」的步骤**——
**这两条就是 IPv6 让硬件转发更快的原因。**

**代价是「扩展」变复杂了**:
**要加功能就得串一条「扩展首部链」,每一节都要按「下一个首部」往下走。**
**链太长,路由器就不愿意跟了**——**所以规范建议把扩展首部放在两端处理。**

6 << 28 这个写法就是「把版本塞进高 4 位」:6 左移 28 位,低 28 位留给流量类别和流标签。 所以一个 32 位整数里同时装了三个字段。 len(hdr) 是 40,len(src) 和 len(dst) 都是 16: 40 减 32 得 8,正好是那 8 字节的控制信息。 「80% 是地址」这个说法就是这么来的。 实测对比一下: IPv4 首部 20 字节里地址占 8 字节(40%),IPv6 首部 40 字节里地址占 32 字节(80%)。 所以「首部变大了一倍」这句话不准确——变大的是地址,控制信息反而从 12 字节缩到了 8 字节。

30 秒实验

  1. 算一下:IPv6 首部里地址占了多少百分比?
  2. 想一下:为什么「有效载荷长度」不含首部?
  3. 想一下:扩展首部链太长会有什么问题?

常见误区

  1. 以为「IPv6 首部里有效载荷长度等于总长度」。 差 40,IPv6 的字段不含首部。
  2. 以为「IPv6 首部也是 20 字节」。 是 40,因为地址从 4 字节变成了 16 字节。
  3. 以为「扩展首部越多越好」。 链太长设备不跟,很多设备只认常见的几种。
自测三问(点开对答案) 1. IPv6 首部固定多少字节?地址占多少? —— **固定 40 字节,两个地址各 16 字节,合计占 80%。** 2. 有效载荷长度包含首部吗? —— **不包含**,只算 40 字节之后的部分。 3. 下一个首部字段的值 6 和 17 分别是什么? —— **6 是 TCP,17 是 UDP**,和 IPv4 的协议号是同一套编号。

第四章到此结束。第五章讲 IP 的配套技术:DNS、ARP、NAT、DHCP。

相关笔记