Unwrapping a packet with Pcap and tcpdump (Part 1 – Ethernet Header)

If you are familiar with the OSI model, you are aware that network packets are wrapped in layers called headers.
Note: Don’t confuse these with C++ headers, these are network packet headers and I will try to always clarify by saying “packet headers” or “C++ headers”.
The goal of this article to demonstrate how to unwrap these packet headers using C++ and the Pcap and tcpdump libraries.

Prerequisites

  • You have read the previous Pcap posts.
  • Basic understanding of the OSI model
  • Basic knowledge of networking

Packet Headers

The first layer of a valid packet will contain an ethernet layer and it will be the first 14 octets. Each octet is a pair of hex numbers.  If you look at a standard TCP Syn packet in Wireshark, it will be displayed in Hex view as follows:

00 18 e7 dc e7 29 00 1f 3b 69 4f 29 08 00 45 00
00 34 0c 93 40 00 80 06 aa 86 c0 a8 00 0f 41 30
41 c3 39 22 00 50 c9 bc 46 2b 00 00 00 00 80 02
20 00 c2 11 00 00 02 04 05 b4 01 03 03 02 01 01
04 02

But you could display the octets in bits using the Bits View to see the binary representation of the packet:

00000000 00011000 11100111 11011100 11100111 00101001 00000000 00011111
00111011 01101001 01001111 00101001 00001000 00000000 01000101 00000000
00000000 00110100 00001100 10010011 01000000 00000000 10000000 00000110
10101010 10000110 11000000 10101000 00000000 00001111 01000001 00110000
01000001 11000011 00111001 00100010 00000000 01010000 11001001 10111100
01000110 00101011 00000000 00000000 00000000 00000000 10000000 00000010
00100000 00000000 11000010 00010001 00000000 00000000 00000010 00000100
00000101 10110100 00000001 00000011 00000011 00000010 00000001 00000001
00000100 00000010

I will use Hex unless demonstrating something that is easier to explain in binary.

Because this is a TCP Syn packet, there actually is no data or payload, just packet headers really, because it is just the first packet of a TCP handshake, but we can still demonstrate the idea of packet headers (and it is easier since the packet is smaller).

The first 14 octets comprises the first packet header and it has three items of data:

00 18 e7 dc e7 29 - (6 octets) Destination Mac Address
00 1f 3b 69 4f 29 - (6 octets) Source Mac Address<
08 00             - (2 octets) Ethertype

So our first job in C++ is to get this packet header data into a struct. Structs are already created in the tcpdump library if you want to use them, but if you want, you can create your own.

We will continue the project we started here: How to read a PCap file from Wireshark with C++

Step 1 – Download tcpdump

  1. Go to the tcpdump developer page:
    http://www.tcpdump.org/#latest-release
  2. Download the latest stable version of WinPcap.
  3. Extract to your solution directory.

Step 2 – Add tcpdump to Additional Include Directories

  1. In Visual Studio, right-click on your project and go to Properties.
  2. Go to Configuration Properties | C/C++ | General.
  3. Note: If you don’t see the C/C++ option, did you forget to do Step 2 above?
  4. For Additional Include Directories add the relative path to the tcpdump-4.2.0 directory you just extracted:
    ..\tcpdump-4.2.0

Step 3 – Add #include statements for packet headers and enums

The ether.h file that comes with tcpdump code has a small amount of code that include three #define and one struct (BSD license and comments omitted for brevity).

#define	ETHERMTU	1500
#define	ETHER_ADDR_LEN		6
struct	ether_header {
	u_int8_t	ether_dhost[ETHER_ADDR_LEN];
	u_int8_t	ether_shost[ETHER_ADDR_LEN];
	u_int16_t	ether_type;
};
#define ETHER_HDRLEN		14

However, there are other C++ headers that provide struct for packet headers as well as useful #define statements and enumerations. Here is a list of some of the tcpdump c++ headers you should become familiar with.

// These provide Packet Headers and Enums
#include <ether.h>
#include <ip.h>
#include <ipproto.h>
#include <tcp.h>
#include <udp.h>
#include <ethertype.h>

Add these to the main.cpp.

We are now ready to start unwrapping the packet headers with c++ code.

Step 4 – Create some helper functions

Let’s move the printing of the packet into its own function, this will get it out of our way.

Lets also create a function to only print parts of the packet.

Also, add a function to convert ethertype values to strings.

First declare them…

// Declare helper functions
void PrintPacket(pcap_pkthdr * header, const u_char *data);
void PrintData(u_int startOctet, u_int endOctet, const u_char *data);
char * get_ethertype(const u_int16_t ethertype);

Then define them….

void PrintPacket(pcap_pkthdr * header, const u_char *data)
{
	// Print using printf. See printf reference:
	// http://www.cplusplus.com/reference/clibrary/cstdio/printf/

	// Show the packet number
	printf("Packet # %i\n", ++packetCount);

	// Show the size in bytes of the packet
	printf("Packet size: %ld bytes\n", header->len);

	// Show a warning if the length captured is different
	if (header->len != header->caplen)
		printf("Warning! Capture size different than packet size: %ld bytes\n", header->len);

	// Show Epoch Time
	printf("Epoch Time: %ld:%ld seconds\n", header->ts.tv_sec, header->ts.tv_usec);

	// loop through the packet and print it as hexidecimal representations of octets
	// We also have a function that does this similarly below: PrintData()
	for (u_int i=0; (i < header->caplen ) ; i++)
	{
		// Start printing on the next after every 16 octets
		if ( (i % 16) == 0) printf("\n");

		// Print each octet as hex (x), make sure there is always two characters (.2).
		printf("%.2x ", data[i]);
	}
	printf("\n\n");
}

void PrintData(u_int startOctet, u_int endOctet, const u_char *data)
{
	for (u_int i = startOctet; i <= endOctet; i++)
	{
		// Print each octet as hex (x), make sure there is always two characters (.2).
		printf("%.2x ", data[i]);
	}
	printf("\n");
}

// Returns a string representation of the common Ethertype
char * get_ethertype(const u_int16_t ethertype)
{
	switch(ethertype)
	{
	case ETHERTYPE_IP:
		return "IPv4";
	case ETHERTYPE_IPV6:
		return "IPv6";
	case ETHERTYPE_ARP:
		return "ARP";
	default:
		return "Unknown";
	}
}

Step 5 – Unwrapping the first packet header with the ether_header struct

Once we have printed the packet out, we are now going to print details of the packet.

  1. Create a pointer to an ether_header struct.
  2. Assign it the same memory address as the data pointer.
const struct ether_header *ethernet;
ethernet = (struct ether_header*)(data);

Now print out the three items in the struct: Destination Mac, Source Mac, and Ethertype.

// Print Destination Mac Address
printf("Dst MAC: ");
PrintData(0,5,ethernet->ether_dhost);

// Print Source Mac Address
printf("Src MAC: ", ethernet->ether_shost);
PrintData(0,5,ethernet->ether_shost);

// Print EtherType
printf("Ethertype: %s (%#.4x)\n", get_ethertype(ethernet->ether_type), ethernet->ether_type);

Everything should print fine except the ethertype. It is 0x0080 instead of 0x8000. In the next step we will fix this.

Step 6 – Fix the bit order of the ether_type

We are going to use the ntohs() function to fix the bits. This is defined on windows in the Ws2_32.lib library, which is not included, so we must include it.

  1. In Visual Studio, right-click on your project and go to Properties.
  2. Go to Configuration Properties | Linker | Input.
  3. For Additional Dependencies, add the following:
    Ws2_32.lib;

Now we can go ahead and use the ntohs() function in our code.

// Print EtherType
u_short ethertype = ntohs(ethernet->ether_type);
printf("Ethertype: %s (%#.4x)\n", get_ethertype(ethertype), ethertype);

Lets end here for now. We have successfully unwrapped the first packet header.

Here is the full main.cpp file.

/*
	Copyright 2011 Rhyous. All rights reserved.

	Redistribution and use in source and binary forms, with or without modification, are
	permitted provided that the following conditions are met:

	   1. Redistributions of source code must retain the above copyright notice, this list of
		  conditions and the following disclaimer.

	   2. Redistributions in binary form must reproduce the above copyright notice, this list
		  of conditions and the following disclaimer in the documentation and/or other materials
		  provided with the distribution.

	THIS SOFTWARE IS PROVIDED BY Rhyous ''AS IS'' AND ANY EXPRESS OR IMPLIED
	WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
	FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rhyous OR
	CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
	SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
	ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
	ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

	The views and conclusions contained in the software and documentation are those of the
	authors and should not be interpreted as representing official policies, either expressed
	or implied, of Rhyous.
/*

/*
* How to read a packet capture file.
*/

/*
* Step 1 - Add includes and namespace
*/
#include <string>
#include <iostream>
#include <pcap.h>

// These provide headers and enums
#include <ether.h>
#include <ip.h>
#include <ipproto.h>
#include <tcp.h>
#include <udp.h>
#include <ethertype.h>

using namespace std;

// Declare helper functions
void PrintPacket(pcap_pkthdr * header, const u_char *data);
void PrintData(u_int startOctet, u_int endOctet, const u_char *data);
u_short get_ethertype_value(const u_char *data);
char * get_ethertype(const u_int16_t ethertype);

// static values
static u_int packetCount = 0;

// Defines
#define SIZE_ETHERNET 14

int main(int argc, char *argv[])
{
	/*
	* Step 2 - Get a file name
	*/

	string file = "C:\\users\\jared\\testfiles\\smallcapture.pcap";

	/*
	* Step 3 - Create an char array to hold the error.
	*/

	// Note: errbuf in pcap_open functions is assumed to be able to hold at least PCAP_ERRBUF_SIZE chars
	//       PCAP_ERRBUF_SIZE is defined as 256.
	// http://www.winpcap.org/docs/docs_40_2/html/group__wpcap__def.html
	char errbuff[PCAP_ERRBUF_SIZE];

	/*
	* Step 4 - Open the file and store result in pointer to pcap_t
	*/

	// Use pcap_open_offline
	// http://www.winpcap.org/docs/docs_41b5/html/group__wpcapfunc.html#g91078168a13de8848df2b7b83d1f5b69
	pcap_t * pcap = pcap_open_offline(file.c_str(), errbuff);

	/*
	* Step 5 - Create a header and a data object
	*/

	// Create a header object:
	// http://www.winpcap.org/docs/docs_40_2/html/structpcap__pkthdr.html
	struct pcap_pkthdr *header;

	// Create a character array using a u_char
	// u_char is defined here:
	// C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include\WinSock2.h
	// typedef unsigned char   u_char;
	const u_char *data;

	/*
	* Step 6 - Loop through packets and print them to screen
	*/
	while (int returnValue = pcap_next_ex(pcap, &header, &data) >= 0)
	{
		// I moved the code for printing the packet as hex values
		// to a function
		PrintPacket(header, data);

		// Print the packet in details
		printf("Packet Details\n");

		// Unwrap ether_header (first 14 bytes)
		const struct ether_header *ethernet;
		ethernet = (struct ether_header*)(data);

		// Print Destination Mac Address
		printf("Dst MAC: ");
		PrintData(0,5,ethernet->ether_dhost);

		// Print Source Mac Address
		printf("Src MAC: ", ethernet->ether_shost);
		PrintData(0,5,ethernet->ether_shost);

		// Print EtherType
		u_short ethertype = ntohs(ethernet->ether_type);
		printf("Ethertype: %s (%#.4x)\n", get_ethertype(ethertype), ethertype);

		// Add two lines between packets
		printf("\n\n");
	}
}

void PrintPacket(pcap_pkthdr * header, const u_char *data)
{
	// Print using printf. See printf reference:
	// http://www.cplusplus.com/reference/clibrary/cstdio/printf/

	// Show the packet number
	printf("Packet # %i\n", ++packetCount);

	// Show the size in bytes of the packet
	printf("Packet size: %ld bytes\n", header->len);

	// Show a warning if the length captured is different
	if (header->len != header->caplen)
		printf("Warning! Capture size different than packet size: %ld bytes\n", header->len);

	// Show Epoch Time
	printf("Epoch Time: %ld:%ld seconds\n", header->ts.tv_sec, header->ts.tv_usec);

	// loop through the packet and print it as hexidecimal representations of octets
	// We also have a function that does this similarly below: PrintData()
	for (u_int i=0; (i < header->caplen ) ; i++)
	{
		// Start printing on the next after every 16 octets
		if ( (i % 16) == 0) printf("\n");

		// Print each octet as hex (x), make sure there is always two characters (.2).
		printf("%.2x ", data[i]);
	}
	printf("\n\n");
}

void PrintData(u_int startOctet, u_int endOctet, const u_char *data)
{
	for (u_int i = startOctet; i <= endOctet; i++)
 	{
		// Print each octet as hex (x), make sure there is always two characters (.2).
		printf("%.2x ", data[i]);
	}
	printf("\n");
}

u_short get_ethertype_value(const u_char *data)
{
	u_short ethertype = data[12] << 8;
	ethertype += data[13];
	return ethertype;
}

// Returns a string representation of the common Ethertype
char * get_ethertype(const u_int16_t ethertype)
{
	switch(ethertype)
	{
	case ETHERTYPE_IP:
		return "IPv4";
	case ETHERTYPE_IPV6:
		return "IPv6";
	case ETHERTYPE_ARP:
		return "ARP";
	default:
		return "Unknown";
	}
}

Stay tuned for Unwrapping a packet with Pcap and tcpdump (Part 2 – IP Header)

9 Comments

  1. puffy says:

    Thank you for the article - it helped me a lot 🙂 Have already been published the second part about Ip header and where to find it?

  2. waseem says:

    Thank you so much for this
    Would love to see a Part 2 – IP Header soon ...really need it

  3. coder says:

    Hello
    this was a fantastic post.
    thanks for posting.
    Anyways what about the Ip Header unwrapping?when is the post going to come?
    Thanks

  4. Paul says:

    Would love to see a part 2! This has helped a lot today.

  5. Suzie says:

    I have been searching for days to find something like this.
    It is a great piece of code, and appreciated you publishing it.

    I'd be very interested to a parse/print function for the application data.

    Thank you very much for this.

  6. ctrld says:

    You have few mistakes in the code:
    1. for (u_int i = startOctet; i {
    2. Empty includes
    3. html in code section (Step 5 - Unwrapping the first packet header with the ether_header struct)

  7. ctrld says:

    Thank you for article - it saves my day 🙂

Leave a Reply

How to post code in comments?