SharpPcap – A Packet Capture Framework for .NET – CodeProject

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~

[A .NET sniffer application written with SharpPcap]

Introduction

Packet capturing (or packet sniffing) is the process of collecting all packets of data that pass through a given network interface. Capturing network packets in our applications is a powerful capability which lets us write network monitoring, packet analyzers and security tools. The libpcap library for UNIX based systems and WinPcap for Windows are the most widely used packet capture drivers that provide API for low-level network monitoring. Among the applications that use libpcap/WinPcap as its packet capture subsystem are the famous tcpdump and Wireshark.

In this article, we will introduce the SharpPcap .NET assembly (library) for interfacing with libpcap or winpcap from your .NET application and will give you a detailed programming tutorial on how to use it.

Background

Tamir Gal started the SharpPcap project around 2004. He wanted to use WinPcap in a .NET application while working on his final project for university. The project involved analyzing and decoding VoIP traffic and he wanted to keep coding simple with C# which has time saving features like garbage collection. Accessing the WinPcap API from .NET seemed to be quite a popular requirement, and he found some useful projects on CodeProject’s website that let you do just that:

The first project is a great ethereal .NET clone that lets you capture and analyze numerous types of protocol packets. However, a few issues with this project make it almost impossible to be shared among other .NET applications. Firstly, the author did not provide any generic API for capturing packets that can be used by other .NET applications. He didn’t separate his UI code and his analyzing and capturing code, making his capturing code depend on the GUI classes such as ListView to operate. Secondly, for some reason the author chose to re-implement some of WinPcap’s functions in C# by himself rather than just wrapping them. This means that his application can’t take advantage of the new WinPcap versions since he hard coded a certain version of WinPcap in his application.

The second and the third articles are nice starts for wrapper projects for WinPcap, however they didn’t provide some important WinPcap features such as handling offline pcap files and applying kernel-level packet filters, and most importantly they provide no parser classes for analyzing protocol packets. Both projects didn’t post their library source code together with the article in order to let other people extend their work and add new features and new packet parser classes.

And so, Tamir decided to start his own library for the task. Several versions in the 1.x series were released. Development slowed towards mid-2007 when the last version in the 1.x series was released, SharpPcap 1.6.2.

Chris Morgan took over development of SharpPcap in November of 2008. Since then SharpPcap has had major internal rewrites and API improvements.

In late February 2010, SharpPcap v3.0 was released. This release represents a rewrite of SharpPcap‘s packet parsers. Packet parsing functionality was broken out into a new library, Packet.Net. SharpPcap takes care of interfacing with libpcap/winpcap and Packet.Net takes care of packet dissection and creation. The details of Packet.Net’s architecture will be discussed later in the tutorial.

SharpPcap v3.5 was released February 1st, 2011. The 3.5 release contains significant API changes as well as WinPcap remote capture and AirPcap support.

About SharpPcap

The purpose of SharpPcap is to provide a framework for capturing, injecting and analyzing network packets for .NET applications.

SharpPcap is openly and actively developed with its source code and file releases hosted on SourceForge. Source code patches to improve or fix issues are welcome via the sharppcap developers mailing list. Bug reports, feature requests and other queries are actively answered on the support forums and issue trackers there so if you have any trouble with the library please feel free to ask.

SharpPcap is a fully managed cross platform library. The same assembly runs under Microsoft .NET as well as Mono on both 32 and 64bit platforms.

The following list illustrates the features currently supported by SharpPcap:

Please check the project homepage homepage for the latest updates and bug fixes.

SharpPcap architecture

SharpPcap has a layered architecture, at the top level are classes that work across all devices:

  • CaptureDeviceList – Retrieves a list of all devices on the system
  • OfflineCaptureDevice – Device that reads from a pcap capture file
  • ICaptureDevice – All capture devices have ICaptureDevice interfaces

The namespaces are layed out hierarchically:

  • LibPcap
    • LibPcapLiveDevice – An ICaptureDevice
    • LibPcapLiveDeviceList – Retrieves a list of LibPcapLiveDevice devices (these include pcap/winpcap and airpcap devices)
  • WinPcap
    • WinPcapDeviceList – Retrieves a list of WinPcapDevices (these include winpcap and airpcap devices)
    • WinPcapDevice – A LibPcapLiveDevice with additional WinPcap features and interfaces
  • AirPcap
    • AirPcapDeviceList – Retrieves a list of AirPcapDevices
    • AirPcapDevice – A WinPcapDevice with additional AirPcap features and interfaces

CaptureDeviceList returns a list of fully differentiated devices. This means that each ICaptureDevice returned by CaptureDeviceList is either a LibPcapLiveDevice, a WinPcapDevice or a AirPcapDevice. This allows you to retrieve the entire list of devices and differentiate by looking at the type of each device. If you would like to get a specific type of device only, you can use one of the particular *DeviceList classes.

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
// Retrieve all capture devices  var devices = CaptureDeviceList;    // differentiate based upon types  foreach(ICaptureDevice dev in devices)  {    if(dev is AirPcapDevice)    {      // process as an AirPcapDevice    } else if(dev is WinPcapDevice)    {      // process as an WinPcapDevice    } else if(dev is LibPcapLiveDevice)    {      // process as an LibPcapLiveDevice    }  }
SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
// Retrieve only WinPcap (and AirPcap devices)  var devices = AirPcapDeviceList;  foreach(AirPcapDevice dev in devices)  {    // Process this AirPcap device  }

Packet.Net Architecture and Usage

Packet.Net switched from the inheritance model of SharpPcap 2.x to one of nesting packets. All packets contain a Packet PayloadPacket property and a Byte[] PayloadData property. One or neither of these can be valid. A TCP packet captured on Ethernet may be EthernetPacket -> IPv4 Packet -> Tcp Packet. In Packet.Net, the TCP packet could be accessed like capturedPacket.PayloadPacket.PayloadPacket but to aid users static GetEncapsulsted() methods have been added so users can do var tcpPacket = TcpPacket.GetEncapsulated(capturedPacket);.

The GetEncapsulated() methods are intelligent and designed to work in many different cases. UdpPacket.GetEncapsulated() will return the Udp packet of a packet that consists of EthernetPacket -> IP packet -> UdpPacket or Linux Cooked Capture -> IP -> UdpPacket or an EthernetPacket -> PPPoE -> PTP -> IP -> UdpPacket. We recommend using the GetEncapsulated() methods to retrieve sub packets.

With Packet.Net, constructing packets looks like:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
using PacketDotNet;    ushort tcpSourcePort = 123;  ushort tcpDestinationPort = 321;  var tcpPacket = new TcpPacket(tcpSourcePort, tcpDestinationPort);    var ipSourceAddress = System.Net.IPAddress.Parse("192.168.1.1");  var ipDestinationAddress = System.Net.IPAddress.Parse("192.168.1.2");  var ipPacket = new IPv4Packet(ipSourceAddress, ipDestinationAddress);    var sourceHwAddress = "90-90-90-90-90-90";  var ethernetSourceHwAddress =    System.Net.NetworkInformation.PhysicalAddress.Parse(sourceHwAddress);  var destinationHwAddress = "80-80-80-80-80-80";  var ethernetDestinationHwAddress =    System.Net.NetworkInformation.PhysicalAddress.Parse(destinationHwAddress);    // NOTE: using EthernetPacketType.None to illustrate that the ethernet  //       protocol type is updated based on the packet payload that is  //       assigned to that particular ethernet packet  var ethernetPacket = new EthernetPacket(ethernetSourceHwAddress,      ethernetDestinationHwAddress,      EthernetPacketType.None);    // Now stitch all of the packets together  ipPacket.PayloadPacket = tcpPacket;  ethernetPacket.PayloadPacket = ipPacket;    // and print out the packet to see that it looks just like we wanted it to  Console.WriteLine(ethernetPacket.ToString());    // to retrieve the bytes that represent this newly created   // EthernetPacket use the Bytes property  byte[] packetBytes = ethernetPacket.Bytes;

SharpPcap Tutorial: A Step by Step Guide to Using SharpPcap

Examples can be found in the Examples/ directory of the source package.

The text of this tutorial was taken directly from WinPcap’s official tutorial but is modified to show the C# use of the SharpPcap library. All examples can be downloaded together with the SharpPcap source code from the top of this page. If you are running on Windows, the WinPcap library must be installed before attempting to run any of these examples so please download and install the latest version from WinPcap’s download page. If running under unix/linux/mac, the libpcap library must be installed using your systems software management system.

The following topics are covered in this tutorial with the name of the example in parenthesis:

  1. Obtaining the device list (Example 1)
  2. Opening an adapter and capturing packets (Example 3)
  3. Capturing packets without the event (Example 4)
  4. Filtering the traffic (Example 5)
  5. Interpreting the packets (Example 6)
  6. Handling offline dump files (Example 8)
  7. Sending packets (Example 9)
  8. Send Queues (Example 10) – WinPcap only
  9. Gathering statistics on the network traffic (Example 11) – WinPcap only
  10. Queuing packets for background processing – High packet rate capture

Obtaining the Device List (Example 1 in the Source Package)

Typically, the first thing that a SharpPcap-based application does is get a list of attached network adapters. SharpPcap provides a class, CaptureDeviceList for this purpose. The class is a singleton instance that holds a cached list of network adapters of type ICaptureDevice. In particular, the Name and Description properties contain the name and a human readable description, respectively, of the corresponding device. The following C# sample shows how to retrieve a list of adapters and print it on the screen, printing an error if no adapters are found:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
// Print SharpPcap version  string ver = SharpPcap.Version.VersionString;  Console.WriteLine("SharpPcap {0}, Example1.IfList.cs", ver);    // Retrieve the device list  CaptureDeviceList devices = CaptureDeviceList.Instance;    // If no devices were found print an error  if(devices.Count < 1)  {      Console.WriteLine("No devices were found on this machine");      return;  }    Console.WriteLine("nThe following devices are available on this machine:");  Console.WriteLine("----------------------------------------------------n");    // Print out the available network devices  foreach(ICaptureDevice dev in devices)      Console.WriteLine("{0}n", dev.ToString());    Console.Write("Hit 'Enter' to exit...");  Console.ReadLine();

The output of the above application will be something like this:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
c:sharppcapExamplesExample1.IfListbinDebug>Example1.IfList.exe  SharpPcap 3.5.0.0, Example1.IfList.cs    The following devices are available on this machine:  ----------------------------------------------------    interface: Name: DeviceNPF_{D8B7C9B2-D53D-45DA-ACF0-2E2116F97314}  FriendlyName: Local Area Connection 2  Description: Intel(R) PRO/1000 MT Desktop Adapter  Addresses:  Addr:      fe80::b444:92d8:c882:8227  Netmask:  Broadaddr:    Addresses:  Addr:      10.0.2.15  Netmask:   255.255.255.0  Broadaddr: 255.255.255.255    Addresses:  Addr:      HW addr: 0800276AC792    Flags: 0    Hit 'Enter' to exit...

Opening an Adapter and Capturing Packets (Example 3 in the Source Package)

Now that we’ve seen how to obtain an adapter to play with, let’s start the real job, opening an adapter and capturing some traffic. In this section, we’ll write a program that prints some information about each packet flowing through the adapter.

The function that opens a device for capture is Open() which is overloaded with some arguments as follows:

  • Open()
  • Open(DeviceMode mode)
  • Open(DeviceMode mode, int read_timeout)

The above two arguments deserve some further explanation.

DeviceMode: In normal mode (DeviceMode.Normal), a network adapter only captures packets addressed directly to it; the packets exchanged by other hosts on the network are ignored. Instead, when the adapter is in promiscuous mode (DeviceMode.Promiscuous), it captures all packets whether they are destined to it or not. This means that on shared media (like non-switched Ethernet), libpcap/WinPcap will be able to capture the packets of other hosts. Promiscuous mode is the default for most capture applications, so we enable it in the following example. NOTE: Promiscuous mode can be detected via network means so if you are capturing in promiscuous mode you may be able to be detected by other entities on the network. Search for “detect promiscuous” via a web search engine for more information.

read_timeout: Specifies the read timeout, in milliseconds. A read on the adapter (for example, using the GetNextPacket() function) will always return after read_timeout milliseconds, even if no packets are available from the network. read_timeout also defines the interval between statistical reports if the adapter is in statistical mode (see the Gathering statistics on the network traffic section). Setting read_timeout to 0 means no timeout, a read on the adapter never returns if no packets arrive. A -1 timeout on the other side causes a read on the adapter to always return immediately.

The following example shows the use of the OnPacketArrival event for receiving packets. We create an event handler that is being called whenever a new packet is going through the ICaptureDevice:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
// Extract a device from the list  ICaptureDevice device = devices[i];    // Register our handler function to the  // 'packet arrival' event  device.OnPacketArrival +=      new SharpPcap.PacketArrivalEventHandler(device_OnPacketArrival);    // Open the device for capturing  int readTimeoutMilliseconds = 1000;  device.Open(DeviceMode.Promiscuous, readTimeoutMilliseconds);    Console.WriteLine("-- Listening on {0}, hit 'Enter' to stop...",      device.Description);    // Start the capturing process  device.StartCapture();    // Wait for 'Enter' from the user.  Console.ReadLine();    // Stop the capturing process  device.StopCapture();    // Close the pcap device  device.Close();

And here is our packet handler implementation:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
/// <summary> /// Prints the time and length of each received packet  /// </summary> private static void device_OnPacketArrival(object sender, CaptureEventArgs packet)  {      DateTime time = packet.Timeval.Date;      int len = packet.Data.Length;      Console.WriteLine("{0}:{1}:{2},{3} Len={4}",          time.Hour, time.Minute, time.Second, time.Millisecond, len);  }

Once the adapter is opened, the capture can be started with the StartCapture() or Capture(int packetCount) functions. These two functions are very similar, the difference is that StartCapture() is a non-blocking function that starts the capturing process on a new thread, while Capture(int packetCount) blocks until packetCount packets have been captured. When using StartCapture() we should later call StopCapture() to terminate the capture process. To capture indefinitely, call the Capture() method.

Both of these functions require that an event handler for processing packets registered prior to calling them. This event handler is invoked by ICaptureDevice for every new packet coming from the network and receives the sender object that invoked this handler (i.e. the ICaptureDevice object) and the actual received Packet, including all the protocol headers. Note that the frame CRC is normally not present in the packet, because it is removed by the network adapter after the frame validation. Note also that most adapters discard packets with wrong CRCs, so WinPcap (and therefore SharpPcap) is normally not able to capture them.

The Packet class represents a generic packet captured from the network. Each such packet has a PcapHeader property containing some info (e.g. the timestamp of the capture and the length of the packet) about the captured packet. The above example extracts the timestamp and the length from every Packet object and prints them on the screen.

Please note that the handler code is called by the ICaptureDevice; therefore the user application does not have direct control over it. Another approach is to use the GetNextPacket() function, which is presented in the next section.

Capturing Packets Without the Event Handler (Example 4 in the Source Package)

The example program in this section behaves exactly like the previous sample, but it uses ICaptureDevice.GetNextPacket() method instead of registering an event handler. The OnPacketArrival event is a good practice and could be a good choice in some situations, such as when capturing from several devices at once. However, handling a callback is sometimes not practical – it often makes the program more complex especially in situations with multithreaded applications. In these cases, GetNextPacket() retrieves a packet with a direct call – using GetNextPacket(), packets are received only when the programmer wants them. In the following program, we re-use the event handler code of the previous example and move it into a loop in the main function right after the call to GetNextPacket().

Note: The following example will exit if the timeout of 1000 ms expires with no packets on the network:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
// Extract a device from the list  ICaptureDevice device = devices[i];    // Open the device for capturing  int readTimeoutMilliseconds = 1000;  device.Open(DeviceMode.Promiscuous, readTimeoutMilliseconds);    Console.WriteLine();  Console.WriteLine("-- Listening on {0}...",      device.Description);    Packet packet = null;    // Keep capture packets using GetNextPacket()  while((packet=device.GetNextPacket()) != null )  {      // Prints the time and length of each received packet      DateTime time = packet.PcapHeader.Date;      int len = packet.PcapHeader.PacketLength;      Console.WriteLine("{0}:{1}:{2},{3} Len={4}",          time.Hour, time.Minute, time.Second,          time.Millisecond, len);  }    // Close the pcap device  device.Close();  Console.WriteLine(" -- Capture stopped, device closed.");

Filtering the Traffic (Example 5 in the Source Package)

One of the most powerful features offered by libpcap and WinPcap is the filtering engine. It provides a very efficient way to receive subsets of the network traffic. WinPcap and libpcap have an integrated compiler that takes a string containing a high-level Boolean (filter) expression and produces a low-level byte code that can be interpreted by the filter engine of the packet capture driver. The syntax (also known as the tcpdump syntax) of the boolean expression is widely used in many applications other than WinPcap and libpcap. You can find its spec in WinPcap’s documentation page or if you are running linux via ‘man pcap-filter’.

The Filter property associates a filter with a capture adapter. Once Filter is set, the associated filter will be applied to all the packets coming from the network, and all the conformant packets (i.e., packets for which the boolean expression evaluates to true) will be actually copied to the application. The following code shows how to compile and set a filter.

Note that libpcap/winpcap’s expression compiler requires that the netmask of the ICaptureDevice to be passed together with the filter, because some filters created by the compiler require it. However SharpPcap takes care of it for us by automatically extracting the netmask from the adapter.

The filter expression we use in the following snippet is “ip and tcp”, which means to “keep only the packets that are both IPv4 and TCP and deliver them to the application”:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
// Open the device for capturing  int readTimeoutMilliseconds = 1000;  device.Open(DeviceMode.Promiscuous, readTimeoutMilliseconds);    // tcpdump filter to capture only TCP/IP packets  string filter = "ip and tcp";  device.Filter = filter;    Console.WriteLine();  Console.WriteLine("-- The following tcpdump filter will be applied: "{0}"",      filter);  Console.WriteLine("-- Listening on {0}, hit 'Enter' to stop...",      device.Description);    // Start capturing packets indefinitely  device.Capture();    // Close the pcap device  // (Note: this line will never be called since  // we're capturing indefinitely  device.Close();

Interpreting the Packets (Example 6 in the Source Package)

Now that we are able to capture and filter network traffic, we want to put our knowledge to work with a simple “real world” application. In this lesson, we will take the code from the previous sections and use these pieces to build a more useful program. The main purpose of the current program is to show how the protocol headers of a captured packet can be parsed and interpreted. The resulting application, called DumpTCP, prints a summary of the TCP traffic on our network. I have chosen to parse and display the TCP protocol (rather than the UDP example posted in the original tutorial) because it is a bit more interesting than UDP and with SharpPcap it doesn’t require too much parsing coding.

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
/// <summary> /// Prints the time, length, src ip,  /// src port, dst ip and dst port  /// for each TCP/IP packet received on the network  /// </summary> private static void device_OnPacketArrival(      object sender, CaptureEventArgs e)  {      var tcp = TcpPacket.GetEncapsulated(e.Packet);      if(tcp != null)      {          DateTime time = e.Packet.Timeval.Date;          int len = e.Packet.Data.Length;            string srcIp = tcp.SourceAddress;          string dstIp = tcp.DestinationAddress;            Console.WriteLine("{0}:{1}:{2},{3} Len={4}",              time.Hour, time.Minute, time.Second,              time.Millisecond, len);          Console.WriteLine(e.Packet.ToString());      }  }

If you take a look at the UDP example of the original WinPcap tutorial, you will see how complex it is to parse the packets (although UDP is a bit simpler to parse than TCP in our example) directly from the raw data bytes provided by the WinPcap library. Luckily for us, SharpPcap provides some useful packet analyzing classes for some common protocols (e.g. TCP, UDP, ICMP and others). These analyzing classes were initially a direct C# translation from JPcap, a Java wrapper for libpcap/WinPcap similar to SharpPcap, but significant changes have been made to make them fit better into .NET. All of the packet parsing and generation code can be found in the PacketDotNet namespace in the PacketDotNet assembly.

As you can see, in our packet handler, we first attempt to extract the TCP packet encapsulated in the packet that was captured on the line. If one was found, and it should be since our filter selects only TCP packets, we can then access its properties. If we also wanted to see the IP packet properties these could be accessed via var ip = IpPacket.GetEncapsulated(e.Packet);

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
Available devices:  ------------------    1) Intel(R) PRO/1000 MT Mobile Connection     (Microsoft's Packet Scheduler)    -- Please choose a device to capture: 1    -- Listening on Intel(R) PRO/1000 MT Mobile Connection  -- (Microsoft's Packet Scheduler)...  1:18:17,675 Len=123 66.102.7.147:80 -> 10.21.98.21:43501  1:18:17,675 Len=80 10.21.98.21:43501 -> 66.102.7.147:80  1:18:17,919 Len=54 66.102.7.147:80 -> 10.21.98.21:43501

Each of the final three lines represents a different packet.

Handling Offline Dump Files (Example 8 in the Source Package)

In this section, we are going to learn how to handle packet capture to a file (dump to file). Libpcap/WinPcap offer built-in functions for saving network traffic to a file and to read the content of dumps – this section will teach you how to accomplish this with SharpPcap. The format for dump files is the libpcap one. This format contains the data of the captured packets in binary form and is a standard widely used by many network tools including wireshark, windump, tcpdump and snort. Therefore, any dump file we create using SharpPcap can be opened with any of the above tools and others and files created by these tools can be opened by SharpPcap.

Saving Packets to a Dump File

First of all, let’s see how to write packets in libpcap file format. The following example captures the packets from the selected interface and saves them on a file whose name is provided by the user:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
Console.Write("-- Please enter the output file name: ");  string capFile = Console.ReadLine();    ICaptureDevice device = CaptureDeviceList.Instance[i];    // Register our handler function to the 'packet arrival' event  device.OnPacketArrival +=      new SharpPcap.PacketArrivalEventHandler(device_OnPacketArrival);    // Open the device for capturing  int readTimeoutMilliseconds = 1000;  device.Open(DeviceMode.Promiscuous, readTimeoutMilliseconds);    // Open or create a capture output file  device.DumpOpen(capFile);    Console.WriteLine();  Console.WriteLine("-- Listening on {0}, hit 'Ctrl-C' to exit...",      device.Description);    // Start capturing indefinitely  device.Capture();    // Close the pcap device  // (Note: this line will never be called since  // we're capturing forever  device.Close();

And here is the packet handler that will dump each received packet to the file:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
/// <summary> /// Dumps each received packet to a pcap file  /// </summary private static void device_OnPacketArrival( object sender, CaptureEventArgs packet) { ICaptureDevice device = (ICaptureDevice)sender; // if device has a dump file opened if(device.DumpOpened) { // dump the packet to the file device.Dump(packet); Console.WriteLine("Packet dumped to file."); } }

As you can see, the structure of the program is very similar to the ones we have seen in the previous sections. The differences are:

  • The call to device.DumpOpen( capFile ) is issued once the interface is opened. This call opens a dump file and associates it with the interface.
  • The packets are written to this file with the device.Dump( packet ) call in the packet handler. Note the use of the sender object parameter passed to the packet handler callback which is cast to a ICaptureDevice.

Reading Packets from a Dump File

Now that we have a dump file available, we can try to read its content. The following code opens a WinPcap/libpcap dump file and displays every packet contained in the file. The SharpPcap.OfflineCaptureDevice(capFile) class is a ICaptureDevice object which represents the offline capture file that we read, then the usual OnPacketArrival event is used to sequence through the packets. As you can see, reading packets from an offline capture is nearly identical to receiving them from a physical interface:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
Console.Write("-- Please enter an input capture file name: ");  string capFile = Console.ReadLine();    ICaptureDevice device;    try  {      // Get an offline file pcap device      device = new OfflineCaptureDevice(capFile);      // Open the device for capturing      device.Open();  }  catch(Exception e)  {      Console.WriteLine(e.Message);      return;  }    // Register our handler function to the 'packet arrival' event  device.OnPacketArrival +=      new SharpPcap.PacketArrivalEventHandler(device_OnPacketArrival);    Console.WriteLine();  Console.WriteLine("-- Capturing from '{0}', hit 'Ctrl-C' to exit...",      capFile);    // Start capturing an indefinite number of packets  device.Capture();    // Close the pcap device  device.Close();  Console.WriteLine("-- End of file reached.");

Sending Packets (Example 9 in the Source Package)

The simplest way to send a packet is shown in the following code snippet. After opening an adapter, SendPacket is called to send a hand-crafted packet. SendPacket takes as argument a byte array or a Packet object containing the data to be sent. Notice that the buffer is sent to the network as it was received. This means that the application has to create the correct protocol headers in order to send something meaningful:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
// Open the device  device.Open();    // Generate a random packet  byte[] bytes = GetRandomPacket();    try  {      // Send the packet out the network device      device.SendPacket( bytes );      Console.WriteLine("-- Packet sent successfully.");  }  catch(Exception e)  {      Console.WriteLine("-- "+ e.Message );  }    // Close the pcap device  device.Close();  Console.WriteLine("-- Device closed.");

Send Queues – WinPcap Specific Extension (Example 10 in the Source Package)

While SendPacket offers a simple and immediate way to send a single packet, send queues provide an advanced, powerful and optimized mechanism to send a collection of packets. A send queue is a container for a variable number of packets that will be sent to the network. It has a size, that represents the maximum amount of bytes it can store.

Because SendQueue functionality is WinPcap specific, the authors of SharpPcap recommend benchmarking your particular usage of sending packets to determine if the loss of cross platform support is worth the added efficiency of using send queues. The old adage, “avoid premature optimization” should be carefully considered.

SharpPcap represents a send queue using the SendQueue class which is constructed by specifying the size of the new send queue.

Once the send queue is created, SendQueue.Add() can be called to add a packet to the send queue. This function takes a PcapHeader with the packet’s timestamp and length and a buffer or a Packet object holding the data of the packet. These parameters are the same as those received by the OnPacketArrival event, therefore queuing a packet that was just captured or a read from a file is a matter of passing these parameters to SendQueue.Add().

To transmit a send queue, SharpPcap provides the WinPcapDevice.SendQueue(SendQueue q, SendQueueTransmitModes transmitMode) function. Note the second parameter: if SendQueueTransmitModes.Synchronized, the send will be synchronized, i.e. the relative timestamps of the packets will be respected. This operation requires a remarkable amount of CPU, because the synchronization takes place in the kernel driver using “busy wait” loops. Although this operation is quite CPU intensive, it often results in very high precision packet transmissions (often around few microseconds or less).

Note that transmitting a send queue with WinPcapDevice.SendQueue() is more efficient than performing a series of ICaptureDevice.SendPacket(), since the send queue buffered at kernel level drastically decreases the number of context switches.

When a queue is no longer needed, it can be deleted with SendQueue.Dispose() that frees all the buffers associated with the send queue.

The next program shows how to use send queues. It opens a capture file by creating a OfflineCaptureDevice(), then it stores the packets from the file to a properly allocated send queue. At this point, it transmits the queue synchronized.

Note that the link-layer of the dumpfile is compared with one of the interfaces that will send the packets using the PcapDevice.DataLink property, and a warning is printed if they are different – it is important that the capture-file link-layer be the same as the adapter’s link layer for otherwise the transmission is pointless:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
ICaptureDevice device;    try  {      // Get an offline file pcap device      device = new OfflineCaptureDevice(capFile);      // Open the device for capturing      device.Open();  }  catch(Exception e)  {      Console.WriteLine(e.Message);      return;  }    Console.Write("Queueing packets...");    // Allocate a new send queue  SendQueue squeue = new SendQueue      ((int)((OfflineCaptureDevice)device).PcapFileSize);  Packet packet;    try  {      // Go through all packets in the file and add to the queue   while((packet=device.GetNextPacket()) != null )      {          if(!squeue.Add(packet))          {              Console.WriteLine("Warning: packet buffer too small, "+                  "not all the packets will be sent.");              break;          }      }  }  catch(Exception e)  {      Console.WriteLine(e.Message);      return;  }    Console.WriteLine("OK");    Console.WriteLine();  Console.WriteLine("The following devices are available on this machine:");  Console.WriteLine("----------------------------------------------------");  Console.WriteLine();    int i=0;    var devices = CaptureDeviceList.Instance;    // Print out all available devices  foreach(ICaptureDevice dev in devices)  {      Console.WriteLine("{0}) {1}", i, dev.Description);      i++;  }    Console.WriteLine();  Console.Write("-- Please choose a device to transmit on: ");  i = int.Parse(Console.ReadLine());  devices[i].Open();  string resp;    if(devices[i].PcapDataLink != device.PcapDataLink)  {      Console.Write("Warning: the datalink of the capture"+          "differs from the one of the selected interface,          continue? [YES|no]");      resp = Console.ReadLine().ToLower();        if((resp!="")&&(!resp.StartsWith("y")))      {          Console.WriteLine("Cancelled by user!");          devices[i].Close();          return;      }  }    // Close the offline device  device.Close();    // find the network device for sending the packets we read  device = devices[i];    Console.Write("This will transmit all queued packets through"+      "this device, continue? [YES|no]");  resp = Console.ReadLine().ToLower();    if((resp!="")&&( !resp.StartsWith("y")))  {      Console.WriteLine("Cancelled by user!");      return;  }    try  {      Console.Write("Sending packets...");      int sent = device.SendQueue(squeue, SendQueueTransmitModes.Synchronized);      Console.WriteLine("Done!");      if(sent < squeue.CurrentLength)      {          Console.WriteLine("An error occurred sending the packets: {0}. "+              "Only {1} bytes were sentn", device.LastError, sent);      }  }  catch(Exception e)  {      Console.WriteLine("Error: "+e.Message );  }    // Free the queue  squeue.Dispose();  Console.WriteLine("-- Queue is disposed.");    // Close the pcap device  device.Close();  Console.WriteLine("-- Device closed.");

Gathering Statistics on the Network Traffic – WinPcap Only (Example 11 in the Source Package)

Adapter statistics are available by calling the ICaptureDevice.Statistics property. The Statistics property is supported by all ICaptureDevice types. WinPcap has a statistics extension that provides statistics callbacks that can be more efficient than retrieving device statistics over and over. WinPcap’s statistical engine makes use of the kernel-level packet filter to efficiently classify the incoming packet. You can take a look at the NPF driver internals manual if you want to learn more about the details.

In order to use this feature, the programmer must open an adapter and put it in statistical mode. This can be done by setting the PcapDevice.Mode property. In particular, the PcapDevice.Mode property should be set to CaptureMode.Statistics.

With the statistical mode, making an application that monitors the TCP traffic load is a matter of a few lines of code. The following sample shows how to do this:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
// Register our handler function to the  // 'winpcap statistics' event  device.OnPcapStatistics +=      new WinPcap.StatisticsModeEventHandler(device_OnPcapStatistics);    // Open the device for capturing  int readTimeoutMilliseconds = 1000;  device.Open(DeviceMode.Promiscuous, readTimeoutMilliseconds);    // Handle TCP packets only  device.Filter = "tcp";    // Set device to statistics mode  device.Mode = CaptureMode.Statistics;    Console.WriteLine();  Console.WriteLine("-- Gathering statistics on "{0}",      hit 'Enter' to stop...", device.Description);    // Start the capturing process  device.StartCapture();    // Wait for 'Enter' from the user.  Console.ReadLine();    // Stop the capturing process  device.StopCapture();    // Close the pcap device  device.Close();  Console.WriteLine("Capture stopped, device closed.");

And our event handler will print the statistics:

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
static int oldSec = 0;  static int oldUsec = 0;    /// <summary> /// Gets a pcap stat object and calculate bps and pps  /// </summary> private static void device_OnPcapStatistics(               object sender, StatisticsModeEventArgs statistics)  {      // Calculate the delay in microseconds   // from the last sample.   // This value is obtained from the timestamp   // that's associated with the sample.   int delay = (statistics.Seconds - oldSec) *          1000000 - oldUsec + statistics.MicroSeconds;      /* Get the number of Bits per second */ long bps = (statistics.RecievedBytes * 8 * 1000000) / delay;      /*                                  ^        ^                                       |        |                                       |        |                                       |        |              converts bytes in bits      -----         |            delay is expressed in microseconds ---------      */ // Get the number of Packets per second   long pps = (statistics.RecievedPackets * 1000000) / delay;        // Convert the timestamp to readable format   string ts = statistics.Date.ToLongTimeString();        // Print Statistics      Console.WriteLine("{0}: bps={1}, pps={2}",          ts, bps, pps);        // store current timestamp      oldSec = statistics.Seconds;      oldUsec = statistics.MicroSeconds;  }

Note that this example is by far more efficient than a program that captures the packets in the traditional way and calculates statistics at the user-level. Statistical mode requires minimum amount of data copies and context switches and therefore the CPU is optimized. Moreover, a very small amount of memory is required. The downside is that if your application uses statistical mode, it will only work with WinPcap on Windows.

Queuing Packets for Background Processing – High Packet Rate Capture (Example QueuingPacketsForBackgroundProcessing in the Source Package)

Packet rates can exceed the rate at which they can be processed by the callback routines. This can occur if the packet processing code is time intensive, such as writing to disk, or if the packet processing code is performing advanced logic. Sometimes it is possible to reduce packet rates by applying bnf filters, but in other cases these filters can be complex.

If packet rates only exceed the rate of processing for short periods of time, it is possible to simply defer packet processing until periods of less activity. One technique for this is to queue the packets and process them in a background thread as shown in this example.

SharpPcap - A Packet Capture Framework for .NET - CodeProject - 夏 - 寻找生命中的那盏明灯,不要迷失在黑夜里~ Collapse | Copy Code
using System;  using System.Collections.Generic;  using SharpPcap;    namespace QueuingPacketsForBackgroundProcessing  {      /// <summary>  /// Basic capture example showing simple queueing for background processing   /// </summary>  public class MainClass      {          /// <summary>  /// When true the background thread will terminate   /// </summary>  /// <param name="args">  /// A <see cref="System.String"/>  /// </param>  private static bool BackgroundThreadStop = false;            /// <summary>  /// Object that is used to prevent two threads from accessing   /// PacketQueue at the same time   /// </summary>  /// <param name="args">  /// A <see cref="System.String"/>  /// </param>  private static object QueueLock = new object();            /// <summary>  /// The queue that the callback thread puts packets in. Accessed by   /// the background thread when QueueLock is held   /// </summary>  private static List<packetdotnet.rawpacket> PacketQueue =       new List<packetdotnet.rawpacket>();            /// <summary>  /// The last time PcapDevice.Statistics() was called on the active device.   /// Allow periodic display of device statistics   /// </summary>  /// <param name="args">  /// A <see cref="System.String"/>  /// </param>  private static DateTime LastStatisticsOutput = DateTime.Now;            /// <summary>  /// Interval between PcapDevice.Statistics() output   /// </summary>  /// <param name="args">  /// A <see cref="System.String"/>  /// </param>  private static TimeSpan LastStatisticsInterval = new TimeSpan(0, 0, 2);            /// <summary>  /// Basic capture example   /// </summary>  public static void Main(string[] args)          {              // Print SharpPcap version   string ver = SharpPcap.Version.VersionString;              Console.WriteLine("SharpPcap {0}", ver);                // If no device exists, print error   if(CaptureDeviceList.Instance.Count < 1)              {                  Console.WriteLine("No device found on this machine");                  return;              }                Console.WriteLine();              Console.WriteLine("The following devices are available on this machine:");              Console.WriteLine("----------------------------------------------------");              Console.WriteLine();                int i=0;                // Print out all devices   foreach(ICaptureDevice dev in CaptureDeviceList.Instance)              {                  Console.WriteLine("{0}) {1} {2}", i, dev.Name, dev.Description);                  i++;              }                Console.WriteLine();              Console.Write("-- Please choose a device to capture: ");              i = int.Parse(Console.ReadLine());                // start the background thread   var backgroundThread = new System.Threading.Thread(BackgroundThread);              backgroundThread.Start();                ICaptureDevice device = CaptureDeviceList.Instance[i];                // Register our handler function to the 'packet arrival' event              device.OnPacketArrival +=                  new PacketArrivalEventHandler(device_OnPacketArrival);                // Open the device for capturing              device.Open();                Console.WriteLine();              Console.WriteLine("-- Listening on {0}, hit 'Enter' to stop...",                  device.Description);                // Start the capturing process              device.StartCapture();                // Wait for 'Enter' from the user. We pause here until being asked to   // be terminated              Console.ReadLine();                // Stop the capturing process              device.StopCapture();                Console.WriteLine("-- Capture stopped.");                // ask the background thread to shut down              BackgroundThreadStop = true;                // wait for the background thread to terminate              backgroundThread.Join();                // Print out the device statistics              Console.WriteLine(device.Statistics().ToString());                // Close the pcap device              device.Close();          }            /// <summary>  /// Prints the time and length of each received packet   /// </summary>  private static void device_OnPacketArrival(object sender, CaptureEventArgs e)          {              // print out periodic statistics about this device   var Now = DateTime.Now;  // cache 'DateTime.Now'    // for minor reduction in cpu overhead   var interval = Now - LastStatisticsOutput;              if(interval > LastStatisticsInterval)              {                  Console.WriteLine("device_OnPacketArrival: " +      ((ICaptureDevice)e.Device).Statistics());                  LastStatisticsOutput = Now;              }                // lock QueueLock to prevent multiple threads accessing PacketQueue at   // the same time   lock(QueueLock)              {                  PacketQueue.Add(e.Packet);              }          }            /// <summary>  /// Checks for queued packets. If any exist it locks the QueueLock, saves a   /// reference of the current queue for itself, puts a new queue back into   /// place into PacketQueue and unlocks QueueLock. This is a minimal amount of   /// work done while the queue is locked.   ///  /// The background thread can then process queue that it saved without holding   /// the queue lock.   /// </summary>  private static void BackgroundThread()          {              while(!BackgroundThreadStop)              {                  bool shouldSleep = true;                    lock(QueueLock)                  {                      if(PacketQueue.Count != 0)                      {                          shouldSleep = false;                      }                  }                    if(shouldSleep)                  {                      System.Threading.Thread.Sleep(250);                  }                  else // should process the queue                  {                      List<PacketDotNet.RawPacket> ourQueue;                      lock(QueueLock)                      {                          // swap queues, giving the capture callback a new one                          ourQueue = PacketQueue;                          PacketQueue = new List<PacketDotNet.RawPacket>();                      }                        Console.WriteLine("BackgroundThread: ourQueue.Count is {0}",          ourQueue.Count);                        foreach(var packet in ourQueue)                      {                          var time = packet.Timeval.Date;                          var len = packet.Data.Length;                          Console.WriteLine("BackgroundThread: {0}:{1}:{2},{3} Len={4}",                              time.Hour, time.Minute, time.Second, time.Millisecond, len);                      }                        // Here is where we can process our packets freely without   // holding off packet capture.   //  // NOTE: If the incoming packet rate is greater than   //       the packet processing rate these queues will grow   //       to enormous sizes. Packets should be dropped in these   //       cases                    }              }          }      }  }

References

History

  • 2011-Feb-01
    • Updated for SharpPcap v3.5 API changes
  • 2010-Nov-07
    • Updated for SharpPcap v3.x
    • Added queueing example for high packet rates
  • 2010-Jan-27
    • Updated for the current version of SharpPcap, v2.4.0
  • 2005-Nov-27
    • Initial version posted

License

This article, along with any associated source code and files, is licensed under The Mozilla Public License 1.1 (MPL 1.1)

About the Authors

Tamir Gal

Software Developer

Israel Israel

Member

Works as a Network Engineer for a leading networking company.

chmorgan



United States United States

Member