简介
由于项目的需要,近期产生了一个需求:将一个 pacp
文件解析出来,然后尝试提取每个连接的各种统计数据,例如提取一个完整的TCP会话中的源IP、目的IP、源端口、目的端口、会话开始时间、会话结束时间、发送的数据包数量、发送的数据包总大小等等。
能够实现这一需求的库有很多,如python中的 scapy
、 dpkt
,C++中的 PcapPlusPlus
等,考虑到将来可能需要处理较大的 pcap
文件,以及C++相比于python在执行速度上的优势,我决定使用C++作为流量处理的语言;简单翻阅了一下 PcapPlusPlus的文档 ,我认为这个库的易用性能够满足我较低的水平,因此决定先实践一下再说。
预装依赖
从这里开始,我想将 PcapPlusPlus 简称为 PCPP ,方便文章的撰写之用 。
本文使用的环境是:Win10 + VisualStudio2019 。
要使用这个库,我们需要先安装一些依赖。
个人建议将这些依赖下载到同一个地方,方便管理!
- 安装 WinPcap开发者工具 或 Npcap SDK 。下载解压而已,很简单!我这边使用的是WinPcap 。
- 安装 pthread-win32工具 ,注意这里的链接跟PCPP文档里面的链接是不一样的!文档里面的URL协议是
ftp
,我下载的时候好像打不开的样子,就改为使用https
了! - 可能还需要一个 Microsoft Visual C++ Redistributable 工具,从这里下下来的是个
exe
文件,我在后面的过程中 暂时没有用到 !也就是说我还没运行过这个EXE,先下载下来而已!
下载PcapPlusPlus
直接从 PcapPlusPlus v21.11 页面上把示例代码下载下来!我这边选用的是 windows-vs2019
这个压缩包!
到这一步,算上我们之前下载 并解压 的依赖,文件夹里面应该有这么多东西:
All right?进入刚才下载的VS2019文件夹里面,有个 ExampleProject
,打开之:
没问题的话,就准备运行示例程序了!
运行示例程序
熟悉VS的朋友可能要迫不及待地把那个 .sln
文件给打开了~不要心急!
首先我们应该修改 .props
文件,这个文件是用来指定项目里面的一些属性的;具体地说,其实就是指定刚才下载的几个依赖的路径而已!如果不修改这个文件,贸然打开 .sln
,那必然是要报错的!
最原始的 .props
文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros">
<PcapPlusPlusHome>Drive:\your\PcapPlusPlus\folder</PcapPlusPlusHome>
<PcapSdkHome>Drive:\WpdPack\folder</PcapSdkHome>
<PThreadWin32Home>Drive:\pthread-win32\folder</PThreadWin32Home>
</PropertyGroup>
<PropertyGroup />
<ItemDefinitionGroup />
<ItemGroup />
</Project>
看到吗?这个文件指定三个关键路径:
- PcapPlusPlusHome :就是刚才解压出来的PCPP目录,也就是现在这个
ExampleProject
的上级目录! - PcapSdkHome :用于处理
pcap
文件的SDK工具的目录,在 预装依赖 小节里面我们下载了WinPcap,还记得吗?把它的路径写在这里即可!如果你选用的是Npcap,道理也一样~ - PThreadWin32Home :多线程库!把pthread路径填进去吧 😀
在我的PC上,修改完成之后这个文件差不多是这样:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros">
<PcapPlusPlusHome>E:\PcapPlusPlus\pcapplusplus-21.11-windows-vs2019</PcapPlusPlusHome>
<PcapSdkHome>E:\PcapPlusPlus\WpdPack</PcapSdkHome>
<PThreadWin32Home>E:\PcapPlusPlus\pthreads-w32-2-9-1-release</PThreadWin32Home>
</PropertyGroup>
<PropertyGroup />
<ItemDefinitionGroup />
<ItemGroup />
</Project>
之后,就可以打开 .sln
文件了!示例项目即刻出现!
main.cpp
简单使用PcapPlusPlus解析了一下示例项目里面的 1_packet.pcap
文件,这个文件只有一个数据包,程序把源IP和目的IP输出了一下而已!
直接编译运行即可:
简单使用PCPP处理pcap文件
刚才运行的示例未免太简单,很难体现出咱们这个库的潜力!
在这一小节,我希望使用这个库简单处理一下我之前捕获的一个 pcap
文件,里面包含两万多条数据,其实也还不算很大。
先把代码贴出来,再慢慢解释吧~
#include <iostream>
#include <IPv4Layer.h>
#include <Packet.h>
#include <PcapFileDevice.h>
#include <set>
#include <ctime>
int main(int argc, char* argv[])
{
// Part 1
// open a pcap file for reading
pcpp::IFileReaderDevice* reader = pcpp::IFileReaderDevice::getReader("test_file_1.pcap");
if (!reader)
{
std::cerr << "Cannot determine reader for file type" << std::endl;
return 1;
}
if (!reader->open())
{
std::cerr << "Cannot open input.pcap for reading" << std::endl;
return 1;
}
// Part 2
if (!reader->setFilter("tcp and port 80"))
{
std::cerr << "Cannot set filter for file reader" << std::endl;
return 1;
}
// Part 3
pcpp::RawPacket rawPacket;
std::set<std::string> dstIPSet;
clock_t BEGIN = clock();
while (reader->getNextPacket(rawPacket))
{
pcpp::Packet parsedPacket(&rawPacket);
if (parsedPacket.isPacketOfType(pcpp::IPv4))
{
pcpp::IPv4Address dstIP = parsedPacket.getLayerOfType<pcpp::IPv4Layer>()->getDstIPv4Address();
if (dstIPSet.count(dstIP.toString()) == 0)
dstIPSet.insert(dstIP.toString());
}
}
clock_t END = clock();
// Part 4
std::cout << "There are total " << dstIPSet.size() << " destination IP address.\n";
std::cout << "Total time: " << double(END - BEGIN) / CLK_TCK * 1000 << " ms.\n";
// Part 5
reader->close();
delete reader;
return 0;
}
根据 参考资料[4] ,我们这次使用的 reader
跟刚才示例代码使用的不太一样,用的是 IFileReaderDevice
里面的 getReader()
API,它会根据文件名来为我们返回合适的Reader!代码里面的 Part 1
就是在干这个事情而已,创建一个合适的Reader,然后打开它,准备读取文件了!
紧接着,我为这个Reader设置了一个过滤器。 reader->setFilter("tcp and port 80")
表明我只希望读取协议为TCP的、且端口为 80
的数据包,其它的数据包(如ICMP、UDP等等)我就不关心了。这个 setFilter
在将来的场景里面应该是蛮有用的,它使用的是 Berkeley Packet Filter (BPF) syntax 。
来到 Part 3
之后,开始分析我们读到的数据。过滤器是TCP协议和 80
端口,其实我们读取的就是发往某个Web服务的请求而已!我用了一个集合 dstIPSet
来记录目的IP,打算待会看看总共有多少不同的目的主机 😹 使用 while
循环来读取数据包,使用 parsedPacket
来解析数据包,然后获取数据包里面的IPv4地址,把它放到集合里面!
Part 3
有个小点缀,就是在循环外面设置了两个 clock_t
,目的是测试一下整个循环要花多长时间!
Part 4
就是我们的输出啦:
Part 5
还是比较重要的,要把劳苦功高的Reader关掉、还要释放它的空间啊!刚开始编写这个程序的时候我给忘了,罪过罪过~由于PCPP的这个 IFileReaderDevice
是动态分配的,如果不把它 delete
掉的话会发生内存泄露呀!
参考资料
[1] Quick Start - Visual Studio - PcapPlusPlus
[2] Build on Windows (VS) - PcapPlusPlus