Troliver

stories of war between boy and machine

Capturing LLDP and CDP packets using C++ and WinPcap

I’ll categorise and change the order of posts later perhaps, but here is the first in a series on how I made an application to record the switchport and vlan, of a switch, that a computer is connected to. This setup will work where:

  • You have a Windows PC with Winsock installed (Windows XP or later will do).
  • You have a network card on your PC.
  • You have the WinPcap runtime installed.
  • You are connected to a switch, or router, that sends out LLDP or CDP packets – I would imagine most corporate networks with Cisco switches even at least 10 years old will be sending these packets out.

The easiest way to get up and running with using WinPcap is to download the library from the WinPCap site and follow this tutorial, which gives you a good starting point for making an application using the WinPcap. (Also, it is worth noting that I’ve been using Visual Studio 2015 for this – not sure how non-Microsoft compilers would handle things but there shouldn’t be anything specific here that would cause any issues.)

This first post deals with the specifics of getting WinPcap working in a way that we want it to. An important thing to note is that this codes provides absolutely no error checking – I stripped it all out to make the code more easy to follow, but you really should try and capture errors! Read on for the breakdown, or skip to the end to grab the whole code.

Headers, libraries and variables

So first of all, here are the libraries, headers etc to use. Also a few global variables.

#define HAVE_REMOTE
#include "pcap.h"
#pragma comment (lib, "wpcap.lib")
#pragma comment (lib, "Packet.lib")
#pragma comment (lib, "Ws2_32.lib")

//Used to store all the adapters in a list (by storing references to next adapters)
pcap_if_t * allAdapters; 

//Used to store a single adapters from the above list.
pcap_if_t * adapter; 

//Used for referencing a capture session
pcap_t * captureInstance;

//You'll need these later to store a reference to a packet and its header 
struct pcap_pkthdr * packetHeader;
const u_char * packetData;

//This stores errors. We won't use it much in this example.
char errorBuffer[PCAP_ERRBUF_SIZE];

 

Selecting the capture interface

First, within the main function, you need to select the right adapter (in other words, the network card. A system can have lots of these. Some of ours definitely do) to initiate a capture on. First we need to store all devices and then iterate through them to find how many adapters there actually are. pcap_findalldevs_ex will return the list to be stored in allAdapters.

 int totalAdapters = 0;

 for (adapter = allAdapters; adapter; adapter = adapter->next)
 {
    ++totalAdapters;   
    printf("\n%d %s) ", totalAdapters, adapter->name);
    printf("-- %s\n", adapter->description);
 }
 //Just add a blank line. Makes it look pretty.
 printf("\n");

 

The next step identifies the correct adapter number by incrementing selectedAdapterNumber until a separate function, testTargetNetwork, returns true

 int selectedAdapterNumber = 0;

 for (adapter = allAdapters; adapter; adapter = adapter->next)
 {
    ++selectedAdapterNumber;
    if (testTargetNetwork(adapter))
    {
        printf("\nComputer is currently attached to the network through adapter %d", selectedAdapterNumber);
        printf("\n");
    }
 }

 

The function that this code uses, testTargetNetwork – below, stores the IP addresses of a single adapter. We are only testing for the sa_family being AF_INET, so no MAC or IPv6 addresses get stored. Then I decided to make a bit of a dodgy way to convert the IP address – stored as characters – to a decimal value, by storing each part as “hundreds”, “tens” and “units”, multiplying them by their position, and then adding them together to determine if the detected IP address is on the correct network we want (this last test returns true or false).

bool testTargetNetwork(pcap_if_t * adapter)
{
 char ipAddress[INET6_ADDRSTRLEN];
 pcap_addr_t * adapterAddress;

 for (adapterAddress = adapter->addresses; adapterAddress; adapterAddress = adapterAddress->next)
 {
    if (adapterAddress->addr->sa_family == AF_INET)
    { 
       inet_ntop(adapterAddress->addr->sa_family,
       &(((struct sockaddr_in*)adapterAddress->addr)->sin_addr),
       ipAddress,
       sizeof(ipAddress));
    }
 }

 int hundreds;
 int tens;
 int units;

 if (ipAddress[2] == '.')
 {
    hundreds = (ipAddress[0] - '0') * 10;
    tens = (ipAddress[1] - '0') * 1;
    units = 0;
 }
 else
 {
    hundreds = (ipAddress[0] - '0') * 100;
    tens = (ipAddress[1] - '0') * 10;
    units = (ipAddress[2] - '0') * 1;
 }

 int temp4 = hundreds + tens + units;
 int network = 10;
 return (temp4 == network);
}

 

If this returns true, then we know the number of the adapter we want. To select that adapter, again, you can perform allAdapters->next for that many times.

Preparing the filter

The next step is go back to the main function to create a filter that will be used to capture only specific packets on the adapter specified. WinPCap does all this magic for you, you just have to specify the filter as a string of text, stored in packet_filter.

captureInstance = pcap_open(adapter->name, 65535, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errorBuffer);

char packet_filter[] = "ether[12:2] = 0x88cc or ether[20:2] = 0x2000";

struct bpf_program fcode;
pcap_compile(captureInstance, &fcode, packet_filter, 1, ((struct sockaddr_in *)(adapter->addresses->netmask))->sin_addr.S_un.S_addr);
pcap_setfilter(captureInstance, &fcode);

 

The two ethernet frames we are looking to capture are CDP and LLDP. CDP data is stored in an Ethernet frame, in an LLC wrapper with SNAP extensions. LLDP data is stored in an Ethernet II frame. Because CDP doesn’t use a (now standard) Ethernet II frame, we can’t specifically filter by saying “if the protocol is LLDP..” – because the place where the type is expected to be determined is actually going to be the length of the packet, rather than the type. So we have to tell it where to look for the type and then work out for ourselves what that that type should be specified as.

proto

The string says that either bytes 12 and 13 of a packet should be 0x88cc (which is the type for LLDP) or bytes 20 and 21 should be 0x2000 (which is the protocol ID for CDP). The filter will then get compiled into binary and passed to the packet capture driver at runtime so that only the packets we want get passed to the application.

Initiating the capture

Now we need to actually capture a packet; the right adapter has been selected, the filter has been set up and the device opened – all we need to do now is to retrieve a packet. You can make a new function and call it from main() after setting up the filter which, to begin with, will free the device list created earlier, since we don’t need it anymore.

void capture()
{
        pcap_freealldevs(allAdapters);

Then add in code to start the loop off. What happens, is that the function pcap_next_ex will attempt to retrieve the next packet available on the specified capture instance.

When it receives a packet, it will store a pointer to the header of the packet, a pointer to the start of the packet’s data and return a value of “1” as its code, which we can use to mean “a packet was found”.

If no packet is found, however, the program should time out – which returns a 0 instead (well, it will actually return -1 as well, if there’s an error, which will break out of the while loop altogether). Back above, when calling pcap_open, a value of 1000 (ms) was specified for the timeout parameter, which means that – if no packets have been captured by the system in that time – a value of 0 is returned. Note that this timeout is more like a “countdown” – nothing will happen within that time, but several packets may have accumulated in that period, before being retrieved by the application.

We can then handle it appropriately by exiting the loop if we want, with break; – although, in this instance, continue; is used instead, which continues the loop but steps over the rest of the code in the current cycle. It would probably be better to just increase the timeout to 1 minute, but even then, it could be a long time before CDP and LLDP packets are received (by default, they are sent out every 1 minute and 30 seconds, respectively, on Cisco devices).

	printf("\nCapture session started (%s)\n", adapter->name);


	int packetFound;
	while ((packetFound = pcap_next_ex(captureInstance, &packetHeader, &packetData)) >= 0)
	{
		if (packetFound == 0)
		{
			printf("\nPacket timed out! Trying again..");
			continue;
		}


		printf("\n%d:%d:%d length of packet: %d\n", (packetHeader->ts.tv_sec % 86400) / 3600, (packetHeader->ts.tv_sec % 3600) / 60, packetHeader->ts.tv_sec % 60, packetHeader->len);
						
		printf("\n");

		for (int x = 0; x < packetHeader->len; x++)
		{
			if ((packetData[x] == 0x0e) || (packetData[x] == '\0'))
			{
				printf("\n");
			}
			else
			{
				printf("%c", packetData[x]);
			}
		}
	}
}

When a packet is received, the date and length of the packet is printed out. I’m not sure if there’s an easier way to date and time from seconds, but the above way works fine. Anyway, all the program then does is print out each byte of data, formatted to be read as a character. If the byte’s value is either “14” or “\0”, then it is going to be a new line, otherwise, it will simply list the entire contents of the packet.

Summary (full code)

Here is the final code, put together, with a pause so you can see what happens. What you should get out is a load of gibberish characters and some human-readable ones. Next post will look at how to properly handle the packets and get something far more readable on screen; until then, if anyone has any questions about the code or using WinPcap libraries (although I’m hardly an expert!), please feel free to ask in the comments!

#include "stdafx.h"

#define HAVE_REMOTE

//WinPcap libraries. This can be done in the project C++/Linker properties
#include "pcap.h"
#pragma comment (lib, "wpcap.lib")
#pragma comment (lib, "Packet.lib")
#pragma comment (lib, "Ws2_32.lib")



int setAdapter();
void capture();
bool testTargetNetwork(pcap_if_t * adapter);




pcap_if_t			*	allAdapters;		//Used to store all the adapters in a list (by storing references to next adapters)
pcap_if_t			*	adapter;			//Used to store one of the adapters in a list.
pcap_t				*	captureInstance;	//Used to store a capture
struct pcap_pkthdr	*	packetHeader;		//Used to store the header for a packet
const u_char		*	packetData;			//Used to store the packet's data

char errorBuffer[PCAP_ERRBUF_SIZE];

int _tmain(int argc, _TCHAR* argv[])
{
	pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &allAdapters, errorBuffer);


	int totalAdapters = 0;

	for (adapter = allAdapters; adapter; adapter = adapter->next)
	{
		printf("\n%d %s) ", ++totalAdapters, adapter->name);
		printf("-- %s\n", adapter->description);
	}
	//Just add a blank line. Makes it look pretty.
	printf("\n");

	
	int selectedAdapterNumber = 0;

	for (adapter = allAdapters; adapter; adapter = adapter->next)
	{
		++selectedAdapterNumber;
		if (testTargetNetwork(adapter))
		{
			printf("\nComputer is currently attached to the network through adapter %d", selectedAdapterNumber);
			printf("\n");
		}
	}
	
	adapter = allAdapters;
	for (int i = 0; i < selectedAdapterNumber - 1; i++)
	{
		adapter = adapter->next;
	}
	


	captureInstance = pcap_open(adapter->name, 65535, PCAP_OPENFLAG_PROMISCUOUS, 65000, NULL, errorBuffer);

	char packet_filter[] = "ether[12:2] = 0x88cc or ether[20:2] = 0x2000";

	struct bpf_program fcode;
	pcap_compile(captureInstance, &fcode, packet_filter, 1, ((struct sockaddr_in *)(adapter->addresses->netmask))->sin_addr.S_un.S_addr);
	pcap_setfilter(captureInstance, &fcode);




	capture(); 


	system("PAUSE");
	return 0;
}

//This is what we use to compare the interfaces against our network identity.
bool testTargetNetwork(pcap_if_t * adapter)
{
	char ipAddress[INET6_ADDRSTRLEN];
	pcap_addr_t * adapterAddress;

	for (adapterAddress = adapter->addresses; adapterAddress; adapterAddress = adapterAddress->next)
	{
		if (adapterAddress->addr->sa_family == AF_INET)
		{
			inet_ntop(adapterAddress->addr->sa_family,
				&(((struct sockaddr_in*)adapterAddress->addr)->sin_addr),
				ipAddress,
				sizeof(ipAddress));
		}
	}

	int hundreds;
	int tens;
	int units;

	if (ipAddress[2] == '.')
	{
		hundreds = (ipAddress[0] - '0') * 10;
		tens = (ipAddress[1] - '0') * 1;
		units = 0;
	}
	else
	{
		hundreds = (ipAddress[0] - '0') * 100;
		tens = (ipAddress[1] - '0') * 10;
		units = (ipAddress[2] - '0') * 1;
	}

	int temp4 = hundreds + tens + units;
	int network = 10;
	return (temp4 == network);
}

//Capture code
void capture()
{
	pcap_freealldevs(allAdapters);


	printf("\nCapture session started (%s)\n", adapter->name);


	int packetFound;
	while ((packetFound = pcap_next_ex(captureInstance, &packetHeader, &packetData)) >= 0)
	{
		if (packetFound == 0)
		{
			printf("\nPacket timed out! Trying again..");
			continue;
		}


		printf("\n%d:%d:%d length of packet: %d\n", (packetHeader->ts.tv_sec % 86400) / 3600, (packetHeader->ts.tv_sec % 3600) / 60, packetHeader->ts.tv_sec % 60, packetHeader->len);

		printf("\n");

		for (int x = 0; x < packetHeader->len; x++)
		{
			if ((packetData[x] == 0x0e) || (packetData[x] == '\0'))
			{
				printf("\n");
			}
			else
			{
				printf("%c", packetData[x]);
			}
		}
                //Stop iterating and exit
                break;
	}
}

 

 

,

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.