Npcap Development Tutorial
A step-by-step guide to writing software that uses Npcap to list network adapters, capture packets, and send network traffic.
This section shows how to use the features of the Npcap API. It is organized as a tutorial, subdivided into a set of lessons that will introduce the reader, in a step-by-step fashion, to program development using Npcap, from the basic functions (obtaining the adapter list, starting a capture, etc.) to the most advanced ones (handling send queues and gathering statistics about network traffic).
The samples are written in plain C, so a basic knowledge of C programming is required. Also, since this is a tutorial about a library dealing with "raw" networking packets, good knowledge of networks and network protocols is assumed.
The code in this section is copied from the the section called “Examples” in the source distribution and the SDK. The code is released under a BSD-3-clause license and copyright: NetGroup, Politecnico di Torino (Italy); CACE Technologies, Davis (California); and Insecure.com, LLC. Full text of the code license can be found in each source file.
Obtaining the device list
Typically, the first thing that a Npcap-based application does is
get a list of attached network adapters. Both libpcap and Npcap provide
the pcap_findalldevs_ex() function for this purpose:
this function returns a linked list of pcap_if
structures, each of which contains
comprehensive information about an attached adapter. In particular, the
fields name
and description
contain the name and a
human readable description, respectively, of the corresponding
device.
The following code retrieves the adapter list and shows it on the screen, printing an error if no adapters are found.
#include "pcap.h" main() { pcap_if_t *alldevs; pcap_if_t *d; int i=0; char errbuf[PCAP_ERRBUF_SIZE]; /* Retrieve the device list from the local machine */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL /* auth is not needed */, &alldevs, errbuf) == -1) { fprintf(stderr, "Error in pcap_findalldevs_ex: %s\n", errbuf); exit(1); } /* Print the list */ for(d= alldevs; d != NULL; d= d->next) { printf("%d. %s", ++i, d->name); if (d->description) printf(" (%s)\n", d->description); else printf(" (No description available)\n"); } if (i == 0) { printf("\nNo interfaces found! Make sure Npcap is installed.\n"); return; } /* We don't need any more the device list. Free it */ pcap_freealldevs(alldevs); }
Some comments about this code.
First of all, pcap_findalldevs_ex(), like
other libpcap functions, has an errbuf
parameter. This
parameter points to a string filled by libpcap with a description of the
error if something goes wrong.
Second, remember that not all the OSes supported by libpcap provide a
description of the network interfaces, therefore if we want to write a
portable application, we must consider the case in which
description
is null: we print the string "No
description available" in that situation.
Note finally that we free the list with pcap_freealldevs() once when we have finished with it.
Assuming we have compiled the program, let's try to run it. On a particular Windows workstation, the result we optained is
1. \Device\NPF_{4E273621-5161-46C8-895A-48D0E52A0B83} (Realtek RTL8029(AS) Ethernet Adapter) 2. \Device\NPF_{5D24AE04-C486-4A96-83FB-8B5EC6C7F430} (3Com EtherLink PCI)
As you can see, the name of the network adapters (that will be passed to libpcap when opening the devices) under Windows are quite unreadable, so the parenthetical descriptions can be very helpful.
Obtaining advanced information about installed devices
Lesson 1 (the section called “Obtaining the device list”) demonstrated how
to get basic information (i.e. device name and description) about
available adapters. Actually, Npcap provides also other advanced
information. In particular, every pcap_if
structure
returned by pcap_findalldevs_ex()
contains also a list of pcap_addr
structures,
with:
- a list of addresses for that interface.
- a list of netmasks (each of which corresponds to an entry in the addresses list).
- a list of broadcast addresses (each of which corresponds to an entry in the addresses list).
- a list of destination addresses (each of which corresponds to an entry in the addresses list).
Additionally, pcap_findalldevs_ex()
can also
return remote adapters and a list of pcap files that are located in a
given local folder.
The following sample provides an ifprint() function that prints the
complete contents of a pcap_if
structure. It is
invoked by the program for every entry returned by
pcap_findalldevs_ex()
.
/* Print all the available information on the given interface */ void ifprint(pcap_if_t *d) { pcap_addr_t *a; char ip6str[128]; /* Name */ printf("%s\n",d->name); /* Description */ if (d->description) printf("\tDescription: %s\n",d->description); /* Loopback Address*/ printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no"); /* IP addresses */ for(a=d->addresses;a;a=a->next) { printf("\tAddress Family: #%d\n",a->addr->sa_family); switch(a->addr->sa_family) { case AF_INET: printf("\tAddress Family Name: AF_INET\n"); if (a->addr) printf("\tAddress: %s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr)); if (a->netmask) printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr)); if (a->broadaddr) printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr)); if (a->dstaddr) printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr)); break; case AF_INET6: printf("\tAddress Family Name: AF_INET6\n"); if (a->addr) printf("\tAddress: %s\n", ip6tos(a->addr, ip6str, sizeof(ip6str))); break; default: printf("\tAddress Family Name: Unknown\n"); break; } } printf("\n"); }
Opening an adapter and capturing the packets
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 lesson we'll write a program that prints some information about each packet flowing through the adapter.
The function that opens a capture device is pcap_open(). The parameters,
snaplen
, flags
and
to_ms
deserve some explanation.
snaplen
specifies the portion of the packet to capture. On
some OSes (like xBSD and Win32), the packet driver can be configured to
capture only the initial part of any packet: this decreases the amount of
data to copy to the application and therefore improves the efficiency of
the capture. In this case we use the value 65536 which is higher than the
greatest MTU that we could encounter. In this manner we ensure that the
application will always receive the whole packet.
flags:
the most important flag is the one that
indicates if the adapter will be put in promiscuous mode. In normal
operation, an adapter only captures packets from the network that are
destined to it; the packets exchanged by other hosts are therefore
ignored. Instead, when the adapter is in promiscuous mode it captures all
packets whether they are destined to it or not. This means that on shared
media (like non-switched Ethernet), Npcap 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.
to_ms
specifies the read timeout, in milliseconds.
A read on the adapter (for example, with pcap_dispatch() or pcap_next_ex()) will always
return after to_ms
milliseconds, even if no packets
are available from the network. to_ms
also defines the
interval between statistical reports if the adapter is in statistical
mode (see the lesson "\ref wpcap_tut9" for information about statistical
mode). Setting to_ms
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.
#include <pcap.h> #include "misc.h" /* LoadNpcapDlls */ /* prototype of the packet handler */ void packet_handler( u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data); int main() { pcap_if_t *alldevs; pcap_if_t *d; int inum; int i=0; pcap_t *adhandle; char errbuf[PCAP_ERRBUF_SIZE]; /* Load Npcap and its functions. */ if (!LoadNpcapDlls()) { fprintf(stderr, "Couldn't load Npcap\n"); exit(1); } /* Retrieve the device list on the local machine */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) { fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf); exit(1); } /* Print the list */ for(d=alldevs; d; d=d->next) { printf("%d. %s", ++i, d->name); if (d->description) printf(" (%s)\n", d->description); else printf(" (No description available)\n"); } if(i==0) { printf("\nNo interfaces found! Make sure Npcap is installed.\n"); return -1; } printf("Enter the interface number (1-%d):",i); scanf_s("%d", &inum); if(inum < 1 || inum > i) { printf("\nInterface number out of range.\n"); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } /* Jump to the selected adapter */ for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++); /* Open the device */ if ( (adhandle= pcap_open(d->name, // name of the device 65536, // portion of the packet to capture // 65536 guarantees that the whole packet will // be captured on all the link layers PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode 1000, // read timeout NULL, // authentication on the remote machine errbuf // error buffer ) ) == NULL) { fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Npcap\n", d->name); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } printf("\nlistening on %s...\n", d->description); /* At this point, we don't need any more the device list. Free it */ pcap_freealldevs(alldevs); /* start the capture */ pcap_loop(adhandle, 0, packet_handler, NULL); return 0; } /* Callback function invoked by libpcap for every incoming packet */ void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) { struct tm ltime; char timestr[16]; time_t local_tv_sec; /* * unused variables */ (VOID)(param); (VOID)(pkt_data); /* convert the timestamp to readable format */ local_tv_sec = header->ts.tv_sec; localtime_s(<ime, &local_tv_sec); strftime( timestr, sizeof timestr, "%H:%M:%S", <ime); printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len); }
Once the adapter is opened, the capture can be started with pcap_dispatch() or pcap_loop(). These two functions are
very similar, the difference is that pcap_dispatch()
returns (although not guaranteed) when the timeout expires while
pcap_loop()
doesn't return until
cnt
packets have been captured, so it can block for an
arbitrary period on an under-utilized network.
pcap_loop()
is enough for the purpose of this sample,
while pcap_dispatch()
is normally used in a more
complex program.
Both of these functions have a callback
parameter,
packet_handler
, pointing to a function that will
receive the packets. This function is invoked by libpcap for every new
packet coming from the network and receives a generic status
(corresponding to the user
parameter of pcap_loop() and pcap_dispatch()), a header with some
information on the packet like the timestamp and the length and the
actual data of the packet including all the protocol headers. Note that
the frame CRC is normally not present, because it is removed by the
network adapter after frame validation. Note also that most adapters
discard packets with wrong CRCs, therefore Npcap is normally not able
to capture them.
The above example extracts the timestamp and the length of every
packet from the pcap_pkthdr
header and prints them on
the screen.
Please note that there may be a drawback using pcap_loop() mainly related to the fact that the handler is called by the packet capture driver; therefore the user application does not have direct control over it. Another approach (and to have more readable programs) is to use the pcap_next_ex() function, which is presented in the next example (the section called “Capturing the packets without the callback”).
Capturing the packets without the callback
The example program in this lesson behaves exactly like the previous program (the section called “Opening an adapter and capturing the packets”), but it uses pcap_next_ex() instead of pcap_loop().
The callback-based capture mechanism of pcap_loop() is elegant and it could be a good choice in some situations. However, handling a callback is sometimes not practical—it often makes the program more complex especially in situations with multithreaded applications or C++ classes.
In these cases, pcap_next_ex() retrievs a packet
with a direct call—using pcap_next_ex()
,
packets are received only when the programmer wants them.
The parameters of this function are the same as a capture callback.
It takes an adapter descriptor and a couple of pointers that will be
initialized and returned to the user (one to a
pcap_pkthdr
structure and another to a buffer with the
packet data).
In the following program, we recycle the callback code of the previous lesson's example and move it inside main() right after the call to pcap_next_ex().
/* Open the device */ if ( (adhandle= pcap_open(d->name, // name of the device 65536, // portion of the packet to capture. // 65536 guarantees that the whole packet will // be captured on all the link layers PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode 1000, // read timeout NULL, // authentication on the remote machine errbuf // error buffer ) ) == NULL) { fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Npcap\n", d->name); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } printf("\nlistening on %s...\n", d->description); /* At this point, we don't need any more the device list. Free it */ pcap_freealldevs(alldevs); /* Retrieve the packets */ while((res = pcap_next_ex( adhandle, &header, &pkt_data)) >= 0){ if(res == 0) /* Timeout elapsed */ continue; /* convert the timestamp to readable format */ local_tv_sec = header->ts.tv_sec; localtime_s(<ime, &local_tv_sec); strftime( timestr, sizeof timestr, "%H:%M:%S", <ime); printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len); } if(res == -1){ printf("Error reading the packets: %s\n", pcap_geterr(adhandle)); return -1; }
Why do we use pcap_next_ex() instead of the old
pcap_next()? Because
pcap_next()
has some drawbacks. First of all, it is
inefficient because it hides the callback method but still relies on
pcap_dispatch()
. Second, it is not able to detect EOF,
so it's not very useful when gathering packets from a file.
Notice also that pcap_next_ex()
returns different
values for success, timeout elapsed, error and EOF conditions.
Filtering the traffic
One of the most powerful features offered by Npcap (and by libpcap as well) is the filtering engine. It provides a very efficient way to receive subsets of the network traffic, and is (usually) integrated with the capture mechanism provided by Npcap. The functions used to filter packets are pcap_compile() and pcap_setfilter().
pcap_compile() takes a string containing a high-level Boolean (filter) expression and produces a low-level byte code that can be interpreted by the fileter engine in the packet driver. The syntax of the boolean expression can be found in the Filtering expression syntax section of this documentation.
pcap_setfilter()
associates a filter with a capture session in the kernel driver. Once
pcap_setfilter()
is called, 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
we must retrieve the netmask from the pcap_if
structure that describes the adapter, because some filters created by
pcap_compile()
require it.
The filter passed to pcap_compile()
in this code
snippet is "ip and tcp", which means to "keep only the packets that are
both IPv4 and TCP and deliver them to the application".
if (d->addresses != NULL) /* Retrieve the mask of the first address of the interface */ netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; else /* If the interface is without an address * we suppose to be in a C class network */ netmask=0xffffff; //compile the filter if (pcap_compile(adhandle, &fcode, "ip and tcp", 1, netmask) < 0) { fprintf(stderr, "\nUnable to compile the packet filter. Check the syntax.\n"); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } //set the filter if (pcap_setfilter(adhandle, &fcode) < 0) { fprintf(stderr,"\nError setting the filter.\n"); /* Free the device list */ pcap_freealldevs(alldevs); return -1; }
If you want to see some code that uses the filtering functions shown in this lesson, look at the example presented in the next Lesson, the section called “Interpreting the packets”.
Interpreting the packets
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 code from the previous lessons 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 UDPdump, prints a summary of the UDP traffic on our network.
We have chosen to parse and display the UDP protocol because it is more accessible than other protocols such as TCP and consequently is an excellent initial example. Let's look at the code:
#include <pcap.h> #include <Winsock2.h> #include <tchar.h> BOOL LoadNpcapDlls() { _TCHAR npcap_dir[512]; UINT len; len = GetSystemDirectory(npcap_dir, 480); if (!len) { fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError()); return FALSE; } _tcscat_s(npcap_dir, 512, _T("\\Npcap")); if (SetDllDirectory(npcap_dir) == 0) { fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError()); return FALSE; } return TRUE; } /* 4 bytes IP address */ typedef struct ip_address{ u_char byte1; u_char byte2; u_char byte3; u_char byte4; }ip_address; /* IPv4 header */ typedef struct ip_header{ u_char ver_ihl; // Version (4 bits) + IP header length (4 bits) u_char tos; // Type of service u_short tlen; // Total length u_short identification; // Identification u_short flags_fo; // Flags (3 bits) + Fragment offset (13 bits) u_char ttl; // Time to live u_char proto; // Protocol u_short crc; // Header checksum ip_address saddr; // Source address ip_address daddr; // Destination address u_int op_pad; // Option + Padding }ip_header; /* UDP header*/ typedef struct udp_header{ u_short sport; // Source port u_short dport; // Destination port u_short len; // Datagram length u_short crc; // Checksum }udp_header; /* prototype of the packet handler */ void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data); int main() { pcap_if_t *alldevs; pcap_if_t *d; int inum; int i=0; pcap_t *adhandle; char errbuf[PCAP_ERRBUF_SIZE]; u_int netmask; char packet_filter[] = "ip and udp"; struct bpf_program fcode; /* Load Npcap and its functions. */ if (!LoadNpcapDlls()) { fprintf(stderr, "Couldn't load Npcap\n"); exit(1); } /* Retrieve the device list */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) { fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf); exit(1); } /* Print the list */ for(d=alldevs; d; d=d->next) { printf("%d. %s", ++i, d->name); if (d->description) printf(" (%s)\n", d->description); else printf(" (No description available)\n"); } if(i==0) { printf("\nNo interfaces found! Make sure Npcap is installed.\n"); return -1; } printf("Enter the interface number (1-%d):",i); scanf_s("%d", &inum); if(inum < 1 || inum > i) { printf("\nInterface number out of range.\n"); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } /* Jump to the selected adapter */ for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++); /* Open the adapter */ if ( (adhandle= pcap_open(d->name, // name of the device 65536, // portion of the packet to capture. // 65536 grants that the whole packet // will be captured on all the MACs. PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode 1000, // read timeout NULL, // remote authentication errbuf // error buffer ) ) == NULL) { fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Npcap\n", d->name); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } /* Check the link layer. We support only Ethernet for simplicity. */ if(pcap_datalink(adhandle) != DLT_EN10MB) { fprintf(stderr,"\nThis program works only on Ethernet networks.\n"); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } if(d->addresses != NULL) /* Retrieve the mask of the first address of the interface */ netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; else /* If the interface is without addresses * we suppose to be in a C class network */ netmask=0xffffff; //compile the filter if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) <0 ) { fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n"); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } //set the filter if (pcap_setfilter(adhandle, &fcode)<0) { fprintf(stderr,"\nError setting the filter.\n"); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } printf("\nlistening on %s...\n", d->description); /* At this point, we don't need any more the device list. Free it */ pcap_freealldevs(alldevs); /* start the capture */ pcap_loop(adhandle, 0, packet_handler, NULL); return 0; } /* Callback function invoked by libpcap for every incoming packet */ void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) { struct tm ltime; char timestr[16]; ip_header *ih; udp_header *uh; u_int ip_len; u_short sport,dport; time_t local_tv_sec; /* * Unused variable */ (VOID)(param); /* convert the timestamp to readable format */ local_tv_sec = header->ts.tv_sec; localtime_s(<ime, &local_tv_sec); strftime( timestr, sizeof timestr, "%H:%M:%S", <ime); /* print timestamp and length of the packet */ printf("%s.%.6d len:%d ", timestr, header->ts.tv_usec, header->len); /* retireve the position of the ip header */ ih = (ip_header *) (pkt_data + 14); //length of ethernet header /* retireve the position of the udp header */ ip_len = (ih->ver_ihl & 0xf) * 4; uh = (udp_header *) ((u_char*)ih + ip_len); /* convert from network byte order to host byte order */ sport = ntohs( uh->sport ); dport = ntohs( uh->dport ); /* print ip addresses and udp ports */ printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n", ih->saddr.byte1, ih->saddr.byte2, ih->saddr.byte3, ih->saddr.byte4, sport, ih->daddr.byte1, ih->daddr.byte2, ih->daddr.byte3, ih->daddr.byte4, dport); }
First of all, we set the filter to "ip and udp". In this way we are sure that packet_handler() will receive only UDP packets over IPv4: this simplifies the parsing and increases the efficiency of the program.
We have also created a couple of structs that describe the IP and UDP headers. These structs are used by packet_handler() to properly locate the various header fields.
packet_handler(), although limited to a single protocol dissector (UDP over IPv4), shows how complex "sniffers" like tcpdump/WinDump decode the network traffic. Since we aren't interested in the MAC header, we skip it. For simplicity and before starting the capture, we check the MAC layer with pcap_datalink() to make sure that we are dealing with an Ethernet network. This way we can be sure that the MAC header is exactly 14 bytes.
The IP header is located just after the MAC header. We will extract the IP source and destination addresses from the IP header.
Reaching the UDP header is a bit more complicated, because the IP header doesn't have a fixed length. Therefore, we use the IP header's length field to know its size. Once we know the location of the UDP header, we extract the source and destination ports.
The extracted values are printed on the screen, and the result is something like:
\Device\Packet_{A7FD048A-5D4B-478E-B3C1-34401AC3B72F} (Xircom t 10/100 Adapter) Enter the interface number (1-2):1 listening on Xircom CardBus Ethernet 10/100 Adapter... 16:13:15.312784 len:87 130.192.31.67.2682 -> 130.192.3.21.53 16:13:15.314796 len:137 130.192.3.21.53 -> 130.192.31.67.2682 16:13:15.322101 len:78 130.192.31.67.2683 -> 130.192.3.21.53
Each of the final 3 lines represents a different packet.
Handling offline dump files
In this lession we are going to learn how to handle packet capture to a file (dump to file). Npcap offers a wide range of functions to save the network traffic to a file and to read the content of dumps—this lesson will teach how to use all of these functions.
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 used by many network tools including WinDump, Wireshark and Snort.
Saving packets to a dump file
First of all, let's see how to write packets in libpcap format.
The following example captures the packets from the selected interface and saves them on a file whose name is provided by the user.
#include <pcap.h> #include "misc.h" /* LoadNpcapDlls */ /* prototype of the packet handler */ void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data); int main(int argc, char **argv) { pcap_if_t *alldevs; pcap_if_t *d; int inum; int i=0; pcap_t *adhandle; char errbuf[PCAP_ERRBUF_SIZE]; pcap_dumper_t *dumpfile; /* Load Npcap and its functions. */ if (!LoadNpcapDlls()) { fprintf(stderr, "Couldn't load Npcap\n"); exit(1); } /* Check command line */ if(argc != 2) { printf("usage: %s filename", argv[0]); return -1; } /* Retrieve the device list on the local machine */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) { fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf); exit(1); } /* Print the list */ for(d=alldevs; d; d=d->next) { printf("%d. %s", ++i, d->name); if (d->description) printf(" (%s)\n", d->description); else printf(" (No description available)\n"); } if(i==0) { printf("\nNo interfaces found! Make sure Npcap is installed.\n"); return -1; } printf("Enter the interface number (1-%d):",i); scanf_s("%d", &inum); if(inum < 1 || inum > i) { printf("\nInterface number out of range.\n"); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } /* Jump to the selected adapter */ for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++); /* Open the device */ if ( (adhandle= pcap_open(d->name, // name of the device 65536, // portion of the packet to capture // 65536 guarantees that the whole packet // will be captured on all the link layers PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode 1000, // read timeout NULL, // authentication on the remote machine errbuf // error buffer ) ) == NULL) { fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Npcap\n", d->name); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } /* Open the dump file */ dumpfile = pcap_dump_open(adhandle, argv[1]); if(dumpfile==NULL) { fprintf(stderr,"\nError opening output file\n"); return -1; } printf("\nlistening on %s... Press Ctrl+C to stop...\n", d->description); /* At this point, we no longer need the device list. Free it */ pcap_freealldevs(alldevs); /* start the capture */ pcap_loop(adhandle, 0, packet_handler, (unsigned char *)dumpfile); return 0; } /* Callback function invoked by libpcap for every incoming packet */ void packet_handler(u_char *dumpfile, const struct pcap_pkthdr *header, const u_char *pkt_data) { /* save the packet on the dump file */ pcap_dump(dumpfile, header, pkt_data); }
As you can see, the structure of the program is very similar to the ones we have seen in the previous lessons. The differences are:
- a call to pcap_dump_open() 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 a pcap_dump() from the
packet_handler() callback. The parameters of
pcap_dump()
are in 1-1 correspondence with the parameters of pcap_handler().
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 Npcap/libpcap dump file and displays every packet contained in the file. The file is opened with pcap_open_offline(), then the usual pcap_loop() 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.
This example introduces another function:
pcap_createsrcstr()
. This function is required to
create a source string that begins with a marker used to tell Npcap the
type of the source, e.g. "rpcap://" if we are going to open an adapter,
or "file://" if we are going to open a file. This step is not required
when pcap_findalldevs_ex()
is used (the returned
values already contain these strings). However, it is required in this
example because the name of the file is read from the user
input.
#include <stdio.h> #include <pcap.h> #include "misc.h" /* LoadNpcapDlls */ #define LINE_LEN 16 void dispatcher_handler(u_char *, const struct pcap_pkthdr *, const u_char *); int main(int argc, char **argv) { pcap_t *fp; char errbuf[PCAP_ERRBUF_SIZE]; char source[PCAP_BUF_SIZE]; /* Load Npcap and its functions. */ if (!LoadNpcapDlls()) { fprintf(stderr, "Couldn't load Npcap\n"); exit(1); } if(argc != 2){ printf("usage: %s filename", argv[0]); return -1; } /* Create the source string according to the new Npcap syntax */ if ( pcap_createsrcstr( source, // variable that will keep the source string PCAP_SRC_FILE, // we want to open a file NULL, // remote host NULL, // port on the remote host argv[1], // name of the file we want to open errbuf // error buffer ) != 0) { fprintf(stderr,"\nError creating a source string\n"); return -1; } /* Open the capture file */ if ( (fp= pcap_open(source, // name of the device 65536, // portion of the packet to capture // 65536 guarantees that the whole packet // will be captured on all the link layers PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode 1000, // read timeout NULL, // authentication on the remote machine errbuf // error buffer ) ) == NULL) { fprintf(stderr,"\nUnable to open the file %s.\n", source); return -1; } // read and dispatch packets until EOF is reached pcap_loop(fp, 0, dispatcher_handler, NULL); return 0; } void dispatcher_handler(u_char *temp1, const struct pcap_pkthdr *header, const u_char *pkt_data) { u_int i=0; /* * Unused variable */ (VOID)temp1; /* print pkt timestamp and pkt len */ printf("%ld:%ld (%ld)\n", header->ts.tv_sec, header->ts.tv_usec, header->len); /* Print the packet */ for (i=1; (i < header->caplen + 1 ) ; i++) { printf("%.2x ", pkt_data[i-1]); if ( (i % LINE_LEN) == 0) printf("\n"); } printf("\n\n"); }
The following example has the same purpose of the last one, but pcap_next_ex() is used instead of the pcap_loop() callback method.
#include <stdio.h> #include <pcap.h> #include "misc.h" /* LoadNpcapDlls */ #define LINE_LEN 16 int main(int argc, char **argv) { pcap_t *fp; char errbuf[PCAP_ERRBUF_SIZE]; char source[PCAP_BUF_SIZE]; struct pcap_pkthdr *header; const u_char *pkt_data; u_int i=0; int res; /* Load Npcap and its functions. */ if (!LoadNpcapDlls()) { fprintf(stderr, "Couldn't load Npcap\n"); exit(1); } if(argc != 2) { printf("usage: %s filename", argv[0]); return -1; } /* Create the source string according to the new Npcap syntax */ if ( pcap_createsrcstr( source, // variable that will keep the source string PCAP_SRC_FILE, // we want to open a file NULL, // remote host NULL, // port on the remote host argv[1], // name of the file we want to open errbuf // error buffer ) != 0) { fprintf(stderr,"\nError creating a source string\n"); return -1; } /* Open the capture file */ if ( (fp= pcap_open(source, // name of the device 65536, // portion of the packet to capture // 65536 guarantees that the whole packet // will be captured on all the link layers PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode 1000, // read timeout NULL, // authentication on the remote machine errbuf // error buffer ) ) == NULL) { fprintf(stderr,"\nUnable to open the file %s.\n", source); return -1; } /* Retrieve the packets from the file */ while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0) { /* print pkt timestamp and pkt len */ printf("%ld:%ld (%ld)\n", header->ts.tv_sec, header->ts.tv_usec, header->len); /* Print the packet */ for (i=1; (i < header->caplen + 1 ) ; i++) { printf("%.2x ", pkt_data[i-1]); if ( (i % LINE_LEN) == 0) printf("\n"); } printf("\n\n"); } if (res == -1) { printf("Error reading the packets: %s\n", pcap_geterr(fp)); } return 0; }
Sending Packets
Although the name Npcap indicates clearly that the purpose of the library is packet capture, other useful features for raw networking are provided. Among them, the user can find a complete set of functions to send packets.
Sending a single packet with pcap_sendpacket()
The simplest way to send a packet is shown in the following code
snippet. After opening an adapter, pcap_sendpacket() is called to
send a hand-crafted packet. pcap_sendpacket()
takes
as arguments a buffer containing the data to send, the length of the
buffer and the adapter that will send it. Notice that the buffer is
sent to the net as is, without any manipulation. This means that the
application has to create the correct protocol headers in order to send
something meaningful.
#include <stdlib.h> #include <stdio.h> #include <pcap.h> #include "misc.h" /* LoadNpcapDlls */ void main(int argc, char **argv) { pcap_t *fp; char errbuf[PCAP_ERRBUF_SIZE]; u_char packet[100]; int i; /* Load Npcap and its functions. */ if (!LoadNpcapDlls()) { fprintf(stderr, "Couldn't load Npcap\n"); exit(1); } /* Check the validity of the command line */ if (argc != 2) { printf("usage: %s interface (e.g. 'rpcap://eth0')", argv[0]); return; } /* Open the output device */ if ( (fp= pcap_open(argv[1], // name of the device 100, // portion of the packet to capture PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode 1000, // read timeout NULL, // authentication on the remote machine errbuf // error buffer ) ) == NULL) { fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Npcap\n", argv[1]); return; } /* Supposing to be on ethernet, set mac destination to 1:1:1:1:1:1 */ packet[0]=1; packet[1]=1; packet[2]=1; packet[3]=1; packet[4]=1; packet[5]=1; /* set mac source to 2:2:2:2:2:2 */ packet[6]=2; packet[7]=2; packet[8]=2; packet[9]=2; packet[10]=2; packet[11]=2; /* Fill the rest of the packet */ for(i=12;i<100;i++) { packet[i]=(u_char)i; } /* Send down the packet */ if (pcap_sendpacket(fp, packet, 100 /* size */) != 0) { fprintf(stderr,"\nError sending the packet: %s\n", pcap_geterr(fp)); return; } return; }
Send queues
While pcap_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.
A send queue is created calling the
pcap_sendqueue_alloc()
function, specifying the size
of the new send queue.
Once the send queue is created,
pcap_sendqueue_queue()
can be used to add a packet
to the send queue. This function takes a pcap_pkthdr
with the timestamp and the length and a buffer with the data of the
packet. These parameters are the same as those received by pcap_next_ex() and
pcap_handler()
, therefore queuing a packet that was
just captured or read from a file is a matter of passing these
parameters to pcap_sendqueue_queue()
.
To transmit a send queue, Npcap provides the
pcap_sendqueue_transmit()
function. Note the third
parameter: if nonzero, 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
pcap_sendqueue_transmit()
is much more efficient
than performing a series of pcap_sendpacket(), because the
send queue is buffered at kernel level drastically decreasing the
number of context switches.
When a queue is no longer needed, it can be deleted with
pcap_sendqueue_destroy()
that frees all the buffers
associated with the send queue.
The next program shows how to use send queues. It opens a capture file with pcap_open_offline(), then it moves the packets from the file to a properly allocated send queue. At his point it transmits the queue, synchronizing it if requested by the user.
Note that the link-layer of the dumpfile is compared with the one of the interface that will send the packets using pcap_datalink(), 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.
#include <stdlib.h> #include <stdio.h> #include <pcap.h> #ifdef _WIN32 #include <tchar.h> BOOL LoadNpcapDlls() { TCHAR npcap_dir[512]; UINT len; len = GetSystemDirectory(npcap_dir, 480); if (!len) { fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError()); return FALSE; } _tcscat_s(npcap_dir, 512, TEXT("\\Npcap")); if (SetDllDirectory(npcap_dir) == 0) { fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError()); return FALSE; } return TRUE; } #endif void usage(); void main(int argc, char **argv) { pcap_t *indesc,*outdesc; char errbuf[PCAP_ERRBUF_SIZE]; char source[PCAP_BUF_SIZE]; FILE *capfile; int caplen, sync; u_int res; pcap_send_queue *squeue; struct pcap_pkthdr *pktheader; u_char *pktdata; float cpu_time; u_int npacks = 0; errno_t fopen_error; #ifdef _WIN32 /* Load Npcap and its functions. */ if (!LoadNpcapDlls()) { fprintf(stderr, "Couldn't load Npcap\n"); exit(1); } #endif /* Check the validity of the command line */ if (argc <= 2 || argc >= 5) { usage(); return; } /* Retrieve the length of the capture file */ fopen_error = fopen_s(&capfile, argv[1],"rb"); if(fopen_error != 0){ printf("Error opening the file, errno %d.\n", fopen_error); return; } fseek(capfile , 0, SEEK_END); caplen= ftell(capfile)- sizeof(struct pcap_file_header); fclose(capfile); /* Chek if the timestamps must be respected */ if(argc == 4 && argv[3][0] == 's') sync = TRUE; else sync = FALSE; /* Open the capture */ /* Create the source string according to the new WinPcap syntax */ if ( pcap_createsrcstr( source, // variable that will keep the source string PCAP_SRC_FILE, // we want to open a file NULL, // remote host NULL, // port on the remote host argv[1], // name of the file we want to open errbuf // error buffer ) != 0) { fprintf(stderr,"\nError creating a source string\n"); return; } /* Open the capture file */ if ( (indesc= pcap_open(source, 65536, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf) ) == NULL) { fprintf(stderr,"\nUnable to open the file %s.\n", source); return; } /* Open the output adapter */ if ( (outdesc= pcap_open(argv[2], 100, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf) ) == NULL) { fprintf(stderr,"\nUnable to open adapter %s.\n", source); return; } /* Check the MAC type */ if (pcap_datalink(indesc) != pcap_datalink(outdesc)) { printf("Warning: the datalink of the capture differs" " from the one of the selected interface.\n"); printf("Press a key to continue, or CTRL+C to stop.\n"); getchar(); } /* Allocate a send queue */ squeue = pcap_sendqueue_alloc(caplen); /* Fill the queue with the packets from the file */ while ((res = pcap_next_ex( indesc, &pktheader, &pktdata)) == 1) { if (pcap_sendqueue_queue(squeue, pktheader, pktdata) == -1) { printf("Warning: packet buffer too small, not all the packets will be sent.\n"); break; } npacks++; } if (res == -1) { printf("Corrupted input file.\n"); pcap_sendqueue_destroy(squeue); return; } /* Transmit the queue */ cpu_time = (float)clock (); if ((res = pcap_sendqueue_transmit(outdesc, squeue, sync)) < squeue->len) { printf("An error occurred sending the packets: %s." " Only %d bytes were sent\n", pcap_geterr(outdesc), res); } cpu_time = (clock() - cpu_time)/CLK_TCK; printf ("\n\nElapsed time: %5.3f\n", cpu_time); printf ("\nTotal packets generated = %d", npacks); printf ("\nAverage packets per second = %d", (int)((double)npacks/cpu_time)); printf ("\n"); /* free the send queue */ pcap_sendqueue_destroy(squeue); /* Close the input file */ pcap_close(indesc); /* * close the output adapter * IMPORTANT: remember to close the adapter, otherwise there will be no * guarantee that all the packets will be sent! */ pcap_close(outdesc); return; } void usage() { printf("\nSendcap, sends a libpcap/tcpdump capture file to the net." " Copyright (C) 2002 Loris Degioanni.\n"); printf("\nUsage:\n"); printf("\t sendcap file_name adapter [s]\n"); printf("\nParameters:\n"); printf("\nfile_name: the name of the dump file that will be sent to the network\n"); printf("\nadapter: the device to use. Use \"WinDump -D\" for a list of valid devices\n"); printf("\ns: if present, forces the packets to be sent synchronously," " i.e. respecting the timestamps in the dump file.\n\n"); exit(0); }
Gathering Statistics on the network traffic
This lesson shows another advanced feature of Npcap: the ability to collect statistics about network traffic. The statistical engine makes use of the kernel-level packet filter to efficiently classify the incoming packet.
In order to use this feature, the programmer must open an adapter and
put it in statistical mode. This can be done with
pcap_setmode()
. In particular,
MODE_STAT
must be used as the mode
argument of this function.
With statistical mode, making an application that monitors the TCP traffic load is a matter of few lines of code. The following sample shows how to do it.
#include <stdlib.h> #include <stdio.h> #include <pcap.h> #include <tchar.h> BOOL LoadNpcapDlls() { _TCHAR npcap_dir[512]; UINT len; len = GetSystemDirectory(npcap_dir, 480); if (!len) { fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError()); return FALSE; } _tcscat_s(npcap_dir, 512, _T("\\Npcap")); if (SetDllDirectory(npcap_dir) == 0) { fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError()); return FALSE; } return TRUE; } void usage(); void dispatcher_handler(u_char *, const struct pcap_pkthdr *, const u_char *); void main(int argc, char **argv) { pcap_t *fp; char errbuf[PCAP_ERRBUF_SIZE]; struct timeval st_ts; u_int netmask; struct bpf_program fcode; /* Load Npcap and its functions. */ if (!LoadNpcapDlls()) { fprintf(stderr, "Couldn't load Npcap\n"); exit(1); } /* Check the validity of the command line */ if (argc != 2) { usage(); return; } /* Open the output adapter */ if ( (fp= pcap_open(argv[1], 100, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf) ) == NULL) { fprintf(stderr,"\nUnable to open adapter %s.\n", errbuf); return; } /* Don't care about netmask, it won't be used for this filter */ netmask=0xffffff; //compile the filter if (pcap_compile(fp, &fcode, "tcp", 1, netmask) <0 ) { fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n"); /* Free the device list */ return; } //set the filter if (pcap_setfilter(fp, &fcode)<0) { fprintf(stderr,"\nError setting the filter.\n"); pcap_close(fp); /* Free the device list */ return; } /* Put the interface in statstics mode */ if (pcap_setmode(fp, MODE_STAT)<0) { fprintf(stderr,"\nError setting the mode.\n"); pcap_close(fp); /* Free the device list */ return; } printf("TCP traffic summary:\n"); /* Start the main loop */ pcap_loop(fp, 0, dispatcher_handler, (PUCHAR)&st_ts); pcap_close(fp); return; } void dispatcher_handler(u_char *state, const struct pcap_pkthdr *header, const u_char *pkt_data) { struct timeval *old_ts = (struct timeval *)state; u_int delay; LARGE_INTEGER Bps,Pps; struct tm ltime; char timestr[16]; time_t local_tv_sec; /* Calculate the delay in microseconds from the last sample. This value * is obtained from the timestamp that the associated with the sample. */ delay = (header->ts.tv_sec - old_ts->tv_sec) * 1000000 - old_ts->tv_usec + header->ts.tv_usec; /* Get the number of Bits per second */ Bps.QuadPart=(((*(LONGLONG*)(pkt_data + 8)) * 8 * 1000000) / (delay)); /* ^ ^ | | | | | | converts bytes in bits -- | | delay is expressed in microseconds -- */ /* Get the number of Packets per second */ Pps.QuadPart=(((*(LONGLONG*)(pkt_data)) * 1000000) / (delay)); /* Convert the timestamp to readable format */ local_tv_sec = header->ts.tv_sec; localtime_s(<ime, &local_tv_sec); strftime( timestr, sizeof timestr, "%H:%M:%S", <ime); /* Print timestamp*/ printf("%s ", timestr); /* Print the samples */ printf("BPS=%I64u ", Bps.QuadPart); printf("PPS=%I64u\n", Pps.QuadPart); //store current timestamp old_ts->tv_sec=header->ts.tv_sec; old_ts->tv_usec=header->ts.tv_usec; } void usage() { printf("\nShows the TCP traffic load, in bits per second and packets per second." "\nCopyright (C) 2002 Loris Degioanni.\n"); printf("\nUsage:\n"); printf("\t tcptop adapter\n"); printf("\t You can use \"WinDump -D\" if you don't know the name of your adapters.\n"); exit(0); }
Before enabling statistical mode, the user has the option to set a filter that defines the subset of network traffic that will be monitored. See the Filtering expression syntax documentation for details. If no filter has been set, all of the traffic will be monitored.
Once
- the filter is set
pcap_setmode()
is called- callback invocation is enabled with pcap_loop()
the interface descriptor starts to work in statistical mode. Notice the
fourth parameter (to_ms
) of pcap_open(): it defines the interval
among the statistical samples. The callback function receives the samples
calculated by the driver every to_ms
milliseconds.
These samples are encapsulated in the second and third parameters of the
callback function.
Two 64-bit counters are provided: the number of packets and the amount of
bytes received during the last interval.
In the example, the adapter is opened with a timeout of 1000 ms. This
means that dispatcher_handler() is called once per second. At this point
a filter that keeps only tcp packets is compiled and set. Then
pcap_setmode()
and pcap_loop()
are
called. Note that a struct timeval pointer is passed to
pcap_loop()
as the user
parameter.
This structure will be used to store a timestamp in order to calculate
the interval between two samples. dispatcher_handler()uses this interval
to obtain the bits per second and the packets per second and then prints
these values on the screen.
Note finally that this example is by far more efficient than a program that captures the packets in the traditional way and calculates statistics at user-level. Statistical mode requires the minumum amount of data copies and context switches and therefore the CPU is optimized. Moreover, a very small amount of memory is required.