寻求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
相关产品推荐
相关产品推荐

