必须确认权限和接口状态:普通用户无权访问网卡,需sudo或setcap;接口须UP(含LOWER_UP);避免用"any"调试;解析以太网帧需处理内存对齐;检查pcap_datalink()返回值;设置超时与非阻塞模式;BPF过滤语法受限;libpcap不处理TCP重组与SSL解密。

用 libpcap 抓包前必须确认权限和接口状态
普通用户进程无法直接访问网卡驱动层,libpcap 会静默失败或返回 NULL,而不是抛出明显错误。最常见现象是 pcap_open_live() 返回 NULL,但 pcap_geterr() 只报 “Permission denied” —— 这通常不是代码问题,而是没加权限。
- Linux 下必须用
sudo启动,或给二进制文件加cap_net_raw+ep:sudo setcap cap_net_raw+ep ./sniffer - 确保目标接口处于 UP 状态:
ip link show eth0查看是否含LOWER_UP;如果接口是 down 的,pcap_open_live()会成功但收不到任何包 - 避免用
"any"设备名调试:它在部分系统(如 Ubuntu)上可能跳过 VLAN 或混杂模式限制,掩盖真实问题;先固定用"eth0"或"wlan0"
解析以太网帧时别直接 cast 到 struct ether_header
不同平台、编译器默认对齐方式不同,struct ether_header 在头文件里定义的字段偏移可能和实际抓到的原始字节不一致,导致 ether_type 读错、IP 头解析偏移全乱。这不是“结构体定义错了”,而是内存布局没对齐。
- 用
#pragma pack(1)强制紧凑对齐(gcc/clang 支持),或改用uint8_t*手动偏移:比如以太网类型在字节 12–13,就用ntohs(*(uint16_t*)(pkt + 12)) -
pcap_datalink()返回值必须检查:如果是DLT_LINUX_SLL(Linux cooked mode),开头根本不是以太网头,而是 16 字节伪头,直接解析会越界 - IPv4 和 IPv6 共存时,别只查
ETHERTYPE_IP;要同时处理ETHERTYPE_IPV6,且 IPv6 的 next header 字段可能嵌套(如 ESP、ICMPv6),不能硬写死解析逻辑
设置超时和非阻塞模式防止 recv 无限挂起
pcap_loop() 或 pcap_next_ex() 默认是阻塞式,一旦链路空闲或过滤器太严,程序就卡住不动——看起来像崩溃,其实是等包。尤其在测试环境没流量时,这会让调试完全无法进行。
- 创建句柄后立刻调用
pcap_set_timeout(handle, 1000)(单位毫秒),再设pcap_setnonblock(handle, 1, errbuf),这样pcap_next_ex()会立即返回0(超时)或-1(错误),而非阻塞 - 不要依赖
pcap_loop()做实时处理:它内部用信号机制,在多线程下行为不可靠;优先用pcap_next_ex()配合循环轮询 - 如果启用了 BPF 过滤器(
pcap_compile()+pcap_setfilter()),注意过滤语法不支持正则,只支持tcp port 80这类原生表达式;写错会导致匹配不到包,且无提示
libpcap 不处理 TCP 重组和 SSL 解密
很多新手以为抓到 TCP 包就能看到完整 HTTP 请求,结果发现 GET / 被切在两个包里,或者全是加密的 TLS 记录。libpcap 层只负责把链路层帧拷贝上来,不维护连接状态,也不触碰应用层协议。
立即学习“C++免费学习笔记(深入)”;
- 需要 TCP 流重组,得自己维护滑动窗口、序列号映射、缓冲区合并逻辑;简单场景可用
tcpreasm库,但要注意内存泄漏风险 - 解密 HTTPS 流量需拿到服务端私钥并启用 NSS Key Log(Chrome/Firefox 支持),libpcap 本身不提供该能力;Wireshark 能解密是因为它集成了 NSS 模块,不是 pcap 的功能
- 时间戳精度取决于底层:Linux 上
pcap_open_live()默认用CLOCK_MONOTONIC,但高频率抓包(>10k pps)时,内核skb时间戳可能被批处理覆盖,实测误差可达毫秒级
真正难的从来不是“怎么抓”,而是“抓下来之后怎么信得过那几个字节”。对齐、权限、超时、协议分层——每个环节都可能让看似正确的代码在另一台机器上彻底失效。


















