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

Raw Socket UDP客户端发包:tcpdump可见但UDP服务器未接收

Raw Socket构造UDP包无法被SOCK_DGRAM服务器接收排查

我正在学习C语言Raw Socket,编写了基于SOCK_DGRAM的UDP服务器和Raw Socket客户端,目标是手动构造UDP包并通过Raw Socket发送至UDP服务器。客户端和服务器编译运行均无错误或警告,使用netcat -u 172.18.100.181 3333测试时,服务器能正常接收并打印数据包。但使用自制客户端发送数据包时,尽管tcpdump和Wireshark能捕获到相关流量且无错误标记,UDP服务器却未接收到任何数据。

服务器代码(server.c)

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>

#define BUFSIZE 1500

int main(void)
{
    int result;

    struct addrinfo hints;
    struct addrinfo* server_infos;
    struct addrinfo* cursor;
    memset(&hints, 0, sizeof(struct addrinfo));
    //setting hints
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_DGRAM;
    getaddrinfo("172.18.100.181", "3333", &hints, &server_infos);

    int udpsocket_fd;
    for(cursor = server_infos; cursor != NULL; cursor = cursor->ai_next)
    {
        udpsocket_fd = socket(cursor->ai_family, cursor->ai_socktype, cursor->ai_protocol);
        if(udpsocket_fd == -1)
            continue;
        result = bind(udpsocket_fd, cursor->ai_addr, cursor->ai_addrlen);
        if(result == 0)
            break;
    }
    if(cursor == NULL)
    {
        printf("No suitable interface\n");
        exit(1);
    }
   
    while(1)
    {
        char buffer[BUFSIZE];
        memset(buffer, 0, BUFSIZE);
        result = recvfrom(udpsocket_fd, buffer, BUFSIZE, 0, NULL, NULL);
        for(int i = 0; i < BUFSIZE; i++)
        {
            if(i % 16 == 0)
                printf("\n");
            printf("%c ", buffer[i]);
        }
    }

return 0; 
}

客户端代码(client.c)

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <net/ethernet.h>
#include <unistd.h>
#include <netinet/ether.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <errno.h>
#include <linux/if_packet.h>
#include <netinet/if_ether.h>

#define PACKET_BUFFER 1024
#define UDP 17

unsigned short ip_checksum(struct iphdr* ip_header, int ip_header_length)
{
    short* cursor = (short*) ip_header;
    int checksum = 0;
    for(int i = 0; i < ip_header_length/2; i++)
    {
        checksum += htons(cursor[i]);
        checksum = (checksum & 0x0000ffff) + (checksum >> 16);
    }

return (unsigned short) (~checksum);
}

int main(void)
{   
    int result;
    int rawsocket_fd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
 
    char packet[PACKET_BUFFER];
    memset(packet, 0, PACKET_BUFFER);

    //ethernet header
    struct ethhdr* ethernet_header = (struct ethhdr*) packet;
    //source mac
    ethernet_header->h_source[0] = 0x00;
    ethernet_header->h_source[1] = 0x15;
    ethernet_header->h_source[2] = 0x5d;
    ethernet_header->h_source[3] = 0x6c;
    ethernet_header->h_source[4] = 0xae;
    ethernet_header->h_source[5] = 0x2b;
    //dest mac
    ethernet_header->h_dest[0] = 0x00;
    ethernet_header->h_dest[1] = 0x15;
    ethernet_header->h_dest[2] = 0x5d;
    ethernet_header->h_dest[3] = 0x6c;
    ethernet_header->h_dest[4] = 0xae;
    ethernet_header->h_dest[5] = 0x2b;
    //protocol
    ethernet_header->h_proto = htons(ETH_P_IP);

    //ip header
    struct iphdr* ip_header = (struct iphdr*) (ethernet_header + 1);
    ip_header->version = 4;
    char ip_saddr[INET_ADDRSTRLEN] = "172.18.100.181";
    char ip_daddr[INET_ADDRSTRLEN] = "172.18.100.181";
    inet_pton(AF_INET, ip_saddr, &(ip_header->saddr));
    inet_pton(AF_INET, ip_daddr, &(ip_header->daddr));
    ip_header->ihl = 5;
    ip_header->protocol = UDP;
    ip_header->ttl = 64;
    ip_header->tos = 16;
    ip_header->id = htons(1212);
    ip_header->check = ip_checksum(ip_header, 20);

    //udp header
    struct udphdr* udp_header = (struct udphdr*) (ip_header + 1);
    udp_header->source = htons(12001);
    udp_header->dest = htons(3333);
    udp_header->check = 0;

    //raw data (payload)
    char* raw_data = (char*) (udp_header + 1);
    unsigned short raw_data_length = 0;
    raw_data[raw_data_length++] = 0x61;
    raw_data[raw_data_length++] = 0x62;
    raw_data[raw_data_length++] = 0x63;
    raw_data[raw_data_length++] = 0x64;
    raw_data[raw_data_length++] = 0x65;
    raw_data[raw_data_length++] = 0x66;
    raw_data[raw_data_length++] = 0x67;
    raw_data[raw_data_length++] = 0x68;
    raw_data[raw_data_length++] = 0x69;
    raw_data[raw_data_length++] = 0x6a;
    raw_data[raw_data_length++] = 0x6b;
    raw_data[raw_data_length++] = 0x6c;
    raw_data[raw_data_length++] = 0x6d;

    //filling missing fields
    ip_header->tot_len = htons(raw_data - (char*) ip_header + raw_data_length);
    udp_header->len = htons(raw_data - (char*) udp_header + raw_data_length);

    unsigned short packet_total_len = raw_data + raw_data_length - packet;

    //sockaddr_ll to specify destination mac/interface index/other informations, necessary to use sendto
    struct sockaddr_ll server_address_ll;
    //index of interface to send to
    server_address_ll.sll_ifindex = 2;
    //length of destination mac address 
    server_address_ll.sll_halen = ETH_ALEN;
    //Setting destination mac address in 
    server_address_ll.sll_addr[0] = 0x00;
    server_address_ll.sll_addr[1] = 0x15;
    server_address_ll.sll_addr[2] = 0x5d;
    server_address_ll.sll_addr[3] = 0x6c;
    server_address_ll.sll_addr[4] = 0xae;
    server_address_ll.sll_addr[5] = 0x2b;

    result = sendto(rawsocket_fd, packet, packet_total_len, 0, (struct sockaddr*) &server_address_ll, sizeof(struct sockaddr_ll));
    if(result == -1)
    {
        perror("error");
        exit(1);
    }

return 0;
}

抓包情况

tcpdump和Wireshark可捕获到客户端发送的流量,且无错误标记,但UDP服务器未接收到数据。

排查与修复建议

1. 修正IP校验和计算顺序

当前代码先计算IP校验和,再设置ip_header->tot_len,但tot_len是IP头部的必填字段,参与校验和计算。正确顺序应为:

// 先填充IP总长度
ip_header->tot_len = htons(raw_data - (char*) ip_header + raw_data_length);
// 再计算IP校验和
ip_header->check = ip_checksum(ip_header, 20);

2. 计算UDP校验和

部分Linux内核会丢弃校验和为0的UDP包(除非明确禁用校验和检查)。需要构造UDP伪头部并计算校验和:

// 添加UDP伪头部结构体
struct udp_pseudo_header {
    uint32_t saddr;
    uint32_t daddr;
    uint8_t zero;
    uint8_t protocol;
    uint16_t udp_len;
} pseudo_hdr;

// 填充伪头部
pseudo_hdr.saddr = ip_header->saddr;
pseudo_hdr.daddr = ip_header->daddr;
pseudo_hdr.zero = 0;
pseudo_hdr.protocol = UDP;
pseudo_hdr.udp_len = udp_header->len;

// 拼接伪头部、UDP头部和payload计算校验和
char checksum_buf[PACKET_BUFFER];
memcpy(checksum_buf, &pseudo_hdr, sizeof(pseudo_hdr));
memcpy(checksum_buf + sizeof(pseudo_hdr), udp_header, ntohs(udp_header->len));
udp_header->check = ip_checksum((struct iphdr*)checksum_buf, sizeof(pseudo_hdr) + ntohs(udp_header->len));

3. 处理回环包接收问题

由于源IP和目的IP均为本机,Raw Socket发送的回环包默认不会被本地SOCK_DGRAM套接字接收。可通过以下方式解决:

  • 修改内核参数允许接收本地回环包:sysctl -w net.ipv4.conf.all.accept_local=1
  • 测试时将目的IP改为同一局域网内的其他机器,验证数据包是否能被正常接收

4. 检查IP头部字段完整性

确保IP头部的frag_off字段被正确初始化(设置为0即可,无分片),避免内核因字段异常丢弃数据包。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:20:00