Ubuntu下Raw UDP数据包丢失排查:疑校验和错误致Wireshark无法捕获
Raw Socket UDP数据包无法被Wireshark捕获的问题排查与修复
你的代码里有几个关键错误导致数据包被丢弃,以下是具体问题和修复方案:
主要错误点
- IP校验和计算长度错误:计算IP头校验和时,你用了
sizeof(ip_header),但ip_header是指针,这个值是4或8字节(取决于系统),而实际IP头长度是ihl * 4(ihl字段表示32位字的数量,每个字4字节)。错误的长度会导致校验和计算错误,路由器直接丢弃数据包。 - UDP长度字段未转网络字节序:UDP头的
len字段需要以网络字节序(大端)传输,你直接赋值8是主机字节序,会导致接收端解析错误。 - 伪头部UDP长度字段未转网络字节序:计算UDP校验和时用到的伪头部中,
udp_length同样需要是网络字节序,否则校验和计算错误。
修正后的代码
#include<iostream> #include<sys/ioctl.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<string.h> #include<linux/ip.h> #include<netdb.h> #include<linux/udp.h> struct pseudo_header { uint32_t source_address; uint32_t dest_address; uint8_t placeholder; uint8_t protocol; uint16_t udp_length; }; uint16_t calc_checksum(void* data, int len) { uint16_t* ptr = (uint16_t*)data; uint32_t sum = 0; while (len > 1) { sum += *ptr++; len -= 2; } if (len == 1) { sum += *(uint8_t*)ptr; } while(sum >> 16) { sum = (sum & 0xFFFF) + (sum >> 16) ; } return ~sum; } int main() { char sendbuff[28]; memset(sendbuff,0,28); iphdr* ip_header = (iphdr*)sendbuff; udphdr* udp_header = (udphdr*)(sendbuff + sizeof(iphdr)); pseudo_header psh; ip_header->version = 4; ip_header->ihl = 5; ip_header->id = htons(5234); ip_header->protocol = 17; ip_header->tos = 0; ip_header->tot_len = htons(28); ip_header->frag_off = 0; ip_header->ttl = 45; inet_pton(AF_INET,"192.168.8.144",&ip_header->saddr); inet_pton(AF_INET,"192.168.8.1",&ip_header->daddr); ip_header->check = 0; // 修正:用ihl*4作为IP头长度计算校验和 ip_header->check = calc_checksum(ip_header, ip_header->ihl * 4); char checksum_data[20]; memset(checksum_data,0,20); udp_header->source = htons(1134); udp_header->dest = htons(12345); // 修正:UDP长度转网络字节序 udp_header->len = htons(8); udp_header->check = 0; psh.dest_address = ip_header->daddr; psh.source_address = ip_header->saddr; psh.placeholder = 0; // 修正:伪头部UDP长度转网络字节序 psh.udp_length = htons(8); psh.protocol = ip_header->protocol; memcpy(checksum_data,&psh,sizeof(pseudo_header)); memcpy(checksum_data + sizeof(pseudo_header), udp_header,8); udp_header->check = calc_checksum(checksum_data,20); int s = socket(AF_INET,SOCK_RAW,IPPROTO_RAW); if(s == -1) { std::cout << "Could not open raw socket" << std::endl; return -1; // 新增:出错后退出,避免后续执行 } int optval = 1; if(setsockopt(s,IPPROTO_IP,IP_HDRINCL,&optval,sizeof(optval)) == -1) { std::cout << "Could not set socket option!" << std::endl; close(s); return -1; } sockaddr_in dstinfo; memset(&dstinfo,0,sizeof(dstinfo)); dstinfo.sin_addr.s_addr = ip_header->daddr; // 注:RAW socket发送时,sin_port会被忽略,因为我们自己构造了UDP头 dstinfo.sin_port = udp_header->dest; dstinfo.sin_family = AF_INET; if(sendto(s,sendbuff,28,0,(sockaddr*)&dstinfo,sizeof(dstinfo)) == -1) { std::cout << "Packet could not be sent!" << std::endl; close(s); return -1; } close(s); // 新增:关闭socket return 0; }
额外优化点
- 新增了错误处理后的资源释放(关闭socket)和退出逻辑,避免资源泄漏。
- 发送时的
dstinfo.sin_port对于IPPROTO_RAWsocket来说是无效的,内核会忽略这个字段,因为我们已经自己构造了完整的IP和UDP头,可以去掉这个赋值。
修复后,以root权限运行程序,应该就能在Wireshark中捕获到UDP数据包了。
内容的提问来源于stack exchange,提问作者ling978089
相关产品推荐
相关产品推荐

