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

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_RAW socket来说是无效的,内核会忽略这个字段,因为我们已经自己构造了完整的IP和UDP头,可以去掉这个赋值。

修复后,以root权限运行程序,应该就能在Wireshark中捕获到UDP数据包了。

内容的提问来源于stack exchange,提问作者ling978089

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 19:57:34