基于C语言计算IP校验和遇问题:对照RFC1071代码仍无效
排查IP校验和计算代码的问题
我帮你梳理几个关键问题,这些都是导致代码无法正常工作的核心原因:
1. 未清零校验和字段(最致命的错误)
RFC 1071明确要求,计算IP首部校验和时,必须先将首部的chksum字段设置为0。因为校验和是对除自身外所有首部字段的16位字求和后取反得到的。你的代码完全跳过了这一步,计算时会包含原来的校验和值,直接导致结果彻底错误。
2. 结构体字节对齐隐患
虽然你的IP结构体看起来总长度是20字节,但C编译器默认的对齐规则可能会在字段之间插入填充字节(比如为了对齐uint32_t类型),导致结构体实际大小大于20字节。此时你的代码遍历整个结构体时,会把这些无效的填充数据加入求和,破坏校验和计算。
3. 奇数字节处理不符合RFC要求
当首部长度为奇数时(虽然20字节是偶数,但代码逻辑需要严谨),RFC要求将最后一个字节作为16位字的高字节(低字节补0)加入总和。你的代码直接将其作为低字节加入(sum += *(uint8_t*)buf),这会导致数值计算错误。
4. 字节序不匹配问题
IP首部的所有字段都采用网络字节序(大端),如果你的结构体中存储的是主机字节序(比如小端机器上的本地字节序),直接将结构体转换为uint16_t*遍历计算时,每个16位字的字节顺序会颠倒,导致求和结果错误。
修正后的代码示例
首先修正IP结构体(解决对齐问题)
使用编译指令强制1字节对齐,同时修正字段命名更符合IP首部定义:
// 强制1字节对齐,避免填充字节(GCC/Clang可用__attribute__((packed)),MSVC用#pragma pack) #pragma pack(push, 1) typedef struct ip_h { uint8_t ver_ihl; // 高4位=版本,低4位=IHL(首部长度) uint8_t tos; uint16_t tot_len; uint16_t id; uint16_t frag_off; uint8_t ttl; uint8_t proto; uint16_t chksum; uint32_t src; uint32_t dst; } IP; #pragma pack(pop)
修正后的校验和计算函数
uint16_t calculate_ip_chksum(IP *ip) { uint16_t *buf = (uint16_t *)ip; uint32_t sum = 0; // 从ver_ihl字段提取IHL(低4位),计算首部总字节数 int counter = (ip->ver_ihl & 0x0F) * 4; // 关键步骤:先清零校验和字段 ip->chksum = 0; // 遍历所有16位字求和 while (counter > 1) { // 如果结构体存储的是主机字节序,需要先转网络字节序再相加 // 如果是直接从网络数据包映射的结构体,直接用*buf即可 sum += ntohs(*buf++); counter -= 2; } // 处理奇数字节(符合RFC要求:作为高字节,补0低字节) if (counter == 1) { sum += (*(uint8_t *)buf) << 8; } // 折叠32位总和的高位到低16位 while (sum >> 16) { sum = (sum & 0xFFFF) + (sum >> 16); } // 取反后转网络字节序返回(IP首部校验和是网络字节序) uint16_t checksum = ~sum; return htons(checksum); }
额外注意事项
- 如果是构造IP首部,所有多字节字段(如
tot_len、id、src、dst)都需要用htons()/htonl()转换为网络字节序后再存入结构体。 - 如果是解析收到的数据包,直接将网络缓冲区映射到
IP结构体即可,不需要转换字节序,直接计算校验和。
内容的提问来源于stack exchange,提问作者S. Sharma
相关产品推荐
相关产品推荐

