- 原始套接字(Raw Socket)允许程序直接访问网络层 / 链路层的数据包,可用于自定义协议、抓包、网络分析等场景,需要 root 权限运行
- 网络层原始套接字:基于 AF_INET,抓包时内核已处理链路层 /header,直接获取 IP 数据包
- 链路层原始套接字:基于 PF_PACKET,抓包时获取完整的链路层数据包(包含以太网头),可捕获所有协议类型
与普通套接字的区别
| 类型 |
普通套接字(TCP/UDP) |
原始套接字(Raw Socket) |
| 操作层级 |
传输层(TCP/UDP),看不到 IP 头、以太网头 |
网络层(IP)/ 链路层(以太网),可直接操作所有协议头 |
| 协议处理 |
内核自动封装 / 解析 IP 头、TCP/UDP 头,只处理数据 |
可跳过内核协议处理,手动封装 / 解析所有协议头,甚至自定义 |
| 数据可见性 |
只能看到应用层数据(比如 HTTP 报文) |
能看到完整数据包(以太网头 + IP 头 + TCP/UDP 头 + 数据) |
| 权限要求 |
普通用户即可使用 |
必须 root / 管理员权限(防止滥用) |
- 绕开内核对协议的默认处理,获得对网络数据包的 "完全控制权"
应用场景
- 网络抓包与协议分析
- 自定义网络协议 / 修改协议头
- 网络诊断与工具开发
- ...
网络层原始套接字 VS 链路层原始套接字
| 维度 |
网络层原始套接字 |
链路层原始套接字 |
| 套接字创建方式 |
socket(AF_INET, SOCK_RAW, IPPROTO_TCP) |
socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) |
| 捕获数据范围 |
仅 IP 层的 TCP 协议数据包(内核过滤非 TCP 包) |
链路层所有协议包(IP/ARP/LOOPBACK 等),需手动过滤 |
| 数据包起始结构 |
直接指向 iphdr(IP 头) |
先指向 ether_header (以太网头),再偏移到 IP 头 |
| IP 头偏移量 |
无(buf 起始即为 IP 头) |
buf + 14(以太网头固定 14 字节) |
| 核心过滤逻辑 |
无需过滤链路层,内核已限定 IPPROTO_TCP |
1. 先过滤以太网类型为ETHERTYPE_IP;2. 再过滤 IP 协议为 IPPROTO_TCP |
| 功能侧重点 |
聚焦 TCP 数据解析(含数据段的字符 / 16 进制打印) |
先识别链路层协议类型,再解析 TCP 头部基础信息 |
c
复制代码
//网络层原始套接字(仅捕获TCP的IP包)
int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
//链路层原始套接字(捕获所有链路层包)
int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
- ETH_P_ALL:捕获所有以太网帧;若改为 ETH_P_IP,可仅捕获 IP 协议帧,减少过滤逻辑
数据包解析流程
latex
复制代码
buf(IP头) → iphdr → 偏移 iph->ihl*4 字节 → tcphdr → 解析TCP数据
latex
复制代码
buf(以太网头) → ether_header(过滤ETHERTYPE_IP)→
buf+14 → iphdr(过滤IPPROTO_TCP) → buf+14+iph->ihl*4 → tcphdr
demo
- 聚焦分析 TCP 数据内容,基于网络层原始套接字,减少链路层解析成本
c
复制代码
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ether.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <sys/ioctl.h>
#define MTU 1500
int main()
{
/* 定义变量 */
int sockfd = -1, len, datalen, i;
uint8_t buf[MTU]={}, *data;
struct iphdr *iph; //IP包头
struct tcphdr *tcph;//TCP包头
struct winsize size;
/* 创建一个原始套接字 */
if( (sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP) ) < 0)
{
perror("socket");
return 0;
}
printf("sockfd = %d\n", sockfd);
/* 接收(只接收TCP数据协议)并处理IP数据报 */
while(1)
{
/* 接收包含TCP协议的IP数据报 */
len = recvfrom(sockfd, buf, sizeof(buf),0,NULL,NULL);
printf("IP数据报长度 = %d\n", len);
/* 打印源IP和目的IP */
iph = (struct iphdr *)buf;
printf("源IP:%s",inet_ntoa(*(struct in_addr *)&iph->saddr) );
printf("目的IP%s\n",inet_ntoa(*(struct in_addr *)&iph->daddr) );
/* 打印TCP包头的源端口号和目的端口号 */
tcph = (struct tcphdr *)(buf+iph->ihl*4);
printf("%hu--->", ntohs(tcph->source));
printf("%hu\n", ntohs(tcph->dest));
/* 打印TCP数据段的长度 */
printf("TCP首部长度:%d\n", tcph->doff*4);
if(iph->ihl*4+tcph->doff*4 < len) {
data = buf + iph->ihl*4 + tcph->doff*4;
datalen = len - iph->ihl*4 - tcph->doff*4;
ioctl(STDIN_FILENO,TIOCGWINSZ,&size); //terminal 结构体
for(i = 0; i < size.ws_col; i++) //显示一行 =
putchar('=');
putchar('\n');
printf("TCP数据字符:\n");
for(i = 0; i < size.ws_col; i++)
putchar('=');
putchar('\n');
for(i = 0; i < datalen-1; i++) {
printf("%c", data[i]);
}
for(i = 0; i < size.ws_col; i++)
putchar('=');
putchar('\n');
printf("TCP数据16进制:\n");
for(i = 0; i < size.ws_col; i++)
putchar('=');
putchar('\n');
for(i = 0; i < datalen-1; i++){
printf("%x ", data[i]);
}
putchar('\n');
for(i = 0; i < size.ws_col; i++)
putchar('=');
putchar('\n');
}
}
//关闭套接字
close(sockfd);
return 0;
}
- 合链路层全协议分析,可扩展支持 ARP、ICMP 等协议解析,灵活性更高
c
复制代码
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ether.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <net/ethernet.h>
#define MTU 1500
int main()
{
/* 定义变量 */
int sockfd, len;
uint8_t buf[MTU]={};
uint16_t ether_type;
struct iphdr *iph; //IP包头
struct tcphdr *tcph;//TCP包头
struct ether_header *eth;
/* 创建一个链路层原始套接字 */
if( (sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) ) < 0){
perror("socket");
return 0;
}
printf("sockfd = %d\n", sockfd);
/* 接收(只接收TCP数据协议)并处理IP数据报 */
while(1)
{
/* 接收包含TCP协议的IP数据报 */
len = recvfrom(sockfd, buf, sizeof(buf),0,NULL,NULL);
eth = (struct ether_header *)buf;
ether_type = htons(eth->ether_type);
switch(ether_type){
case ETHERTYPE_IP:
printf("IP协议\n");
break;
case ETHERTYPE_ARP:
printf("ARP协议\n");
break;
case ETHERTYPE_LOOPBACK:
printf("loop back\n");
break;
default:
printf("其他协议 %x\n",eth->ether_type);
}
if(ether_type != ETHERTYPE_IP)
continue;
/* 打印源IP和目的IP */
iph = (struct iphdr *)(buf+14);
if(iph->protocol != IPPROTO_TCP)
continue;
printf("源IP:%s\n",inet_ntoa(*(struct in_addr *)&iph->saddr) );
printf("目的IP%s\n",inet_ntoa(*(struct in_addr *)&iph->daddr) );
/* 打印TCP包头的源端口号和目的端口号 */
tcph = (struct tcphdr *)(buf+14+iph->ihl*4);
printf("%hu--->", ntohs(tcph->source));
printf("%hu\n", ntohs(tcph->dest));
/* 打印TCP数据段的长度 */
printf("TCP首部长度:%d\n", tcph->doff*4);
}
//关闭套接字
close(sockfd);
return 0;
}