You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

发送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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:23:15