摘要

  • RFC 768 为 UDP 校验和字段中的零规定了两种不同含义:计算结果为零时发送 0xFFFF,而线上 0x0000 表示发送方没有生成校验和。
  • 1989 年的主机规范保留了省略选项,但要求默认计算。IPv6 后来将校验和设为必需,2013 年又为严格限定的隧道场景增加例外。

一个字段里的两个零

1980 年 8 月,Jon Postel 起草的 RFC 768 定义了用户数据报协议(UDP),让应用程序用尽可能少的协议机制交换数据报。它不保证按序送达,不提供重复保护,也不承诺可靠交付。头部只有八个字节:源端口、目的端口、长度和校验和,后面是数据。

最后一个字段看起来简单,却碰上了算术中的特殊情况。UDP 使用 16 位反码校验和,TCP 也采用同类计算。校验覆盖 UDP 头部和数据,还包括一个伪首部,其中含有 IP 源地址、目的地址、协议号和 UDP 长度。Postel 解释说,伪首部有助于防止数据报被错误地送到别处。

在反码算术中,零有两种位模式:全 0 和全 1。RFC 768 给它们分配了不同的线上含义。计算结果为零时,发送方必须把字段写成全 1。相反,如果线上字段是全 0,就表示发送方没有生成校验和。这样,字段不增加标志位,也同时承载了计算结果和一个状态信号。

本文的历史焦点就在这里。UDP 的“零校验和”不是计算所得的零值,而是对“未计算”的编码。混淆这两种状态,可能使实现错误丢弃有效数据报,也可能把缺失的差错检测误当成已完成校验。

可选不等于默认

原始规范允许省略校验和,例如用于调试,或用于不关心校验的上层协议。九年后,规定互联网主机要求的 RFC 1122 明确了运行规则:主机必须实现校验和的生成与验证;应用程序可以控制是否生成,但默认必须开启。接收方也可以选择是否将没有校验和的数据报交给应用程序。

RFC 1122 还指出一个常见实现错误:计算结果为零时必须发送 65535,而全零字段代表“缺省”。文档称,一些只在局域网中运行的应用曾为提高效率而关闭 UDP 校验,并报告过许多未检测出的错误。它没有提供数量、具名事件或统计分母。因此,这段话证明标准作者记录过一种担忧,却不能作为其普遍程度的测量。

校验和从来不是数字签名。伪首部可以帮助发现某些错投递,计算也能发现部分意外损坏;但它不能认证发送方、阻止蓄意篡改,也不能证明应用数据在语义上正确。“带有校验和”和“可信数据包”是两种不同判断。

IPv6 收紧了边界

1998 年的 IPv6 规范 RFC 2460 要求 UDP 必须带校验和。若计算结果为零,也要写为 0xFFFF;接收方应丢弃校验和字段为零的 IPv6 UDP 数据报。于是,过去可选的“未计算”标记在普通 IPv6 UDP 流量中不再可接受。

随后出现了一个具体的性能场景。2013 年,RFC 6935 和 RFC 6936 更新了 IPv6 的校验和处理规则,适用于受限的 UDP 隧道封装。隧道内层数据包可能已有自己的完整性校验,而计算外层校验和会产生额外成本。例外需要满足适用条件、两端行为和中间设备兼容性要求;它并没有把零校验和变成 IPv6 的普遍选项。

这段标准演变显示,位模式没变,允许使用它的上下文却变了。IPv4 中,零可以标记缺省,而主机规范仍把计算设为默认。IPv6 起初拒绝缺省,之后才为特定的外层隧道用途有条件地恢复。单看数值,接收方无法知道省略是否可以接受;IP 版本和用途同样重要。

来源