4. UDP校验和计算:校验和的计算范围(伪首部+UDP报文)、伪首部的构造、校验和计算步骤与示例

UDP校验和,说实话,是很多网络工程师容易忽略的一个细节。我记得刚入行那会儿,总觉得UDP是“不可靠传输”,校验和可有可无。直到有一次在项目中排查丢包问题,才发现校验和计算错误会导致接收端直接丢弃报文——嗯,那会儿我花了一整天才定位到问题。

今天我们就来彻底搞懂UDP校验和。我会从计算范围讲起,再到伪首部的构造,最后手把手带你算一遍。

4.1 校验和的计算范围

很多人以为UDP校验和只算UDP报文本身,其实不对。它算的是三部分:伪首部 + UDP首部 + UDP数据

你可能会问:伪首部是什么?它不是真正的IP首部,而是从IP首部里“借”来的几个字段。说白了,UDP校验和要覆盖IP层的一些信息,这样才能检测报文是否被错误地送到了别的机器上。

计算范围总结:

  • 伪首部(12字节)—— 从IP首部提取
  • UDP首部(8字节)—— 源端口、目的端口、长度、校验和
  • UDP数据(可变长度)—— 应用层载荷

这里有个坑:校验和字段本身在计算时填0。如果校验和计算结果为0,则全填0xFFFF(因为0是保留值,表示未计算校验和)。

4.2 伪首部的构造

伪首部不是真的在网络中传输的,它只在计算校验和时“临时存在”。构造方式如下:

字段 长度(字节) 说明
源IP地址 4 IP首部中的源地址
目的IP地址 4 IP首部中的目的地址
保留(全0) 1 固定为0
协议类型 1 UDP为17(0x11)
UDP长度 2 UDP首部+数据的总长度

伪首部一共12字节。我习惯把它想象成一个“粘合剂”,把IP层和传输层的信息粘在一起做校验。这样即使IP层转发错了,UDP层也能发现。

个人经验: 我在做嵌入式网络协议栈时,经常需要手动构造伪首部。最容易出错的是字节序——网络字节序是大端,千万别搞反了。我曾经因为这个bug,调试了整整一个下午。

4.3 校验和计算步骤

计算步骤其实很简单,就是二进制反码求和。我一步步拆给你看:

  1. 拼接数据:伪首部(12字节)+ UDP首部(8字节,校验和字段填0)+ UDP数据
  2. 补齐长度:如果总字节数是奇数,末尾补一个0字节(凑成偶数)
  3. 按16位分组:每2个字节组成一个16位整数
  4. 反码求和:把所有16位整数相加,进位加到最低位(循环进位)
  5. 取反:对最终的和取反,得到校验和

你想想看,这个过程其实和IP首部校验和很像,只是计算范围更大。为什么用反码?因为反码求和在硬件上实现简单,而且能检测出所有奇数个位的错误。

4.4 计算示例

光说不练假把式。我们来算一个实际的例子。

假设数据如下:

  • 源IP:192.168.1.1(0xC0A8 0101)
  • 目的IP:192.168.1.2(0xC0A8 0102)
  • 协议类型:17(0x11)
  • UDP长度:8(只有首部,无数据)
  • 源端口:1234(0x04D2)
  • 目的端口:5678(0x162E)
  • UDP数据:无

第一步:构造伪首部

源IP:       0xC0A8 0101
目的IP:     0xC0A8 0102
保留+协议:   0x0011
UDP长度:    0x0008

第二步:拼接所有数据

伪首部:   C0A8 0101 C0A8 0102 0011 0008
UDP首部:  04D2 162E 0008 0000  (校验和字段填0)
总数据:   C0A8 0101 C0A8 0102 0011 0008 04D2 162E 0008 0000

第三步:按16位分组求和

0xC0A8 + 0x0101 = 0xC1A9
0xC1A9 + 0xC0A8 = 0x18251  → 进位1,结果为0x8252
0x8252 + 0x0102 = 0x8354
0x8354 + 0x0011 = 0x8365
0x8365 + 0x0008 = 0x836D
0x836D + 0x04D2 = 0x883F
0x883F + 0x162E = 0x9E6D
0x9E6D + 0x0008 = 0x9E75
0x9E75 + 0x0000 = 0x9E75

第四步:处理进位

没有进位,直接取反:~0x9E75 = 0x618A

最终校验和:0x618A

把这个值填到UDP首部的校验和字段里,就完成了。

避坑指南: 我曾经在项目中遇到一个奇怪的问题——两台机器之间UDP通信偶尔丢包。排查了半天,发现是发送端计算校验和时忘了把伪首部算进去。接收端一校验,发现对不上,直接丢弃。所以记住:伪首部必须参与计算,这是RFC 768明确规定的。

4.5 校验和验证

接收端收到UDP报文后,会做同样的计算。如果计算出的校验和等于0(注意是反码求和后的结果),说明数据完整。为什么是0?因为发送端已经把校验和填进去了,接收端把所有16位整数(包括校验和字段)加起来,如果没出错,结果应该是全1(0xFFFF),取反后就是0。

嗯,这里要注意:如果UDP校验和为0,表示发送端没有计算校验和。这在某些场景下是允许的(比如IPv4),但IPv6强制要求计算校验和。

我个人建议:永远不要关闭UDP校验和。虽然UDP本身不可靠,但校验和至少能保证数据不被篡改或错传。我在生产环境中见过太多因为校验和问题导致的诡异故障了。

好了,UDP校验和的计算就讲到这里。下一节我们会聊UDP的封装与解封装过程,到时候你会看到校验和在整个流程中扮演的角色。