IP报头中数据报长度字段不一致问题排查
问题描述
我尝试从IP报头开始解析ICMP报文,使用的Go代码如下:
func main() { fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_ICMP) if err != nil { fmt.Println(err) return } for { buf := make([]byte, 1024) numRead, err := syscall.Read(fd, buf) if err != nil { fmt.Println(err) } fmt.Println(hex.EncodeToString(buf[:numRead])) } }
操作步骤:
- 运行该应用;
- 使用Wireshark监听ICMP报文,过滤规则为
ip.src==192.168.1.109||ip.dst==192.168.1.109; - ping google.com以捕获回显应答。
预期Wireshark与应用捕获的报文一致,但实际两者仅IP报头前20字节中的**数据报长度字段(第3-4字节)**不同:Go捕获的是4000,Wireshark捕获的是0054,请求解答该差异的原因。
可能的原因分析
1. 字段位置混淆(最可能)
IP报头的**总长度字段(Total Length)位于偏移2-3字节(从0开始计数,对应你说的第3-4字节),而标识字段(Identification)**位于偏移4-5字节。如果你误将标识字段当成了总长度字段,就会出现数值差异——标识字段通常是随机生成的,而总长度字段是固定的(对应ping报文的84字节,即0054)。
IP报头前20字节的关键字段位置参考:
| 偏移(字节) | 字段 |
|---|---|
| 0-1 | 版本 + 报头长度 |
| 2-3 | 总长度(Total Length) |
| 4-5 | 标识(Identification) |
| ... | ... |
2. 字节序解读错误
IP报头的所有数值字段均采用网络字节序(大端),Wireshark会自动将其解析为十进制值展示,但你直接打印原始十六进制字节时,可能误将字节顺序按主机字节序(小端)解读。不过你描述的4000与0054数值差距过大,这个可能性较低。
可以在代码中添加字段解析逻辑验证:
import "encoding/binary" // 在syscall.Read之后添加: if numRead >= 4 { totalLength := binary.BigEndian.Uint16(buf[2:4]) fmt.Printf("解析后的总长度(十进制):%d,十六进制:0x%04x\n", totalLength, totalLength) }
如果解析后的十进制值为84(与Wireshark一致),说明你只是误读了原始字节的顺序。
3. 内核对原始套接字的字段修改
在Linux系统中,当通过SOCK_RAW接收ICMP报文时,极少数情况下内核会修改IP报头的总长度字段(比如特殊的内核参数或套接字配置)。但这种情况非常罕见,且通常会伴随其他字段的变化,与你描述的“仅该字段不同”不符。
4. Windows系统的特性差异
如果你的代码运行在Windows系统下,Windows的原始套接字行为与Linux不同:内核可能会将IP报头的数值字段转换为主机字节序返回给用户空间,导致原始字节与Wireshark展示的网络字节序不一致。
内容的提问来源于stack exchange,提问作者Commander_Rex

