2. UDP 报文格式:端口、长度、校验和与封装解封装
聊 UDP,绕不开它的报文格式。很多人觉得 UDP 简单,不就是「发出去就不管了」吗?
嗯,话是这么说,但真到抓包排障的时候,报文里每个字段都可能藏着坑。
今天我就带你把这 8 个字节的 UDP 头部彻底吃透。
2.1 源端口与目的端口
端口是什么?说白了就是「进程的门牌号」。
IP 地址把你带到某台主机,端口再把你带到主机上的某个应用。
- 源端口(16 bit):发送方的端口号。如果对方需要回包,这个字段就很重要。
- 目的端口(16 bit):接收方的端口号。这是报文能送到正确应用的关键。
我的一点经验: 很多初学者以为 UDP 不需要源端口。错。DNS 查询时,源端口通常是随机高位端口(比如 54321),目的端口固定是 53。服务器回包时,会把源端口和目的端口互换。没有源端口,回包就找不到回家的路。
端口范围是 0~65535。0~1023 是知名端口,需要管理员权限才能绑定。
1024~49151 是注册端口,49152~65535 是动态/私有端口。
我建议你在做客户端程序时,用 49152 以上的端口,避免跟系统服务冲突。
2.2 长度字段
长度字段占 16 bit,单位是字节。
它表示的是 UDP 头部 + UDP 数据 的总长度。
举个例子:
如果长度字段的值是 32,说明头部 8 字节 + 数据 24 字节。
最小值是 8(只有头部,没有数据),最大值是 65535。
我曾经踩过的坑: 有一次抓包发现某个设备发的 UDP 报文,长度字段比实际报文还大。接收端直接丢弃了。查了半天,是驱动层填长度时没算对。所以,长度字段必须精确,多一个字节少一个字节都不行。
你可能会问:IP 头里也有总长度字段,UDP 为什么还要自己记长度?
原因很简单:UDP 是传输层,它要保证自己能独立解析。万一 IP 层被分片了,UDP 还能靠自己的长度字段知道数据边界在哪。
2.3 校验和
校验和是 UDP 里最容易被忽视、也最容易出问题的字段。
它覆盖三部分内容:
- 伪头部(12 字节):包含源 IP、目的 IP、协议号、UDP 长度。这不是 UDP 报文的一部分,但参与校验。
- UDP 头部(8 字节)
- UDP 数据
计算方式很简单:发送方把所有 16 bit 字加起来,取反码,填入校验和字段。
接收方同样把所有 16 bit 字(包括校验和字段本身)加起来,结果应该是 0xFFFF。
关键点: IPv4 下 UDP 校验和是可选的(填 0 表示不校验),但 IPv6 下是强制的。我个人的习惯是:不管 IPv4 还是 IPv6,一律开启校验和。省得数据被篡改了还不知道。
伪头部为什么要参与校验?
因为 UDP 本身不检查 IP 地址。如果不加伪头部,攻击者可以伪造 IP 地址,把数据送到错误的端口。伪头部相当于把 IP 层的身份信息「绑定」到了 UDP 报文上。
2.4 UDP 报文封装与解封装过程
这部分我建议你结合下面的图来理解。
封装过程其实就三步:
- 应用层:产生数据,交给传输层。
- UDP 层:加上 UDP 头部(端口、长度、校验和),形成 UDP 报文。
- IP 层:再加上 IP 头部,形成 IP 数据报,交给链路层发送。
解封装就是反过来:
- IP 层收到数据报,检查 IP 头,去掉它,把剩下的交给 UDP 层。
- UDP 层先校验校验和。如果不对,直接丢弃(不通知发送方)。
- 校验通过后,根据目的端口找到对应的应用进程,把数据交上去。
我建议你记住: UDP 没有重传机制。校验和失败就丢包,应用层收不到任何通知。所以,如果你的应用对可靠性有要求,要么在应用层自己做确认和重传,要么换 TCP。
2.5 一张表总结 UDP 头部字段
| 字段 | 长度 | 作用 | 注意事项 |
|---|---|---|---|
| 源端口 | 16 bit | 标识发送方应用 | 可为 0(不需要回包时) |
| 目的端口 | 16 bit | 标识接收方应用 | 必须填写,否则无法交付 |
| 长度 | 16 bit | UDP 头部 + 数据的总长度 | 最小值 8,最大值 65535 |
| 校验和 | 16 bit | 校验头部、伪头部、数据 | IPv4 可选,IPv6 强制 |