摘要
- IPv4 的校验和只覆盖首部。路由器验证收到的首部,递减 TTL 或执行合法的分片修改,再为新的首部状态生成校验和;它从未保证载荷无误。
- TCP 与 UDP 把传输层首部、数据和一段概念性的伪首部纳入同一运算。伪首部把源地址、目的地址、协议和长度绑定进验证范围,用来发现部分错投,却不证明发送者身份或路径合法性。
- 反码运算有正零与负零。RFC 1141 的增量公式可能得到
0xFFFF,而完整重算得到0x0000;RFC 1624 修复了这个边界。IPv6 随后移除了基本首部校验和,但没有取消上层的完整性责任。
一张每跳都会改写的凭条
如果把 IP 首部想成封死的信封,校验和的设计就很难理解。IPv4 的首部在旅途中本来就会变化。每个处理数据报的模块至少把 TTL 减一;分片会改变总长度、标志和片偏移;某些选项也可能在途中更新。
RFC 791 因而没有要求路由器保存发送端留下的一枚永久印章。它规定:把首部看成一组十六位字,以反码加法求和,再对结果取反码;计算时校验和字段自身按零处理。覆盖范围明确止于首部。
路由器收到数据报后,先检查眼前这版首部是否一致;若校验失败就丢弃。若需要继续转发,它修改 TTL 等允许变化的字段,再为修改后的版本更新校验和。前一跳的值已经完成使命,下一跳看到的是一条新的、局部生成的陈述。
这条陈述非常有限。RFC 791 同时说明 IP 不提供数据差错控制、确认、重传或流量控制。一个通过 IPv4 首部校验的数据报,载荷仍可能损坏。校验和保护的是网络层据以处理数据报的字段,不是数据报携带的一切事实。
同一算法,三条边界
“互联网校验和”听上去像覆盖整包的一层保护,实际却至少有三种不同范围。
IPv4 只计算自己的首部。RFC 768 让 UDP 计算 UDP 首部、数据与伪首部。RFC 793 则让 TCP 计算 TCP 首部、正文和九十六位伪首部。三者使用相近的反码运算,却回答不同的问题。
这种分层避免了一个代价:如果端到端的 TCP 或 UDP 校验包含会在每跳递减的 TTL,那么每台路由器都必须改写传输层结果。实际设计让路由器只更新 IPv4 首部校验,端点保留对传输层字节的责任。链路层帧可能还有自己的差错检测,它又只在一段链路上生效。
因此,一包数据可以同时通过多个检查,却没有任何一个检查自动升级为总证明。链路检查没有说明远端路径;IPv4 首部检查没有说明载荷;TCP 或 UDP 校验也没有说明发送者是谁。
一段不出现在报文里的首部
伪首部是一个有意构造的计算对象。UDP 把 IP 源地址、目的地址、协议号和 UDP 长度放在 UDP 首部与数据之前参与求和。TCP 采用相同思想,把源地址、目的地址、协议号和 TCP 长度放入九十六位伪首部。
它不作为独立首部在线路上传输。接收端从 IP 与传输层字段重新构造它。RFC 768 和 RFC 793 给出的理由是防止错投:一段 TCP 或 UDP 内容即使自身未变,若被交给错误的地址或错误的上层协议,也不应轻易通过验证。
这里的“绑定”不能读成认证。能够构造报文的发送者也能计算校验和;有意改动受保护字节的中间设备同样可以重算。伪首部不证明地址的登记归属,不证明路由策略正当,也不保证数据没有经过截取。它只使某些字段之间的意外错配变得可见。
这个虚构结构之所以优雅,是因为它跨层取证,却没有合并权力。传输层借用自己依赖的 IP 事实来验证交付环境,并不因此获得控制路由或裁定地址所有权的资格。
UDP 为什么不能只保留一个零
反码运算允许两个零:全零位模式 0x0000 是正零,全一位模式 0xFFFF 是负零。纯粹从算术结果看,两者可以等价;进入协议字段后,它们却承担不同含义。
RFC 768 规定,若 UDP 计算出的校验和为零,发送端在线路上写入全一。全零字段则表示发送端没有生成 UDP 校验和。于是 0xFFFF 代表一次得到零结果的有效计算,0x0000 代表 IPv4 UDP 原始契约允许的省略。
这不是奇怪的编码装饰,而是在“检查结果为零”与“根本没有检查”之间留出可辨识边界。实现若因为内部算术把两个零视作相同,便会抹去协议层的语义。
RFC 8200 在 IPv6 中收紧了规则。默认情况下 UDP 校验不可省略,计算结果为零仍写作 0xFFFF,接收方必须丢弃全零字段。标准为受严格条件约束的 UDP 隧道封装保留了例外,但那不是普遍的可选校验和。
允许每台机器用自己的快法
校验和必须作用于每一包数据,成本因此也是互操作问题。RFC 1071 记录了反码加法可供实现利用的性质。
只要保持字节的奇偶位置,求和可以交换顺序,也可以任意拆成若干组后再合并。实现可按任一字节序计算并在适当位置交换结果;可以用更宽的累加器推迟回卷进位,也可以展开循环或使用并行路径。输入长度为奇数时,计算末尾补零,但补位并不发送。
协议统一的是覆盖范围和最终位模式,而不是处理器内部的循环。不同年代、不同字长的机器都能选择本地高效的方法。这种自由有严格条件:改变字节配对、丢掉最后一个奇数字节或错误处理回卷进位,都会产生另一个协议结果。
所以,运行代码的自由来自可验证的共同输出,而不是“各自差不多”。边缘条件与普通数据同样属于契约。
TTL 减一,不必重算一切
路由器修改 TTL 时,已经知道是哪一个十六位字发生变化。与其重新遍历整个 IPv4 首部,不如从旧总和中去掉旧字,再加入新字。
RFC 1141 把这个增量过程写成实现方法。TTL 减一时,根据字节位置,在存储的校验和上用反码加法增加 1 或 256 即可。RFC 1624 还列出分片与源路由更新等场景。
它让计算成本与改变规模相称。稳定的源地址和目的地址不必因为 TTL 的一次递减而再次参与全部运算。但增量结果必须与对新首部完整重算完全一致;只在大多数输入上相同,仍然是协议错误。
一个公式误入了另一个零
问题出在看似最不值得担心的位置。RFC 1624 说明,RFC 1141 的表达式隐含使用了一个在结果为零时不成立的分配性质。
标准给出具体例子:某十六位字段从 0x5555 变成 0x3285,其余首部字节之和为 0xCD7A。对新首部完整重算,得到存储值 0x0000;使用 RFC 1141 的方法,却得到 0xFFFF。
在 IPv4 首部字段里,两者不能任意互换。IP 首部保证至少有一个非零字段;非零输入的反码相加可以产生负零,却不能产生正零。最终再取反后,合法的首部校验和可以是 0x0000,却不应是 0xFFFF。旧公式生成了完整重算不可能生成的表示。
RFC 1624 的修正式为 HC' = ~(~HC + ~m + m'),其中加法都遵循反码规则。它不再跨越那个无效的分配步骤,因此恢复了与完整计算的等价性。
并非所有接收端都会立即暴露错误。按 RFC 1071 把收到的校验和字段也加入总和、再与负零比较的实现,在示例中会接受两种表示。另一些实现会重新计算并直接与字段值比较,它们可能拒绝非规范结果。产品测试正是在这种差异中发现了失败条件,随后通过分析与模拟验证修复。
宽容的接收端不能替生产者解除规范责任。协议要求发送一条规范陈述,而不是赌下一台机器愿意替它解释。
IPv6 把检查移到了别处
RFC 8200 的 IPv6 基本首部没有互联网层校验和。首部包含版本、流量类别、流标签、载荷长度、下一个首部、跳数限制和一百二十八位地址,但没有 IPv4 的十六位字段。
这不表示 IPv6 放弃完整性。TCP、UDP 以及 ICMPv6 使用包含 IPv6 源地址、最终目的地址、上层长度和下一个首部值的伪首部。RFC 8200 特别解释,ICMPv6 纳入伪首部,正因为相关 IPv6 字段不像 IPv4 那样受到互联网层校验保护。
IPv6 改变的是责任位置。IPv4 让每台路由器检查并更新一个会随跳变化的首部声明,即使链路与上层往往已有相邻检查。IPv6 去掉这项重复的基本首部工作,把端点依赖的字段留给上层协议保护。
这不是从“弱”简单升级到“强”,而是重新划分边界:保留兼容且必要的端到端计算,移除一次每跳重复计算,并让 UDP 的默认省略通道更窄。
来源与不确定性
RFC 768 规定 UDP 范围、伪首部和两个线路零值;RFC 791 规定 IPv4 首部校验;RFC 793 规定 TCP 范围;RFC 1071 说明快速实现性质;RFC 1141 给出增量更新;RFC 1624 记录边界失败与修正;RFC 8200 展示 IPv6 的新边界。
这些文献不能证明某一位唯一发明者,也不能说明今天所有硬件卸载方式或厂商默认值。它们没有给出本文可安全引用的漏检概率。校验通过无法区分自然一致与有意改动后的重算;校验失败也不能单独指出哪条链路、哪台设备或哪位行为者造成了变化。
可确认的历史事实更窄:互联网通过明确哪些字节参与、谁负责更新、结果在哪里失效,让许多差错能够以低成本被本地发现。
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
