原始套接字Raw Socket

  • 原始套接字(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;
}
相关推荐
落子摘星2 年前
Raw Socket(一)实现TCP三次握手
网络·网络协议·tcp/ip·raw socket
小胖西瓜2 年前
Go 使用原始套接字捕获网卡流量
go·原始套接字