原始Socket缓冲区中IP与UDP头访问异常:指针偏移问题排查
问题:AF_PACKET原始Socket解析UDP头时指针偏移错误
我通过AF_PACKET类型的原始Socket接收数据包,接收代码如下:
struct msghdr msg; struct iovec iov; unsigned char buf[BUFSIZE] = { '\0', }; ssize n; int fd; ... fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); ... msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; iov.iov_base = buf; iov.iov_len = sizeof(buf); ... n = recvmsg(sockfd, &msg, 0); ... parse_pkt(buf, BUFSIZE);
数据包接收正常,但解析时遇到问题:
static int parse_packet(unsigned char *pkt, int len) { struct iphdr *ip = (struct iphdr *)(pkt + 14); struct udphdr *udp; /* ip指针有效,可访问IP头字段,未被修改 */ ... udp = (struct udphdr *)(ip + (ip->ihl << 2)); /* 预期udp指针指向IP头之后的位置,但实际并非如此 */ ... }
上述代码中udp指针并未指向预期位置。数据包包含完整的无VLAN以太网头,ip指针已跳过以太网头,理论上ip + (ip->ihl << 2)应跳过IP头,但实际无效。而以下写法却能正常工作:
struct iphdr *ip = (struct iphdr *)(pkt + 14); ... udp = (struct udphdr *)(pkt + 14 + (ip->ihl << 2));
请问这是什么原因?我哪里出错了?
解答
核心问题是混淆了C语言中结构体指针与字节指针的算术运算规则:
- 当你使用
struct iphdr*类型的指针ip做加法时,ip + N的计算逻辑是:(unsigned char*)ip + N * sizeof(struct iphdr),也就是偏移N个struct iphdr结构体的大小,而不是偏移N个字节。 - 你的代码中
ip->ihl << 2得到的是IP头的实际字节长度(因为ihl是32位字的个数,乘以4就是字节数),但用ip + 这个值时,相当于跳过了(ihl*4)个IP头的大小,这显然远远超过了实际需要偏移的字节数,导致udp指针指向错误的位置。
正确的做法是先将结构体指针转换为字节级指针(比如unsigned char*),再进行字节偏移:
udp = (struct udphdr *)((unsigned char *)ip + (ip->ihl << 2));
这样转换后,加法运算就会按字节数来偏移,加上IP头的实际字节长度后,就能准确指向UDP头的起始位置。
你后来的写法pkt + 14 + (ip->ihl << 2)能正常工作,是因为pkt本身就是unsigned char*类型,所有偏移都是按字节计算的,相当于直接从原始数据包起始位置跳过以太网头(14字节)和IP头(ihl*4字节),自然能定位到UDP头。
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

