文章目录
- 一、添加控件
- 二、代码分析
- 2.1 代码
- 2.2 控件初始化
- 2.3 打开和关闭设备
- 2.4 开始和结束捕获
- 2.5 设置捕获条件
- 2.6 捕获数据包
- 三、运行程序
- 四、结果分析
提要:如果你通过vs打开.sln
文件,然后代码界面或者前端界面都没找到,视图里面也没找到的话。我这里有个小技巧(启用调试,会出现代码界面,可能是主函数的部分,需要你对Form1这个函数点进去,就是一个你的代码文件了)
点击中间逐语句
,出现program,然后再进入Form1
一、添加控件
二、代码分析
①利用添加的若干个控件实现捕获数据包的条件过滤
②将捕获的数据包简单分析后显示到RichTextBox控件中
2.1 代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SharpPcap;
using PacketDotNet;
using SharpPcap.LibPcap;
using System.Net;
using System.IO;
namespace 实验教程
{
// 协议类型
public enum SnapProtocol : int
{
udp = 0,
tcp = 1,
arp = 2,
rarp = 3,
ip = 4
}
public partial class Form1 : Form
{
/// <summary>
/// 全局私有变量
/// </summary>
CaptureDeviceList device_list; // 设备列表
ICaptureDevice device; // 当前选择设备
DelegateMethod disp_info; // 委托
public Form1()
{
InitializeComponent();
// 获得设备列表
device_list = GetDeviceList();
// 获取支持的协议列表
LoadProto();
// 载入所有网卡
LoadDevice();
// 显示数据包委托函数
disp_info = new DelegateMethod(Disp_PacketInfo);
}
#region 程序初始化
/// <summary>
/// 获得当前的设备列表(网卡)
/// </summary>
/// <returns></returns>
private CaptureDeviceList GetDeviceList()
{
// Print SharpPcap version
string ver = SharpPcap.Version.VersionString;
this.richTextBox1.Text = string.Format("SharpPcap {0}, Device List\n", ver);
try
{
// Retrieve the device list
CaptureDeviceList devices = CaptureDeviceList.Instance;
// If no devices were found print an error
if (devices.Count < 1)
{
this.richTextBox1.Text += "No devices were found on this machine\n";
return null;
}
this.richTextBox1.Text += "\nThe following devices are available on this machine:\n";
this.richTextBox1.Text += "----------------------------------------------------\n";
// Print out the available network devices
foreach (ICaptureDevice dev in devices)
this.richTextBox1.Text += string.Format("{0}\n", dev.ToString());
return devices;
}
catch (System.Exception ex)
{
MessageBox.Show(ex.ToString());
return null;
}
}
/// <summary>
/// 获取支持的协议列表
/// </summary>
private void LoadProto()
{
comboBox2.Items.Clear();
foreach (string e in Enum.GetNames(typeof(SnapProtocol)))
{
comboBox2.Items.Add(e);
}
comboBox2.SelectedIndex = 0;
}
/// <summary>
/// 载入所有网卡信息
/// </summary>
private void LoadDevice()
{
comboBox1.Items.Clear();
if (device_list == null)
{
MessageBox.Show("没有找到任何网卡设备!");
return;
}
foreach (LibPcapLiveDevice dev in device_list)
{
try
{
comboBox1.Items.Add(dev.Addresses[0].Addr);
}
catch
{
continue;
}
}
comboBox1.SelectedIndex = 0;
}
#endregion
/// <summary>
/// 打开设备
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
if (button1.Text == "打开设备")
{
button1.Text = "关闭设备";
groupBox1.Enabled = false;
device = device_list[comboBox1.SelectedIndex];
device.OnPacketArrival += new SharpPcap.PacketArrivalEventHandler(device_OnPacketArrival);
device.Open(DeviceMode.Promiscuous, 1000);
device.Filter = RcvPacketFilter();
device.StartCapture();
}
else
{
button1.Text = "打开设备";
groupBox1.Enabled = true;
try {
device.StopCapture();
}
catch { device.Close();
}
}
string result1 = @"C:\Users\19468\Desktop\\测试文件.txt";//结果保存到桌面
FileStream fs = new FileStream(result1, FileMode.Append);
StreamWriter wr = null;
wr = new StreamWriter(fs);
wr.WriteLine("测试!");
wr.Close();
}
#region 捕获和显示函数
/// <summary>
/// 根据要求构造数据包过滤字符串
/// </summary>
private string RcvPacketFilter()
{
SnapProtocol proto = (SnapProtocol)comboBox2.SelectedIndex;
IPAddress src_host, dst_host;
string filter = proto.ToString();
if ((textBox1.Text.Trim().Length > 0) && (IPAddress.TryParse(textBox1.Text, out src_host)))
{
filter += string.Format(" and src host {0}", src_host.ToString());
}
if ((textBox2.Text.Trim().Length > 0) && (IPAddress.TryParse(textBox2.Text, out dst_host)))
{
filter += string.Format(" and dst host {0}", dst_host.ToString());
}
return filter;
}
/// <summary>
/// 抓包事件函数,在抓到符合条件的数据包的时候该函数将被调用
/// 功能:
/// 1. 获得当前数据包的时间间隔、长度、协议类型、地址等参数
/// 2. 将信息输出到RichTextBox控件显示出来
/// </summary>
private void device_OnPacketArrival(object sender, CaptureEventArgs packet)
{
// 时间和长度的获取
DateTime time = packet.Packet.Timeval.Date;
int len = packet.Packet.Data.Length;
// 解析数据包成:IP包
Packet p = Packet.ParsePacket(packet.Packet.LinkLayerType, packet.Packet.Data);
IpPacket ip = (IpPacket)p.Extract(typeof(IpPacket));
// 数据包信息
string info = string.Format("\nsrc_addr={0}, des_addr={1}, type={2}\n",
ip.SourceAddress, ip.DestinationAddress, ip.Protocol);
info += string.Format("{0}:{1}:{2},{3} Len={4}\n",
time.Hour, time.Minute, time.Second, time.Millisecond, len);
info += string.Format(byteToHexStr(packet.Packet.Data));
// 使用委托显示结果
richTextBox1.Invoke(disp_info, info);
}
delegate void DelegateMethod(string info);
/// <summary>
/// 显示收到数据包的信息(由于捕获过程开辟了新线程,因此捕获结果需要委托来传递到RichTextBox)
/// </summary>
/// <param name="info"></param>
private void Disp_PacketInfo(string info)
{
richTextBox1.Text += info;
}
#endregion
#region 字符串与byte数组相互转换
/// <summary>
/// 字节数组转16进制字符串
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string byteToHexStr(byte[] bytes)
{
string returnStr = "";
if (bytes != null)
{
for (int i = 0; i < bytes.Length; i++)
{
returnStr += bytes[i].ToString("X2") + " ";
}
}
return returnStr;
}
/// <summary>
/// 字符串转16进制字节数组
/// </summary>
/// <param name="hexString"></param>
/// <returns></returns>
public static byte[] strToToHexByte(string hexString)
{
hexString = hexString.Replace(" ", "");
if ((hexString.Length % 2) != 0)
hexString += " ";
byte[] returnBytes = new byte[hexString.Length / 2];
for (int i = 0; i < returnBytes.Length; i++)
returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
return returnBytes;
}
/// <summary>
/// 字符串IP地址转换成byte数组
/// </summary>
/// <param name="decString"></param>
/// <returns></returns>
public static byte[] strIPToByte(string decString)
{
string[] decStringArray = decString.Split('.');
if (decStringArray.Length == 4)
{
byte[] returnBytes = new byte[4];
for (int i = 0; i < 4; i++)
{
returnBytes[i] = Convert.ToByte(decStringArray[i]);
}
return returnBytes;
}
else
{
MessageBox.Show("IP地址格式错误!(参考:192.168.0.1)");
return null;
}
}
#endregion
}
}
2.2 控件初始化
捕获设置中使用ComboBox选择网卡和协议类型。
其中,协议类型定义如下
// 协议类型
public enum SnapProtocol : int
{
udp = 0,
tcp = 1,
arp = 2,
rarp = 3,
ip = 4
}
网卡信息则由实验一中的函数获得。现将这些选项填充到 ComboBox 中,这里使用两个函数来完成这两个ComboBox的初始化,即填充Item属性。函数的具体代码如下:
/// <summary>
/// 获取支持的协议列表
/// </summary>
private void LoadProto()
{
comboBox2.Items.Clear();
foreach (string e in Enum.GetNames(typeof(SnapProtocol)))
{
comboBox2.Items.Add(e);
}
comboBox2.SelectedIndex = 0;
}
/// <summary>
/// 载入所有网卡信息
/// </summary>
private void LoadDevice()
{
comboBox1.Items.Clear();
if (device_list == null)
{
MessageBox.Show("没有找到任何网卡设备!");
return;
}
foreach (LibPcapLiveDevice dev in device_list)
{
try
{
comboBox1.Items.Add(dev.Addresses[0].Addr);
}
catch
{
continue;
}
}
comboBox1.SelectedIndex = 0;
}
这些内容是在程序一开始执行,因此同实验一中获得设备列表一样,这两个函数可以放在界面初始化以后执行,紧随GetDeviceList函数:
public Form1()
{
InitializeComponent();
// 获得设备列表
device_list = GetDeviceList();
// 获取支持的协议列表
LoadProto();
// 载入所有网卡
LoadDevice();
// 显示数据包委托函数
disp_info = new DelegateMethod(Disp_PacketInfo);
}
至此,控件的初始化就完成了,下面是实现功能的关键步骤。
2.3 打开和关闭设备
打开设备调用ICaptureDevice
对象的Open
函数实现,ICaptureDevice
对象则通过实验一获得的设备列表得到,方式如下:
ICaptureDevice device = device_list[i];
Open
函数有三个重载:
Open()
Open(DeviceMode mode)
Open(DeviceMode mode, int read_timeout)
mode
参数可选DeviceMode.Normal
和 DeviceMode.Promiscuous
,分别代表普通模式和混合模式。其中普通模式捕获到达本机或者从本机发出的数据包,忽略转发到其他终端的数据包;而混合模式将捕获所有到达网卡的数据包。
timeout
参数指示超时时间,单位为毫秒,设置为0表示不限制超时。
关闭设备使用Close
函数即可。
在本项目中,打开和关闭由按钮控制,因此该按钮(button1)的Click事件函数编辑如下,特别注意黄色高亮部分:
private void button1_Click(object sender, EventArgs e)
{
if (button1.Text == "打开设备")
{
button1.Text = "关闭设备";
groupBox1.Enabled = false;
device = device_list[comboBox1.SelectedIndex];
device.OnPacketArrival += new SharpPcap.PacketArrivalEventHandler(device_OnPacketArrival);
device.Open(DeviceMode.Promiscuous, 1000);
device.Filter = RcvPacketFilter();
device.StartCapture();
}
else
{
button1.Text = "打开设备";
groupBox1.Enabled = true;
try {
device.StopCapture();
}
catch { device.Close();
}
}
2.4 开始和结束捕获
在打开设备以后,捕获还未真正开始,捕获操作将调用专门的捕获函数后开始。捕获分为异步捕获和同步捕获,异步捕获需要注册事件 OnPacketArrival
;而同步捕获只需要在打开设备以后调用函数 GetNextPacket
,并且为了防止阻塞主线程,同步捕获通常需要开辟一个线程去实现。同步捕获方式参照下方链接文章说明:
SharpPcap - A Packet Capture Framework for .NET
本例程使用异步方式,基本流程为:
注册OnPacketArrival 事件——》StartCapture——》StopCapture。使用方式见上一节button1的Click事件灰色背景部分代码。
在StartCapture 执行后,一旦有符合条件的网络数据包到达网卡,被注册的事件函数(device_OnPacketArrival)就会执行,这一过程类似Button的Click事件的执行过程。
2.5 设置捕获条件
不对捕获的包进行条件过滤将捕获所有到达网卡的数据包是没有意义的。通常对网络封包进行分析需要能够根据封包的参数对收到的数据包进行过滤,从而快速定位到想要进行分析的封包。成熟的网络封包分析软件能够依据 IP、协议类型、端口、数据大小、通讯方向、数据内容等等一系列参数对封包进行过滤。 在SharpPcap 中,参数的过滤通过设置字符串表达式来实现,即,通过设置ICaptureDevice 对象的 Filter
属性来实现,例如:
string filter = "ip and tcp";
device.Filter = filter;
上述表达式表示只接收TCP/IP数据包。
过滤字符串的格式完全参照 WinPcap 用户手册,详细参数的定义和解释见下方链接:
Filtering expression syntax [WinPcap user’s manual]
在本教程中,展示了如何对协议类型、源IP地址和目的IP地址这三个参数进行过滤。如下方代码中高亮所示,在开始捕获之前设置Filter
属性实现封包过滤,其中RcvPacketFilter
函数用于生产过滤字符串表达式。
RcvPacketFilter 函数负责对控件中输入的参数进行判断,然后生成规定格式的字符串,代码如下:
/// <summary>
/// 根据要求构造数据包过滤字符串
/// </summary>
private string RcvPacketFilter()
{
SnapProtocol proto = (SnapProtocol)comboBox2.SelectedIndex;
IPAddress src_host, dst_host;
string filter = proto.ToString();
if ((textBox1.Text.Trim().Length > 0) && (IPAddress.TryParse(textBox1.Text, out src_host)))
{
filter += string.Format(" and src host {0}", src_host.ToString());
}
if ((textBox2.Text.Trim().Length > 0) && (IPAddress.TryParse(textBox2.Text, out dst_host)))
{
filter += string.Format(" and dst host {0}", dst_host.ToString());
}
return filter;
}
表达式最终的格式为:
proto{ and src host xxx.xxx.xxx.xxx}{ and dst host xxx.xxx.xxx.xxx}
如果TextBox 控件中填入了IP地址,则对于源IP地址(src host)或者目的IP 地址(dst host)就作为过滤条件,例如:
udp and src host 192.168.1.100 and dst host 192.168.1.101
如果对应的IP地址为空,该项就被忽略。例如,源地址TextBox为空,则最终生成的字符串为:
udp and dst host 192.168.1.101
按照上述方式构造字符串即可实现过滤,其它过滤条件使用方式类似。
2.6 捕获数据包
在设置完成后,进入捕获状态后,如4.3.3节所述,捕获到满足条件的数据包以后,程序将调用被注册的事件函数 device_OnPacketArrival
,通过该函数我们可以通过编写相关代码对捕获的数据包进行处理。
本实验展示了如何在 OnPacketArrival
中提取数据包的时间戳、长度、源IP 地址、目的IP地址以及协议类型。上述参数将伴随整个数据包内容以字符串的形式显示在RichTextBox
中。函数定义如下:
private void device_OnPacketArrival(object sender, CaptureEventArgs packet)
{
// 时间和长度的获取
DateTime time = packet.Packet.Timeval.Date;
int len = packet.Packet.Data.Length;
// 解析数据包成:IP包
Packet p = Packet.ParsePacket(packet.Packet.LinkLayerType, packet.Packet.Data);
IpPacket ip = (IpPacket)p.Extract(typeof(IpPacket));
// 数据包信息
string info = string.Format("\nsrc_addr={0}, des_addr={1}, type={2}\n",
ip.SourceAddress, ip.DestinationAddress, ip.Protocol);
info += string.Format("{0}:{1}:{2},{3} Len={4}\n",
time.Hour, time.Minute, time.Second, time.Millisecond, len);
info += string.Format(byteToHexStr(packet.Packet.Data));
// 使用委托显示结果
richTextBox1.Invoke(disp_info, info);
}
在这一节中需要对以太网的帧格式有一定的了解,本试验中部分参数需要解析数据包的内容来获得。其中程序的时间和数据长度可以直接访问属性获得,而以太网帧内的IP地址信息则需要通过分析数据包内容得到。
SharpPcap 中提供了所有报文格式的解析方式,可以方便直观地解析以太网中所有的字段,如上述代码中高亮部分:
函数Packet.ParsePacket 用于将数据 packet.Packet.Data 解析成以太网帧;Extract 函数可以提取并解析以太网帧中的各种报文。进行提取和解析后,其中的组成部分就可以通过属性的形式获得。如上述代码中的灰色部分,IP 数据报的源IP地址、目的IP地址和协议类型通过属性的形式可以直接获得。 最后,将所有内容转换成字符串,提交给委托函数进行显示。
三、运行程序
四、结果分析