用libpcap读取pcap文件比raw socket更直接:pcap_open_offline()自动校验魔数、处理字节序、识别pcap/pcapng,返回标准化pcap_pkthdr和数据指针;须用pcap_next_ex()判eof,依pcap_datalink()结果确定链路层类型再解析。

用 libpcap 读取 PCAP 文件比用 raw socket 更直接
PCAP 文件本质是二进制封装格式,不是靠自己解析文件头就能安全读包的。libpcap(或其 Windows 变种 WinPcap/Npcap)提供了 pcap_open_offline() 这个专用接口,它会校验魔数、处理字节序、跳过无效全局头字段,并统一返回标准化的 struct pcap_pkthdr 和原始数据指针——绕过这些细节,自己解析大概率在 big-endian 设备或旧版本捕获文件上出错。
实操建议:
-
pcap_open_offline()返回nullptr时,务必调用pcap_geterr()查看具体错误,常见如"bad dump file format"(文件被截断)或"unsupported version"(比如用较新 tcpdump 保存的 pcapng 格式,而 libpcap 版本太老) - 不要对文件后缀做判断(如看到 .pcap 就硬开),libpcap 会根据实际魔数识别,.cap/.pkt 等扩展名也能正常打开
- 若需支持 pcapng(Wireshark 默认格式),确认编译时链接的是 libpcap ≥ 1.10 或使用
npf.dll(Npcap);旧版 libpcap 会静默失败
从 pcap_pkthdr 提取时间戳和包长要小心字段顺序
struct pcap_pkthdr 的 ts.tv_sec/ts.tv_usec 是秒+微秒,但注意:libpcap 在不同平台下可能把 tv_sec 定义为 time_t(64 位)或 int32_t(32 位),尤其在交叉编译嵌入式环境时容易溢出;len 和 caplen 区分原始长度与截断长度,误用 len 去 memcpy 可能越界(比如设置了 -s 68 抓包,但应用层协议仍按完整 IP 包解析)。
实操建议:
- 始终用
hdr->caplen作为实际可读数据长度,hdr->len仅用于判断是否被截断(caplen ) - 时间戳转 std::chrono:用
std::chrono::system_clock::from_time_t(hdr->ts.tv_sec) + std::chrono::microseconds(hdr->ts.tv_usec),避免手动拼接整数导致精度丢失 - 不要假设
ts.tv_sec是 int32_t——检查你链接的 libpcap 头文件中struct timeval定义
解析以太网帧时,先检查 link-layer type 再 cast 数据指针
同一个 PCAP 文件里所有包的链路层类型(link-layer header type)是固定的,由文件全局头里的 linktype 字段决定,常见值有 DLT_EN10MB(以太网)、DLT_LINUX_SLL(Linux cooked)、DLT_RAW(纯 IP)。直接把 packet data 强转成 ethhdr* 而不验证 pcap_datalink() 返回值,会导致指针偏移错误,后续解析 IP 层全乱。
实操建议:
- 打开文件后立刻调用
int dlt = pcap_datalink(handle),只在dlt == DLT_EN10MB时才按以太网帧解析;否则查 tcpdump 官方 linktype 表 对应结构 - 对于
DLT_LINUX_SLL,前 16 字节是struct sll_header,真实以太网帧从 offset 16 开始;DLT_RAW则无链路层头,首字节就是 IP header - Wireshark 导出的“IEEE 802.11”抓包,
pcap_datalink()返回DLT_IEEE802_11,此时数据开头是 802.11 MAC header,不是 Ethernet II
用 pcap_next_ex() 比 pcap_next() 更安全,尤其处理大文件
pcap_next() 返回裸指针,无法区分 EOF、错误、超时三种情况;而 pcap_next_ex() 通过返回值明确告知:1 是成功读到包,0 是超时(对离线文件恒为 0,可忽略),-1 是错误(调 pcap_geterr()),-2 是 EOF。在循环读取时漏判 -2 会导致最后一次 pcap_next() 返回空指针后继续解引用,直接 crash。
实操建议:
- 坚持用
int res = pcap_next_ex(handle, &hdr, &pkt),然后 switch(res) 处理每种返回码 - 对超大 PCAP(>1GB),避免一次性 malloc 所有包内存;用局部变量接收
pkt指针,解析完立即处理或深拷贝,别缓存原始指针 - 如果需要随机访问某第 N 个包,libpcap 不支持 seek,只能顺序遍历;真有此需求,改用
libtins或手写 mmap + 自解析(但得自己处理 pcapng 分块)
最易被忽略的是 link-layer type 的实际值和对应结构体偏移——很多人对着 RFC 写死 sizeof(ethhdr),却没意识到抓包环境可能根本不是以太网。这点一旦出错,后面所有协议解析都是空中楼阁。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











