- END #header -->

Packet capture with C++ & Linux

As part of a larger project I’m working on I need to refamiliarize myself with some old friends: C and libcap. While I do have a healthy dislike for coding in C, I have a much healthier respect for those who have used it to create some pretty fantastic stuff. It still takes conscious effort to not spend all my time wrapping C code in C++ objects, but I digress.

libpcap is a C library used to capture and analyze network packets off the wire, otherwise known as packet sniffing. Some of the more prominent applications out there using it right now are Snort intrusion detection system, Wireshark network analyzer (formerly known as Ethereal), and the proprietor of libpcap, tcpdump.

Why would you want to be able to analyze packets in code and not use one of these applications? Because packet sniffing, in the hands of someone knowledgeable in network protocols, can be a great, non-intrusive way to gather discrete or statistical information and tie it to other coded business logic. There’s also that little, evil, don-your-black-hat, I-wanna-be-a-hacker kind of feel to seeing packets you have no explicit reason to see. Motives aside, let’s see a simple example of how it works in Linux with a little C++ tacked on.

The Code

#include 
#include 
 
using namespace std;
 
static int packetCount = 0;
 
void packetHandler(u_char *userData, const struct pcap_pkthdr* pkthdr, const u_char* packet) {
        cout << ++packetCount << " packet(s) captured" << endl;
}
 
int main() {
        char *dev;
        pcap_t *descr;
        char errbuf[PCAP_ERRBUF_SIZE];
 
        dev = pcap_lookupdev(errbuf);
        if (dev == NULL) {
                cout << "pcap_lookupdev() failed: " << errbuf << endl;
                return 1;
        }
 
        descr = pcap_open_live(dev, BUFSIZ, 0, -1, errbuf);
        if (descr == NULL) {
                cout << "pcap_open_live() failed: " << errbuf << endl;
                return 1;
        }
 
        if (pcap_loop(descr, 10, packetHandler, NULL) < 0) {
                cout << "pcap_loop() failed: " << pcap_geterr(descr);
                return 1;
        }
 
        cout << "capture finished" << endl;
 
        return 0;
}

The Output

1 packet(s) captured
2 packet(s) captured
3 packet(s) captured
4 packet(s) captured
5 packet(s) captured
6 packet(s) captured
7 packet(s) captured
8 packet(s) captured
9 packet(s) captured
10 packet(s) captured
capture finished

In this very basic example we are simply setting up a network interface to use libpcap, capturing a few packets, and exiting with a message. Pretty vanilla, but there’s a good bit going on here. let’s break it down in more detail.

The Breakdown

#include 
#include 
using namespace std;

Here along with standard includes we also include pcap.h. This is the header file that is necessary for all libpcap operations and constants.


static int packetCount = 0;
void packetHandler(u_char *userData, const struct pcap_pkthdr* pkthdr, const u_char* packet) {
        cout << ++packetCount << " packet(s) captured" << endl;
}

This is the callback packet handler function that will do all the heavy lifting when it comes to network analysis. This callback is referenced later by the pcap_loop() function. Every packet that pcap receives will be passed to this function. Here’s the explanation of the parameters:

  • userData - a pointer to user defined data that can be passed into each callback, as defined in the pcap_loop() call’s 4th parameter.
  • pkthdr - the pcap packet header that includes relevant information about the packet as it relates to pcap’s capture.
  • packet - a pointer to the actual packet that will be analyzed.

For the sake of this example, we won’t be doing any protocol analysis and will simply be counting the number of packets that pass over our listening interface.


int main() {
        char *dev;
        pcap_t *descr;
        char errbuf[PCAP_ERRBUF_SIZE];

The start of our packet capture program.

  • dev – this will hold the name of the interface on which we will sniff
  • descr – The descriptor, or handle, for the libpcap packet capture
  • errbuf – a character buffer to contain any potential errors from libpcap. The max error size is defined by PCAP_ERRBUF_SIZE in pcap.h.

 dev = pcap_lookupdev(errbuf);
        if (dev == NULL) {
                cout << "pcap_lookupdev() failed: " << errbuf << endl;
                return 1;
        }

Here we use pcap_lookupdev() to find an available network interface on which to sniff. The name of the interface is returned in the dev character array. If there is an error or if no interface is available, pcap_lookupdev() returns a NULL value and fills the errbuf with the relevant error information. Returning a failure value then populating an error buffer is common practice for libpcap functions that end in error.


 descr = pcap_open_live(dev, BUFSIZ, 0, -1, errbuf);
        if (descr == NULL) {
                cout << "pcap_open_live() failed: " << errbuf << endl;
                return 1;
        }

Next we create our packet capture descriptor using pcap_open_live(). This is how we will enable our target network interface for sniffing. It will return a valid descriptor on success, a NULL on failure. The parameters are as follows:

  • device – the name of the network interface to be used by libpcap for sniffing. We found it using pcap_lookupdev().
  • snaplen – the maximum snap length, or packet size, to be handled by this descriptor. We using the system defined BUFSIZ constant.
  • promisc – Determines whether or not the interface will listen in promiscuous mode. Promiscuous mode will analyze packets not specifically addressed for your machine, like if you were attached to a hub or a mirrored switch port. 1 turns it on, 0 turns it off.
  • to_ms – this is the packet read timeout in milliseconds. Specify -1 for no timeout.
  • errbuf – In case of an error, this is where a descriptive message will be left.

 if (pcap_loop(descr, 10, packetHandler, NULL) < 0) {
                cout << "pcap_loop() failed: " << pcap_geterr(descr);
                return 1;
        }
        cout << "capture finished" << endl;
 
        return 0;
}

And here’s the call we’ve been waiting for: pcap_loop(). This will take our pcap descriptor and start sniffing packets, sending them all to our packet handler callback function, aptly named packetHandler(). Here’s pcap_loop()’s parameters:

  • descr – the packet capture descriptor that we have initialized in the previous steps.
  • count – the number of packets to capture before pcap_loop() exits. Use -1 or 0 to use no limit.
  • callback – this is the callback function that is called every time pcap sniffs a packet. As specified above in the packetHandler() function, it receives relevant user data, pcap headers, and full packet data. It is the work horse of the packet analysis process. If creating a callback of your own, be sure to follow the function signature given in packetHandler().
  • userData – this is an array of unsigned bytes to be sent in with each packet. You can use it to hold any relevant user data you would like to send to the callback as its first paramater. You can also specify NULL if you wish to pass no user data to the callback.

The Summary

With this basic example you can now use C/C++ to capture network packets. But keeping a count of packets isn’t terribly interesting, is it? No, I don’t think so either. So what should I do next? If you managed to make it all the way to this summary, you can help me decide what aspects of packet capture or C/C++ to show case next. Comment on one of the following or ad your own idea and I’ll probably do it next!

  • Learn how to use Berkeley packet filters to focus and make more efficient your packet sniffing.
  • Process offline packet capture dumps from some of the other programs I mentioned earlier. It’s a critical skill for testing.
  • Use libpcap to learn more about the interfaces on your system?
  • Wrap this ugly C code in some much more developer friendly C++ objects.
  • Get right into the nitty gritty and start learning how to analyze packet data.

The choice is your’s guys.

3 Responses to “Packet capture with C++ & Linux”

  1. Speaker-to-Animals says:

    All of the above! But assign lowest priority to the C++ wrappers (I can figure that out on my own if I want it).

  2. Black of Hat says:

    I would like to see code that can process Wireshark capture dumps. Good stuff.

  3. Black, I was actually working on that one right now. Offline packet processing it is! In fact, I think the next installment will address all the points I mentioned, short of the actual packet analysis. That will definitely merit a post of its own.