前言
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); |