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

Linux RAW UDP套接字接收额外IP头是否属于Bug?

关于Linux UDP RAW套接字收发数据包格式差异的问题

我创建了基于UDP协议的RAW套接字,发送包含UDP头和数据的数据包,抓包显示数据包正常。但在另一个程序中创建同类型套接字接收数据时,却收到了包含IP头的数据包——即发送的是UDP头+数据,接收的却是IP头+UDP头+数据,必须将指针偏移20字节(IP头长度)才能正常解析。这是Linux套接字的Bug吗?为何相关文档中没有说明此情况?


问题解答

这不是Linux套接字的Bug,是RAW套接字的默认行为,细节如下:

  • 发送端逻辑:当你用SOCK_RAW + IPPROTO_UDP创建套接字时,内核默认会自动帮你封装IP头(除非显式设置IP_HDRINCL选项)。所以你只需要构造UDP头和数据,内核会负责补充IP头字段,抓包看到的完整IP+UDP+数据就是这么来的。
  • 接收端逻辑:同类型的RAW套接字接收时,默认会把完整的IP数据包(包含IP头+UDP头+数据)交付给用户空间。这是RAW套接字的设计特性——允许直接访问IP层的完整报文,方便做协议分析、自定义报文处理等场景。

相关文档其实有说明该特性,只是容易被忽略:SOCK_RAW类型的套接字,接收报文包含IP头,发送时未设置IP_HDRINCL则内核自动添加IP头。

优化方案

如果不想硬编码20字节偏移(IP头可能因选项字段变长,长度不固定),可以动态解析IP头长度:

struct iphdr *ip = iov.iov_base;
int ip_header_len = ip->ihl * 4; // IP头长度字段以4字节为单位
struct udphdr *udp = (uint8_t*)ip + ip_header_len;

如果希望接收端直接拿到UDP头+数据,可以通过套接字选项修改行为(部分内核版本支持):

int flag = 0;
setsockopt(m_socket, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(flag));

发送端代码

#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <linux/udp.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/ether.h>
#include <sys/types.h>
#include <sys/socket.h>
#define __USE_XOPEN2K
#include <netdb.h>

#define AF_INETv4 0x02
#define AF_INETv6 0x0A

int main(int argc, char **argv)
{
    int m_socket = socket(AF_INETv4, SOCK_RAW, IPPROTO_UDP);

    struct sockaddr_in bnd;
    bnd.sin_family = AF_INET;
    bnd.sin_port = htons(55555);
    bnd.sin_addr.s_addr = inet_addr("127.0.0.1");

    bind(m_socket, &bnd, sizeof(bnd));
    perror("bind: ");

    struct iovec iov;

    char buffer[1500 - 14 - 20] = {0};
    iov.iov_base = &buffer;
    iov.iov_len  = sizeof(buffer);
    struct udphdr *udp = iov.iov_base;
    udp->source = htons(55555);
    udp->dest   = htons(22222);
    udp->len    = htons(8);
    udp->check = 0;

    struct  msghdr  msg = {0};

    msg.msg_flags = 0;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    struct sockaddr_in in;
    in.sin_family = AF_INETv4;
    in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    in.sin_port = htons(22222);

    msg.msg_name = &in;
    msg.msg_namelen = sizeof(struct sockaddr_in);

    for(int i = 0; i < 10; i++)
    {
        sendmsg(m_socket, &msg, 0);
        perror("");
    }

    return 0;
}

接收端代码

#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <linux/udp.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/ether.h>
#include <sys/types.h>
#include <sys/socket.h>
#define __USE_XOPEN2K
#include <netdb.h>

#define AF_INETv4 0x02
#define AF_INETv6 0x0A

int main(int argc, char **argv)
{
    int m_socket = socket(AF_INETv4, SOCK_RAW, IPPROTO_UDP);

    struct sockaddr_in bnd;
    bnd.sin_family = AF_INET;
    bnd.sin_port = htons(22222);
    bnd.sin_addr.s_addr = inet_addr("127.0.0.1");

    bind(m_socket, &bnd, sizeof(bnd));
    perror("bind: ");

    struct iovec iov;

    char buffer[1500 - 14 - 20] = {0};
    iov.iov_base = &buffer;
    iov.iov_len  = sizeof(buffer);

    struct  msghdr  msg = {0};

    msg.msg_flags = 0;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    struct sockaddr_in in;
    in.sin_family = AF_INETv4;
    in.sin_addr.s_addr = 0;
    in.sin_port = 0;

    msg.msg_name = &in;
    msg.msg_namelen = sizeof(struct sockaddr_in);

    for(int i = 0; i < 10; i++)
    {
        recvmsg(m_socket, &msg, MSG_ZEROCOPY);
        perror("");

        // 动态解析IP头长度,替代硬编码20字节
        struct iphdr *ip = iov.iov_base;
        int ip_header_len = ip->ihl * 4;
        struct udphdr *udp = (uint8_t*)ip + ip_header_len;

        uint16_t src = htons(udp->source );
        uint16_t dst = htons(udp->dest);
        uint16_t len = htons(udp->len);
        uint16_t chk = htons(udp->check);

        fprintf(stderr, "src: %hu dst: %hu len: %hu chk: %hu\n", src, dst, len, chk);
    }

    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 13:55:54