本文作者:icy

深入浅出 Raw Socket:手把手教你用 C/C++ 构建高性能网络嗅探器 (raw-socket-sniffer)

icy 昨天 10 抢沙发
深入浅出 Raw Socket:手把手教你用 C/C++ 构建高性能网络嗅探器 (raw-socket-sniffer)摘要: 项目概述 在网络安全、协议分析和系统调试领域,能够直接地“看到”网络线缆上流动的原始数据包是一项核心能力。raw-socket-sniffer 是一个基于 C/C++ 开发的轻量级...

深入浅出 Raw Socket:手把手教你用 C/C++ 构建高性能网络嗅探器 (raw-socket-sniffer)

项目概述

在网络安全、协议分析和系统调试领域,能够直接地“看到”网络线缆上流动的原始数据包是一项核心能力。raw-socket-sniffer 是一个基于 C/C++ 开发的轻量级网络嗅探工具,它摒弃了对 libpcap 等第三方重量级库的依赖,直接利用 Linux 内核提供的 Raw Socket (原始套接字) 机制来捕获链路层或网络层的数据包。

该项目旨在为开发者提供一个简洁、透明的参考实现,展示如何从底层构建一个能够截获、解析并分析 TCP/IP 协议栈数据的嗅探程序。


核心技术原理

1. 什么是 Raw Socket?

在标准的 Socket 编程中,我们通常使用 SOCK_STREAM (TCP) 或 SOCK_DGRAM (UDP),此时操作系统内核会帮我们处理复杂的协议头(如 IP 头、TCP 头)。而 SOCK_RAW 允许应用程序绕过传输层,直接访问网络层(IP 层)甚至链路层(以太网层)的数据。

2. 捕获机制

raw-socket-sniffer 通过调用 socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) 创建套接字。 - AF_PACKET:指定在设备驱动层直接接收数据包。 - SOCK_RAW:要求内核不对数据包进行任何处理,原封不动地交给用户空间。 - ETH_P_ALL:指示捕获所有协议类型的流量(包括 IP, ARP, ICMP 等)。


项目功能特性

  • 零依赖:仅依赖标准 C/C++ 库和 Linux 系统调用,无需安装 tcpdump 或 wireshark 的开发库。
  • 全协议捕获:支持捕获以太网帧及其承载的所有上层协议。
  • 实时解析:能够将二进制流实时转换为人类可读的协议字段(如源IP、目的IP、端口号、协议类型)。
  • 高性能:采用直接内存映射或高效的系统调用,最大限度减少数据拷贝开销。

快速上手实例

为了让你快速理解该项目的运作方式,下面是一个基于该项目逻辑的简化实现实例。

1. 基础代码实现 (sniffer.cpp)

cpp
#include <iostream>
#include <vector>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <unistd.h>

void process_packet(unsigned char* buffer, int size) {
    // 1. 解析以太网头 (Ethernet Header)
    struct ethhdr *eth = (struct ethhdr *)buffer;
    
    // 检查是否为 IP 协议 (0x0800)
    if (ntohs(eth->h_proto) == ETH_P_IP) {
        // 2. 解析 IP 头 (IP Header)
        struct iphdr *ip = (struct iphdr *)(buffer + sizeof(struct ethhdr));
        
        struct sockaddr_in source, dest;
        memset(&source, 0, sizeof(source));
        source.sin_addr.s_addr = ip->saddr;
        memset(&dest, 0, sizeof(dest));
        dest.sin_addr.s_addr = ip->daddr;

        std::cout << "IP Packet: " << inet_ntoa(source.sin_addr) 
                  << " -> " << inet_ntoa(dest.sin_addr) 
                  << " Protocol: " << (int)ip->protocol << std::endl;

        // 3. 如果是 TCP 协议,进一步解析 TCP 头
        if (ip->protocol == IPPROTO_TCP) {
            struct tcphdr *tcp = (struct tcphdr *)(buffer + sizeof(struct ethhdr) + ip->ihl * 4);
            std::cout << "  TCP Port: " << ntohs(tcp->source) 
                      << " -> " << ntohs(tcp->dest) << std::endl;
        }
    }
}

int main() {
    // 创建 Raw Socket
    int sock_raw = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (sock_raw < 0) {
        perror("Socket Error");
        return 1;
    }

    std::cout << "Sniffer is running... Press Ctrl+C to stop." << std::endl;

    unsigned char *buffer = (unsigned char *)malloc(65536); 
    while (true) {
        struct sockaddr saddr;
        int saddr_size = sizeof(saddr);
        
        // 接收数据包
        int data_size = recvfrom(sock_raw, buffer, 65536, 0, &saddr, (socklen_t*)&saddr_size);
        if (data_size < 0) {
            perror("Recvfrom error");
            break;
        }
        process_packet(buffer, data_size);
    }

    close(sock_raw);
    free(buffer);
    return 0;
}

2. 编译与运行

由于 Raw Socket 需要访问网络硬件底层,必须拥有 root 权限 才能运行。

text
# 编译
g++ sniffer.cpp -o sniffer

# 运行 (必须使用 sudo)
sudo ./sniffer

深度解析:数据包的解剖过程

在 raw-socket-sniffer 项目中,数据包的解析遵循一个“剥洋葱”的过程:

  1. L2 (链路层) \(\rightarrow\) 检查 ethhdr。确定数据包是从哪个 MAC 地址来的,以及它承载的是什么协议(通常是 IPv4)。
  2. L3 (网络层) \(\rightarrow\) 偏移 sizeof(ethhdr) 字节,解析 iphdr。这里我们可以获取源 IP 和目的 IP,并检查协议号(TCP=6, UDP=17, ICMP=1)。
  3. L4 (传输层) \(\rightarrow\) 根据 IP 头的 ihl (Internet Header Length) 字段计算偏移量,解析 tcphdr 或 udphdr。此时可以获取源端口和目的端口。
  4. Payload (应用层) \(\rightarrow\) 越过传输层头部,剩下的就是实际传输的业务数据(如 HTTP 请求、DNS 查询等)。

项目进阶方向

如果你想在 raw-socket-sniffer 的基础上进行扩展,可以尝试以下方向:

1. 过滤器实现 (BPF)

目前的实现是捕获所有包,这在流量巨大的环境下会导致 CPU 占用过高。可以研究 Berkeley Packet Filter (BPF),在内核空间就过滤掉不需要的包,只将感兴趣的包传给用户空间。

2. 多线程异步处理

使用一个线程专门负责 recvfrom 将数据包写入环形缓冲区 (Ring Buffer),另一个线程负责解析和打印,避免解析耗时导致丢包。

3. 协议深度解析

增加对 DNS、HTTP、TLS 握手包的解析逻辑,将原始的十六进制数据转换为可读的协议字段。


注意事项与安全警告

  1. 权限要求:Raw Socket 仅限 root 或具有 CAP_NET_RAW 能力的用户使用。
  2. 法律合规:请仅在自己拥有的设备或获得授权的网络环境中进行测试。未经许可嗅探他人流量属于违法行为。
  3. 性能瓶颈:在万兆网卡环境下,简单的 recvfrom 可能会导致严重的丢包,建议研究 PACKET_MMAP 机制。
raw-socket-sniffer_20260729103229.zip
类型:压缩文件|已下载:0|下载方式:免费下载
立即下载
文章版权及转载声明

作者:icy本文地址:https://www.zelig.cn/cpp/1355.html发布于 昨天
文章转载或复制请以超链接形式并注明出处软角落-SoftNook

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

阅读
分享

发表评论

快捷回复:

评论列表 (暂无评论,10人围观)参与讨论

还没有评论,来说两句吧...