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

寻求Linux下GNU LibC原始套接字API及struct iphdr使用文档

嗨,我之前在做原始套接字开发的时候也遇到过这个困惑,刚好可以给你详细讲下怎么处理——从接收数据包到解析struct iphdr结构体,再到访问那些容易搞混的位字段,一步步来:

解析SOCK_RAW接收的IP数据包为struct iphdr及访问位字段

一、先明确struct iphdr的核心结构

首先,netinet/ip.h里的struct iphdr是Linux内核定义的IPv4头部结构体,核心部分大概是这样的(重点标出来位字段):

struct iphdr {
    __u8    ihl:4,        // IP头部长度(单位:4字节)
            version:4;    // IP版本(IPv4是4)
    __u8    tos;          // 服务类型(含优先级和TOS子字段)
    __be16  tot_len;      // 数据包总长度(网络字节序)
    __be16  id;           // 数据包ID
    __be16  frag_off;     // 分片偏移+标志位(网络字节序)
    __u8    ttl;          // 生存时间
    __u8    protocol;     // 上层协议(1=ICMP,6=TCP,17=UDP等)
    __sum16 check;        // 头部校验和
    __be32  saddr;        // 源IP(网络字节序)
    __be32  daddr;        // 目标IP(网络字节序)
};

这里的ihl和version是共用一个字节的4位位字段,frag_off则包含了分片标志和偏移量,这些都是需要特别处理的部分。

二、接收原始套接字数据包并解析

要把接收到的原始数据转成struct iphdr,步骤很直接,核心是指针强制转换,但要注意权限和字节序问题:

代码示例:接收并解析IP头部

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>

int main() {
    int sockfd;
    struct iphdr* ip_header;
    unsigned char buffer[4096]; // 足够容纳IP包+上层数据

    // 创建原始套接字,必须用root权限运行
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
    if (sockfd < 0) {
        perror("Failed to create raw socket");
        exit(EXIT_FAILURE);
    }

    while (1) {
        // 接收数据包
        ssize_t recv_len = recv(sockfd, buffer, sizeof(buffer), 0);
        if (recv_len < sizeof(struct iphdr)) {
            perror("Received incomplete packet");
            continue;
        }

        // 把缓冲区指针强制转换为struct iphdr*
        ip_header = (struct iphdr*)buffer;

        // 开始解析字段,注意多字节字段要转主机字节序
        printf("=== IP Header Info ===\n");
        printf("IP Version: %u\n", ip_header->version);
        printf("Header Length: %u bytes\n", ip_header->ihl * 4); // ihl是4字节单位,转成实际长度
        printf("Total Packet Length: %u bytes\n", ntohs(ip_header->tot_len));
        printf("Source IP: %s\n", inet_ntoa(*(struct in_addr*)&ip_header->saddr));
        printf("Destination IP: %s\n", inet_ntoa(*(struct in_addr*)&ip_header->daddr));
        printf("Protocol: %u\n", ip_header->protocol);
        printf("TTL: %u\n", ip_header->ttl);
        printf("\n");
    }

    close(sockfd);
    return 0;
}

运行时记得加sudo,因为普通用户没有创建原始套接字的权限。

三、访问位字段的具体方法

结构体里的位字段不能直接像普通成员那样用,需要结合位运算或者内核提供的宏来提取:

1. 处理frag_off里的分片标志和偏移

frag_off是16位字段,高3位是标志(DF=不分片,MF=更多分片),低13位是分片偏移(单位:8字节)。可以用netinet/ip.h里的宏来处理:

// 先转为主机字节序
uint16_t frag_off_host = ntohs(ip_header->frag_off);

// 提取DF和MF标志
int df_flag = (frag_off_host & IP_DF) ? 1 : 0;
int mf_flag = (frag_off_host & IP_MF) ? 1 : 0;
printf("DF Flag (Don't Fragment): %d\n", df_flag);
printf("MF Flag (More Fragments): %d\n", mf_flag);

// 提取分片偏移(转成字节数)
uint16_t frag_offset = (frag_off_host & IP_OFFMASK) * 8;
printf("Fragment Offset: %u bytes\n", frag_offset);

2. 解析tos字段的优先级和服务类型

tos是8位字段,高3位是优先级,低5位是服务类型:

// 提取优先级(高3位)
int priority = (ip_header->tos >> 5) & 0x07;
// 提取服务类型(低5位)
int tos = ip_header->tos & 0x1F;
printf("Priority: %d\n", priority);
printf("TOS Value: %d\n", tos);

3. 直接访问ihl和version

这两个是4位位字段,直接用结构体成员访问就可以了——内核已经帮我们处理好了位布局,不用自己做位运算:

printf("IP Version: %u\n", ip_header->version); // 直接拿到4(IPv4)
printf("Header Length: %u bytes\n", ip_header->ihl *4); // 乘以4转成实际字节数

四、关键注意事项

  • 字节序转换:所有多字节字段(tot_len、id、frag_off、saddr、daddr)都是网络字节序(大端),必须用ntohs(16位)或ntohl(32位)转为主机字节序后再使用,否则数值会错乱。
  • 内存安全:接收缓冲区要足够大,并且确保接收长度至少大于sizeof(struct iphdr),避免指针越界。
  • 数据包过滤:如果不想接收所有IP数据包,可以用setsockopt设置过滤规则,比如只接收TCP或ICMP包:
    int proto = IPPROTO_TCP;
    setsockopt(sockfd, IPPROTO_IP, IPPROTO_TCP, &proto, sizeof(proto));
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 09:04:03