发送ICMP报文时数据字段丢失的问题求助
问题分析与代码修正
你的代码存在两个核心错误,直接导致了ICMP数据字段丢失和校验和无效的问题:
1. sendto发送长度错误
sendto调用中,第三个参数使用了sizeof(icmp_data),但icmp_data是指针类型,sizeof(icmp_data)返回的是指针本身的字节数(64位系统下为8字节),远小于struct icmpdata的实际大小。这就导致你只发送了ICMP头部的一部分,数据字段完全没被发送。
修正方式:将发送长度改为sizeof(*icmp_data)或者sizeof(struct icmpdata),确保整个ICMP报文(头部+数据)被发送。
2. 校验和计算范围错误
当前校验和仅针对struct icmphdr计算,但ICMP协议要求校验和覆盖整个ICMP报文(头部+数据)。只计算头部的话,当包含数据时校验和必然无效,导致目标端丢弃报文,你收不到回复。
修正方式:计算校验和时,传入整个icmp_data结构体的地址,长度使用sizeof(struct icmpdata)。
其他优化点
- ICMP Echo请求的ID建议设置为当前进程PID(
getpid()),这样可以区分不同ping进程的报文,避免干扰。 - 循环发包时添加
sleep(1),避免疯狂发包占用资源。 - 增加回复报文的解析与打印逻辑,验证接收结果。
- getaddrinfo需要指定
hints.ai_family = AF_INET,避免解析到IPv6地址(当前代码针对IPv4)。
修正后的完整代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> #include <arpa/inet.h> #include <errno.h> struct icmpdata { struct icmphdr hdr; char data[32]; }; unsigned short compute_checksum(unsigned short* buffer, int size) { unsigned long cksum = 0; while (size > 1) { cksum += *buffer++; size -= sizeof(unsigned short); } if (size) { cksum += *(char*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >> 16); return (unsigned short)(~cksum); } struct icmpdata * buildRequestPacket () { struct icmpdata * icmp_data = malloc(sizeof(struct icmpdata)); memset(icmp_data, 0, sizeof(struct icmpdata)); icmp_data->hdr.type = ICMP_ECHO; icmp_data->hdr.code = 0; icmp_data->hdr.checksum = 0; icmp_data->hdr.un.echo.id = getpid(); // 用PID作为ID icmp_data->hdr.un.echo.sequence = 1; memcpy(icmp_data->data, "Hello, world!", strlen("Hello, world!")); // 计算整个ICMP报文的校验和 icmp_data->hdr.checksum = compute_checksum((unsigned short *)icmp_data, sizeof(struct icmpdata)); return icmp_data; } int main(int argc, char * argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <target_ip>\n", argv[0]); exit(EXIT_FAILURE); } struct addrinfo hints, * res; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; // 指定IPv4 int ret = getaddrinfo(argv[1], NULL, &hints, &res); if (ret != 0) { fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(ret)); exit(EXIT_FAILURE); } struct icmpdata * icmp_data = buildRequestPacket(); int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sockfd < 0) { perror("socket creation failed"); free(icmp_data); freeaddrinfo(res); exit(EXIT_FAILURE); } char * recvbuffer = malloc(128); if (!recvbuffer) { perror("malloc failed"); close(sockfd); free(icmp_data); freeaddrinfo(res); exit(EXIT_FAILURE); } int sequence = 1; while(1) { icmp_data->hdr.un.echo.sequence = sequence++; // 重新计算校验和(序列号变化了) icmp_data->hdr.checksum = 0; icmp_data->hdr.checksum = compute_checksum((unsigned short *)icmp_data, sizeof(struct icmpdata)); // 发送整个ICMP报文 ssize_t sent = sendto(sockfd, icmp_data, sizeof(*icmp_data), 0, res->ai_addr, res->ai_addrlen); if (sent < 0) { perror("sendto failed"); break; } // 接收回复 struct sockaddr_in src_addr; socklen_t addr_len = sizeof(src_addr); ssize_t recv_len = recvfrom(sockfd, recvbuffer, 128, 0, (struct sockaddr *)&src_addr, &addr_len); if (recv_len < 0) { perror("recvfrom failed"); break; } // 解析回复:跳过IP头部,获取ICMP部分 struct iphdr *ip_hdr = (struct iphdr *)recvbuffer; int ip_header_len = ip_hdr->ihl * 4; struct icmphdr *icmp_hdr = (struct icmphdr *)(recvbuffer + ip_header_len); // 验证是对应ID的ECHO回复 if (icmp_hdr->type == ICMP_ECHOREPLY && icmp_hdr->un.echo.id == getpid()) { printf("Received reply from %s: seq=%d, data: %.*s\n", inet_ntoa(src_addr.sin_addr), icmp_hdr->un.echo.sequence, (int)(recv_len - ip_header_len - sizeof(struct icmphdr)), (char *)(icmp_hdr + 1)); } sleep(1); } free(recvbuffer); free(icmp_data); freeaddrinfo(res); close(sockfd); return 0; }
验证说明
编译运行后,Wireshark会看到完整的ICMP报文(包含数据字段),同时终端会打印来自目标的回复,其中包含你发送的"Hello, world!"数据。注意运行raw socket需要root权限,执行时要加sudo。
内容的提问来源于stack exchange,提问作者bg92
相关产品推荐
相关产品推荐

