IPv4 头校验和只盖住 IP 头,不盖住后面的载荷。计算时先把这 16 位当成 0,再对头里全部 16 位字取一的补码和,字段里放这个和的一的补码。头被改过就要重算。算法在 1981 年 9 月的 RFC 791。IPv6 基本头没有这一字段。
求和的范围只有头
一个可独立引用的定义句:IPv4 头校验和是 16 位,只覆盖 IP 头。它是头里全部 16 位字的一的补码和,再取一的补码;计算时这个字段本身当作 0(RFC 791,头校验和字段)。
引言写明:它用来核对处理数据报所用的信息有没有传错。数据里可以有差错。校验失败时,发现错误的实体立刻丢掉这份数据报。互联网协议不对数据做差错控制,只有头校验和。第 3.2 节说,检测到的错误可以经由 ICMP 报告。那一句没有把头校验和失败单独写成必须发出 ICMP。本文不把「立刻丢掉」写成「一定有一条 ICMP」。
头长(IHL)是 4 位,单位是 32 位字,正确的头最小是 5。分片一节写明头最长可以到 60 个八位组。5 乘 4 等于 20,原文在 IHL 那一段没有把 20 印出来。算法句说的是头里的全部 16 位字,因此选项和填充都在范围内。填充是为了把头补到 32 位边界的零。算法句没有把「填充」两个字再写一遍。
| 字节 | 是否进入 IPv4 头校验和 |
|---|---|
| 版本、头长、总长度、地址、生存时间、协议 | 在头里,进入 |
| 选项和填充 | 在头里,进入 |
| 头校验和字段自身 | 计算时当作 0;核验时再加回去 |
| TCP、UDP 或隧道里的内层头 | 对这一层 IPv4 头来说是数据,不进入 |
1981 年的文本把算法称作临时的,并写它以后可能被 CRC 换掉。2026 年 10 月 22 日的元数据仍把 RFC 791 标成 Internet Standard,没有把它标成已被取代。更新者是 RFC 1349、2474 和 6864。本文没有重读这三份正文,因此不判断它们有没有改写头校验和那一段。服务类型这一字节后来如何被差分服务重新解释,见 差分服务标记。它仍是头里的字节。
RFC 791 早于用来规定大写关键词的 RFC 2119。下文不把原文的小写 must 升级成后来的大写必须。
从零重算,以及只改一个字段
一个可独立引用的定义句:头被改过,头校验和就要重算。RFC 791 举的例子是生存时间减少、互联网选项的增加或改变,以及分片。分片可能改动的字段清单里,第六项就是头校验和。
核验的一种写法不在 RFC 791,而在 RFC 1071 第 1 节:把校验和字段一并加进去,结果若是全 1(一的补码里的 −0),核验通过。该文写明自己不是标准,只是一组实现技术。它的元数据列出更新者是 RFC 1141。已验证勘误 560、4918 和 7711 改的是第 4 节示例代码里的星号、指针步进和一处拼写,不改第 1 节这三步。Held for Document Update 的 3133 本文没有读完。本文不引用那段示例代码。
只改一个 16 位字段时,RFC 1624 给出增量更新。它是 1994 年 5 月的 Informational,更新 RFC 1141,自己没有被标成标准。引言把生存时间更新、IP 分片和源路由更新列为适用的操作。源路由选项的格式本文没有重读。
它先记下 RFC 1141 的等式 2:HC' = HC + m + ~m'。然后说明,一的补码里有两个零:全 0 是 +0,全 1 是 −0。非零输入相加可以得到 −0,得不到 +0。IP 头里至少有一个非零字段,校验和又是和的补码,因此这个字段可以是 ~(-0),也就是 0x0000,按该文的推理不会是 0xFFFF。等式 2 在边界上会得到 −0,和从零重算不一致。该文给出的等式 3 是 HC' = ~(~HC + ~m + m')。第 9 节建议实现使用等式 3 或等式 4;等式 4 是 HC' = HC - ~m - m'。本文只复算第 4 节已经印出的那一组。
| 步骤 | 第 4 节印出的十六进制 |
|---|---|
| 其余字节的一的补码和 | 0xCD7A |
| 旧字段 m | 0x5555 |
| 旧校验和 | ~(0xCD7A + 0x5555) = ~0x22D0 = 0xDD2F |
| 新字段 m’ | 0x3285 |
| 从零重算 | ~(0xCD7A + 0x3285) = ~0xFFFF = 0x0000 |
| 等式 2 | 0xFFFF |
| 等式 3 | 0x0000 |
原文把 0xCD7A + 0x5555 直接写成 0x22D0,没有把进位过程印出来。0x0000 在这个例子里是算出来的结果,不是「关掉校验和」的保留值。UDP 校验和 一文写的是另一套:算出 0 时线上要写成全 1。不要把「IP 头里不会是 0xFFFF」挪到 TCP 或 UDP 上。那句推理用的是「IP 头里至少有一个非零字段」。
2026 年 10 月 22 日的勘误页上,RFC 1624 没有 Verified 条目。Held for Document Update 的 4782 和 5864 讨论等式 3 在字段其实没变、旧校验和又是 0 时会不会得到 0xFFFF,以及一的补码加法要不要把进位加回去。两条都还不是已验证的改写。本文不把其中一条当成已经改过的规范。
RFC 791 的已验证勘误里,716 改的是流标识选项的长度图,6356 改的是分片伪代码里的头长。两条都不改头校验和的算法句。该页标着 Verified (9),其余条目本文没有逐条读完。
外层算外层,内层仍是数据
封装再加一重 IPv4 头时,外层校验和只加外层头里的 16 位字。内层头对外层来说是数据。外层这份通过了,并不证明内层头没有差错。
沿途路由器减少的是它正在转发的那一头的生存时间。外层是 IPv4 时,这一减改的是外层头,外层头校验和就要按上一节重算。路由跟踪 写的是封装之后生存时间有两层。本文补的是:外层这一层若是 IPv4,减生存时间会碰到外层头校验和。
IP-in-IP 与 GRE 一文写,这次封装若是转发的一部分,入口把内层生存时间减一。减的是内层头里的字段。按 RFC 791,那一头被改过,那一头的校验和就要重算。本文没有重读 RFC 2003,所以不断言那一份封装规范有没有把校验和三个字印出来。同一篇里的 GRE 校验和盖住的是 GRE 头和载荷,不是外层投递头。两份和不要并成一份。
分片会改头。哪些字段用来把片拼回去,见 IP 分片。本文只保留「头校验和在受影响字段的清单里」这一句。
地址也在头里。改地址就要重算头校验和。传输层校验和还会通过伪首部再盖住地址,范围见 TCP 校验和。两份都要动,是因为地址同时出现在 IP 头和伪首部里。出口 NAPT 讨论内层源地址何时被改写。本文不提供改写步骤。头校验和防的是传输差错,不是认证。改头的人可以同时改这个字段。本文也不提供这种改法。
RFC 8200 第 3 节的 IPv6 基本头图里没有校验和字段。第 8.1 节写明:IPv6 头里 ICMP 所依赖的那些字段,不像 IPv4,没有被互联网层校验和盖住。第 3 节的跳数限制由每个转发节点减 1。这一减没有 IPv4 那种头校验和字段要跟着改。第 5 节仍要求每条链路的 MTU 至少 1280 个八位组。这句只说明链路下限,不把 IPv6 头补出一个校验和字段。
现象对照
| 现象 | 更可能先看什么 |
|---|---|
头校验和是 0x0000 | 按 RFC 1624,可以是算出来的 +0,不是「没有计算」 |
头校验和是 0xFFFF | 该文写明,按它的推理 IP 头里不会出现。它和从零重算的结果不是同一个值 |
| 只改了生存时间,校验和却原样留下 | 与「头被改过就要重算」不符 |
| 外层校验失败 | 丢掉的是这一层 IPv4 数据报。内层头是否完好,要另算 |
| 外层是 IPv6 | 基本头没有这一字段。减跳数限制不用按 IPv4 去改头校验和 |
| 载荷里有一位翻转,头校验和仍通过 | RFC 791 允许数据里有差错。载荷要看上一层自己的校验和 |
核对清单
- 只加头里的 16 位字:载荷不在范围内。IHL 最小是 5,头最长 60 个八位组。选项和填充在头里;
0x0000可以是结果:这是 RFC 1624 的信息性说明,并论证 IP 头里不会是0xFFFF。不要挪到 TCP 或 UDP;- 改头就要重算:生存时间、选项和分片。等式 3 来自 RFC 1624,不是 RFC 791 里的大写必须。4782 与 5864 仍是 Held;
- 外层不证明内层:外层失败丢掉外层数据报。内层生存时间若被改,重算的是内层那一头。RFC 2003 本文没有重读;
- IPv6 基本头没有这一字段。RFC 791 的更新者三份正文没有重读。已读的勘误 716 和 6356 不改算法,其余已验证条目没有读完。