如何修复原生ping实现中IPv4报文畸形问题以确保正确性?
问题排查与修复方案
1. 先修正IPv4头部长度的核心错误
IPv4头部的IHL字段是4位,单位是32位字(也就是4字节),不是总字节数。你手动设为40字节完全搞错了逻辑:
- 标准无选项的IPv4头部长度是20字节,对应IHL值为
5(20÷4) - 带选项的头部最多60字节,对应IHL值为
15 - 代码里直接写死字节数会让Wireshark完全无法识别头部结构,这是畸形报文的核心原因之一。
2. 校验和计算的常见坑点
不管是IPv4头部还是ICMP报文的校验和,都要注意这几点:
- 计算范围别错:IPv4校验和只覆盖IPv4头部(不含数据);ICMP校验和要覆盖整个ICMP报文(头部+数据)
- 字节序必须统一:所有16位字段(比如ID、序列号)要先转成网络字节序(大端)再参与计算,不能用主机字节序直接算
- 算法细节要严谨:
- 把报文按16位分组求和,字节数为奇数的话最后补个0字节
- 把求和结果的高16位和低16位相加,重复到结果只剩16位
- 对最终结果取反才是校验和
- 关键:计算前必须把校验和字段先设为0,再填充计算结果。
3. 其他容易忽略的畸形报文诱因
- IP版本字段:确保Version设为
4,别写成6或者其他值 - ICMP类型代码:ping用的回显请求类型是
8,代码是0,搞混的话Wireshark会识别成未知ICMP类型 - 总长度字段:IPv4的TotalLength是整个IP报文的总字节数(头部+ICMP数据),要动态计算,不能硬编码
- 原始套接字设置:Golang用原始套接字发送时,要设置
IP_HDRINCL选项,避免系统自动补全IP头部,导致报文里出现重复的头部结构
修正后的代码片段参考
正确初始化IPv4头部
type IPv4Header struct { Version uint8 // 高4位是版本,低4位是IHL,注意序列化时要合并 TOS uint8 TotalLen uint16 ID uint16 Flags uint16 TTL uint8 Protocol uint8 Checksum uint16 SrcIP [4]byte DstIP [4]byte } // 注意:Version和IHL要合并成一个字节(高4位Version,低4位IHL) ipHeader := IPv4Header{ Version: (4 << 4) | 5, // Version=4,IHL=5(对应20字节头部) TOS: 0, TotalLen: htons(uint16(20 + len(icmpPacket))), // IP头部+ICMP报文总长度 ID: htons(uint16(rand.Intn(65535))), Flags: 0, TTL: 64, Protocol: 1, // ICMP的协议号是1 SrcIP: [4]byte{192, 168, 1, 100}, // 替换成你的本地IP DstIP: dstIPBytes, // 目标IP的4字节数组 } // 计算校验和前先置0 ipHeader.Checksum = 0 ipHeader.Checksum = calculateChecksum(serializeIPv4(ipHeader))
ICMP报文的正确处理
type ICMPHeader struct { Type uint8 Code uint8 Checksum uint16 ID uint16 Seq uint16 } // 构建ICMP请求包 icmpHeader := ICMPHeader{ Type: 8, // 回显请求 Code: 0, Checksum: 0, ID: htons(uint16(os.Getpid() & 0xffff)), // 用进程ID当标识 Seq: htons(uint16(seqNum)), // 序列号自增 } // 拼接头部和数据 icmpData := []byte("ping test payload") icmpRaw := append(serializeICMP(icmpHeader), icmpData...) // 计算并填充校验和 icmpHeader.Checksum = calculateChecksum(icmpRaw) icmpRaw = append(serializeICMP(icmpHeader), icmpData...)
通用校验和计算函数
func calculateChecksum(data []byte) uint16 { var sum uint32 for i := 0; i < len(data); i += 2 { if i+1 < len(data) { sum += uint32(binary.BigEndian.Uint16(data[i:i+2])) } else { sum += uint32(data[i]) << 8 // 奇数字节补0 } } // 折叠高16位 for sum>>16 != 0 { sum = (sum & 0xffff) + (sum >> 16) } return ^uint16(sum) }
内容的提问来源于stack exchange,提问作者Swagnik Dutta
相关产品推荐
相关产品推荐

