前言
SharpPcap 是.NET 环境中跨平台的抓包框架,对 WinPcap 和 LibPcap 进行了统一的封装,使用 C#语言
本人的毕设需要使用 WinPcap 进行抓包解析,还需要做一个 UI 界面,正好.NET 有这样一个库,同时还有 WPF 这样的 UI 框架,之前参与过 Android 项目,WPF 的 xaml 布局写法和 Android 很类似,上手 WPF 难度应该不算很高,综合考虑下选择使用 C#完成毕设(~~根本原因是 C++ 用不顺手~💔💔💔)
理想是丰满的,现实是骨感的,当我兴致勃勃准备查找文档开始干的时候,发现怎么网上搜出来的例子跑不通。找到 GitHub 仓库,在 Tutorial 找到一篇文档,但是还是有例子跑不通,猜测是版本的问题,结果发现在 releases 中写“Please see nuget for releases”,这个 nuget 又是啥,咋还跑到那里去发布,后来了解到 nuget 是.NET 的包管理平台,类似 Java 的 Maven。一路搜索过去,倒是找到了 SharpPcap 的 Nuget 地址,但是还是找不到最新的文档,此时我的内心是崩溃的
没办法,只能硬着头皮看 Tutorial 的文档和反编译的源码慢慢调试了,在此记录一下 SharpPcap 新版本的 API 使用,SharpPcap 版本为 6.3.0
SharpPcap 的 GitHub 仓库:dotpcap/sharppcap
PacketDotNet 的 GitHub 仓库:dotpcap/packetnet
NuGet 地址:NuGet Gallery SharpPcap 6.3.0
SharpPcap 安装
SharpPcap 已经发布在 NuGet 上,所以我们可以直接通过 Visual Studio 的 NuGet 管理器获取安装,这里使用 Visual Studio 2022 版本
-
安装 SharpPcap
打开项目的 NuGet 管理器,点击浏览,在搜索框搜索 SharpPcap,点击安装即可


-
查看依赖项
安装完成后,我们可以看到项目中的依赖项已经有了 SharpPcap 的依赖,其中也包含一个叫 PacketDotNet 的库,SharpPcap 主要负责数据包的捕获,而 PacketDotNet 就是负责数据包的解析

-
在代码中使用 SharpPcap
在代码中引入 SharpPcap 命名空间和 PacketDotNet 命名空间即可,打印版本检查是否可以正常使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15using System;
using SharpPcap;
using PacketDotNet;
namespace backend {
public class Backend {
public static void Main(string[] args) {
// Tutorial中获取版本为string ver = SharpPcap.Version.VersionString;
var version = Pcap.Version;
var sharpPcapVersion = Pcap.SharpPcapVersion;
Console.WriteLine(version);
Console.WriteLine($"SharpPcapVersion = {sharpPcapVersion}");
}
}
}
可以打印出 Npcap 版本和 SharpPcap 版本,接下来就可以愉快的使用了👏👏👏
获取接口列表
在 SharpPcap 中获取接口列表非常简单,只需要一行代码
1 | var list = CaptureDeviceList.Instance; |
获取到的 CaptureDeviceList 继承了 ReadOnlyCollection<ILiveDevice>,是一个 ILiveDevice 类型的只读集合,由此可见获取到的设备实例是 ILiveDevice 类型的
ILiveDevice 继承了 ICaptureDevice 和 IInjectionDevice 两个接口,这两个接口都继承了 IPcapDevice 接口,这两个接口中主要包含了各自功能模块的方法接口
1 | // ILiveDevice |
IPcapDevice 中包含了接口设备相关的信息
1 | public interface IPcapDevice : IDisposable { |
打开接口并捕获
从上面的接口方法中,可以看到相关的打开、捕获等方法,基本使用如下
1 | var devices = CaptureDeviceList.Instance; |
实际上这里使用的 Open() 是一个扩展方法,IPcapDevice 接口中的 Open() 接收一个 DeviceConfiguration 类型的参数,表示启动配置,而在 CaptureDeviceExtensions.cs 文件中,对 Open() 和 IInjectionDevice 接口的 SendPacket() 做了扩展
DeviceConfiguration 中有两个常用的属性
DeviceModes Mode:接口工作模式None:默认模式Promiscuous:混杂模式
int ReadTimeout:捕获超时时间
开启接口混杂模式和设置超时时间,可以调用 CaptureDeviceExtensions.cs 文件中的扩展
1 | device.Open(mode: DeviceModes.Promiscuous, read_timeout: 1000); |
捕获数据包
与 WinPcap 一样,SharpPcap 也有回调捕获和非回调捕获两种方式,在 SharpPcap 新版本中,将两种方式返回的数据包类型统一为了 PacketCapture 类型
回调捕获
在 ICaptureDevice 接口中有两个委托属性,可以定义回调函数
-
PacketArrivalEventHandler OnPacketArrival数据包捕获回调,函数类型为
void OnPacketArrival(object sender, PacketCapture e) -
CaptureStoppedEventHandler OnCaptureStopped停止捕获回调,函数类型为
void OnCaptureStop(object sender, CaptureStoppedEventStatus status)
在调用 Open() 之前,设置接口的 OnPacketArrival 属性即可设置捕获回调
1 | device.OnPacketArrival += new PacketArrivalEventHandler(OnPacketArrival); |
非回调捕获
使用 ICaptureDevice 接口中的 GetNextPacket() 获取一个数据包,该函数接收一个 PacketCapture 类型的输出参数,PacketCapture 是数据包类型,返回值是 GetPacketStatus 枚举类型,表示捕获数据包的状态
1 | public enum GetPacketStatus { |
在 While 循环中持续获取数据包,不需要调用 StartCapture()
1 | var device = devices[index]; |
过滤数据包
在 SharpPcap 中设置过滤数据包非常简单,只需要设置 IPcapDevice 接口中的 Filter 属性为过滤表达式即可
1 | device.Filter = "ip6 and icmp6"; // 过滤ICMPv6包 |
数据包解析
数据包主要使用到 PacketDotNet 库,PacketDotNet 中将几乎所有类型的数据包都封装了实体类,而它们都继承了 Packet 这个抽象父类,其中也包含一些用于解析的方法,主要用到下面两个方法
Packet.ParsePacket():将PacketCapture解析为Packet对象,Extract<T>():从Packet对象中提取指定数据包类型,返回相应的数据包对象
基本使用如下
1 | private static void OnPacketArrival(object s, PacketCapture packetCapture) { |
数据包解析的部分工作原理详见:SharpPcap 数据包解析原理
堆文件处理
堆文件处理使用到 LibPcap 模块的功能,LibPcap 中接口的父类为 PcapDevice 类,该类实现了 ICaptureDevice 接口
写入堆文件
写入文件主要使用 CaptureFileWriterDevice 类,它继承了 PcapDevice 类,主要使用以下方法
-
CaptureFileWriterDevice():唯一构造器传入写入文件名和打开模式,默认为打开并创建
-
Open():打开接口传入
DeviceConfiguration,主要参数是链路层类型LinkLayers,要与捕获接口的链路层类型一致在
CaptureDeviceExtensions.cs中包含两个Open扩展函数void Open(this CaptureFileWriterDevice device, ICaptureDevice captureDevice)void Open(this CaptureFileWriterDevice device, LinkLayers linkLayerType = LinkLayers.Ethernet)
-
Write():写入文件,有两个重载void Write(ReadOnlySpan<byte> p, ref PcapHeader h)void Write(ReadOnlySpan<byte> p)void Write(RawCapture p)
基本使用如下,注意在 Windows 中,文件的相对路径是相对于 .exe 可执行文件的路径
1 | // 默认模式为打开并创建 |
读取堆文件
读取文件主要使用 CaptureFileReaderDevice 类,它继承了 PcapDevice 类,主要使用以下方法
CaptureFileReaderDevice():唯一构造器,传入读取的文件名Open():打开接口StartCapture():开始读取文件
基本使用如下
1 | CaptureFileReaderDevice reader = new("capture.pcap"); |
发送数据包
发送单个数据包
使用 IInjectionDevice 接口中的 SendPacket 方法,CaptureDeviceExtensions.cs 中包含该方法的四个扩展方法
void SendPacket(ReadOnlySpan<byte> p, ICaptureHeader header = null)void SendPacket(this IInjectionDevice device, byte[] p, int size)void SendPacket(this IInjectionDevice device, Packet p)void SendPacket(this IInjectionDevice device, Packet p, int size)void SendPacket(this IInjectionDevice device, RawCapture p, ICaptureHeader header = null)
基本使用如下,从文件中读取数据包发送
1 | var device = devices[index]; |
发送队列
发送队列是 WinPcap 扩展功能,使用 LibPcap 模块,主要使用到 SendQueue 类,使用以下方法
SendQueue():唯一构造器,传入队列大小,单位 BAdd():添加到发送队列,SendQueue.cs的SendQueueExtensions中包含它的四个扩展方法bool Add(PcapHeader header, byte[] packet)bool Add(this SendQueue queue, byte[] packet)bool Add(this SendQueue queue, Packet packet)bool Add(this SendQueue queue, RawCapture packet)bool Add(this SendQueue queue, byte[] packet, int seconds, int microseconds)
Transmit():发送发送队列,传入PcapDevice类型接口对象,返回发送的字节数,有一个重载int Transmit(PcapDevice device, bool synchronized)int Transmit(PcapDevice device, SendQueueTransmitModes transmitMode)
基本使用如下,从文件中读取数据包添加到发送队列并发送
1 | CaptureFileReaderDevice reader = new("capture.pcap"); |
统计流量信息
使用到 ICaptureDevice 对象的 Statistics 属性,对于 LibPcapLiveDevice 对象,该属性不为 null,该属性为 ICaptureStatistics 类型,包含以下属性
ReceivedPackets:已接收的数据包数量DroppedPackets:丢失的数据包数量InterfaceDroppedPackets:接口丢包数
基本使用如下
1 | device.Open(mode: DeviceModes.Promiscuous, read_timeout: 1000); |