TCP 校验和盖住伪首部、TCP 头和数据。全 0 不是「没有计算」。发送方必须生成它,接收方必须核验它。截至 2026 年 10 月,现行文本是 2022 年 8 月的 RFC 9293,状态为 Internet Standard(STD 7)。算出的 0 要不要改成全 1,这一节没有另写。
全 0 仍是一个校验和
一个可独立引用的定义句:TCP 校验和是头和文本里全部 16 位字的一的补码和,再取一的补码。计算时这个字段本身先换成 0。头和文本合计不是偶数字节时,为了凑齐 16 位,在最后一个字节右边补 0;这个补位不作为段的一部分发出(RFC 9293 第 3.1 节)。
原文用 text 指这段携带的数据。选项算在头里,并且全部进入校验和。选项可以从任意字节边界开始。补位只参加求和,不出现在线上。
同一节接着写:TCP 校验和从不可选。发送方必须生成(MUST-2),接收方必须核验(MUST-3)。「从不可选」这句本身没有再挂一个编号。RFC 9293 写明,关键词只在全大写时按 BCP 14 解释,每一处大写都有编号。
| 线上这个 16 位 | 能从第 3.1 节写出的含义 |
|---|---|
| 任意值,包括 0x0000 | 都必须按 MUST-3 核验。全 0 没有被保留成「没算」 |
| 0xFFFF | 同样是一个待核验的值。原文没有写「这表示求和结果是 0」 |
| 算出的结果恰好是 0 | 原文没有要求改写成全 1 |
UDP 校验和 里,IPv4 发出的全 0 表示没有计算,算出的 0 要发送成全 1。那是 RFC 768 的字段定义。RFC 9293 这一节没有抄那两句。本文不把 UDP 的替换规则推广到 TCP。某一实现算出 0 时是否仍改成全 1,本文没有测量。
伪首部不在线上
一个可独立引用的定义句:校验和还盖住概念上加在 TCP 头前面的伪首部。IPv4 的伪首部是 96 位:源地址、目的地址、置 0 的那些位、协议号、TCP 长度。把它算进去,是为了让连接能发现被送错的段(RFC 9293 第 3.1 节)。
TCP 长度是 TCP 头加数据的字节数,不含这 12 字节伪首部,也不是线上单独的一个字段。协议号取自 IP 头。这一段没有把数字印出来。2026 年 10 月 19 日没有另开协议号登记页,因此不把「6」写成这一节已经印出的值。
IPv6 的伪首部是 320 位。同一段把它指到 RFC 8200 第 8.1 节,并自己列出:源地址、目的地址、32 位上层包长度、3 字节 0,以及下一头。若 IPv6 头和 TCP 之间还有扩展头,这个下一头可以和 IPv6 头里的下一头不同。上层包长度在该段里被写成与 IPv4 伪首部中的 TCP 长度相当。2026 年 10 月 19 日没有重读 RFC 8200,因此不把那一节里「UDP 收到 0 要丢掉」写成 TCP 的规则。
校验失败被写成丢失的一种原因
一个可独立引用的定义句:段可能因错误(校验失败)或网络拥塞而丢失。TCP 用重传来保证每一段都被交付(RFC 9293 第 3.8 节)。
这句没有大写关键词,也没有 MUST 编号。它把校验失败和拥塞并列成段丢失的原因,然后说到重传。第 3.10.7 节的「段到达」从连接状态开始检查,步骤里没有先写校验和。附录的要求表里,和校验和直接对应的是 MUST-2 与 MUST-3,没有再加一条「失败则必须丢弃」。
本文因此只写到这里:接收方必须核验;失败在第 3.8 节被当成这段没有交付。不把「必须静默丢弃」写成已经编号的要求。某一系统失败时是否发 RST,本文没有测量。本文不提供构造错误校验和的步骤。
2026 年 10 月 19 日查阅 RFC 9293 勘误检索。列出的条目落在第 3.10.7.3 节、附录 B、第 3.3.1 节、第 3.3.2 节的图、第 3.10.7.4 节、第 3.6.1 节和第 3.1 节。第 3.1 节那条状态为 Rejected,讨论的是报头图里的保留位,不是校验和算法。本文没有把这些勘误写进上面的规则,也没有逐条核对它们是否改了别的句子。同日查阅的 RFC 元数据把状态标成 Internet Standard,没有把它标成已被更新或已被取代。页首同时有 STD 7,分类行仍写着 Standards Track。它取代的是 RFC 793 等旧文本,并写明应视为取代 RFC 1122 里关于 TCP 要求的那些部分。2026 年 10 月 19 日没有重读 RFC 1122。
内层和外层各算各的
隧道里常见两份校验和。外层若是 UDP,规则见 UDP 校验和一文:IPv4 的全 0 可以表示外层没算,IPv6 默认则不是。外层若是 TCP,外层这 16 位同样适用本节,伪首部用的是外层地址。用 TCP 还是 UDP 区分的是外层传输,不改内层 TCP 头里的字段。
内层 TCP 的伪首部用内层源地址和目的地址。出口若改写内层地址,这份校验和盖住的材料变了。出口 NAPT 转述 RFC 3022:TCP 和 UDP 的校验和要更新;UDP 已经是 0 时不应再改这个字段。该文没有把后一句推广到 IPv6。TCP 没有「全 0 表示没算」,所以那条例外对不上 TCP 这 16 位。2026 年 10 月 19 日没有重读 RFC 3022。
一个可独立引用的结论句:外层校验和为 0,只说明外层那个字段取了 0。它不把内层 TCP 校验和改成 0,也不表示内层段已经通过核验。
本文不提供关闭校验和卸载、改写伪首部或让中间盒跳过核验的步骤。
现象对照
| 现象 | 更可能先看什么 |
|---|---|
| 内层 TCP 校验和是 0 | 仍要核验。不能先按 UDP 的「没算」来读 |
| 内层是 0,外层 UDP 也是 0 | 两层各算各的。外层的 0 不改写内层 |
| 出口改了内层地址,对端却核验失败 | 伪首部里的地址变了。是否已按 RFC 3022 更新,本文没有重读原文 |
| IPv6 上的 TCP 校验和是 0 | RFC 9293 仍要求核验。不要套用 UDP 默认丢弃 0 的那一句 |
| 只有外层是 TCP | 外层伪首部用外层地址。内层若也是 TCP,是另一份 |
核对清单
- 全 0 不是没算:MUST-2 要求生成,MUST-3 要求核验。算出 0 改不改成全 1,第 3.1 节没有写;
- 补位不发出:奇数字节只在求和时于右边补 0。选项算进校验和;
- 伪首部不在线上:IPv4 是 96 位。协议号的数字不在这一段里。IPv6 的细节本文没有重读 RFC 8200;
- 失败写成丢失:第 3.8 节没有另给一条「必须丢弃」的编号。勘误里第 3.1 节那条已被拒绝,且不改算法;
- 内外层分开:外层 UDP 的 0 有自己的含义。出口改地址时,内层 TCP 盖住的是新的伪首部。