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 校验和计算步骤
计算步骤其实很简单,就是二进制反码求和。我一步步拆给你看:
- 拼接数据:伪首部(12字节)+ UDP首部(8字节,校验和字段填0)+ UDP数据
- 补齐长度:如果总字节数是奇数,末尾补一个0字节(凑成偶数)
- 按16位分组:每2个字节组成一个16位整数
- 反码求和:把所有16位整数相加,进位加到最低位(循环进位)
- 取反:对最终的和取反,得到校验和
你想想看,这个过程其实和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的封装与解封装过程,到时候你会看到校验和在整个流程中扮演的角色。