10.2. Getting Started with libpcapNow that we have libpcap installed, we can write our first network packet-capture tool. The example we are going to demonstrate is a simple tool for capturing Address Resolution Protocol (ARP) packets from a local network interface. A slightly more complex tool utilizing libpcap to capture and process TCP headers (SYNplescan) is discussed in Chapter 11.
10.2.1. Overview of ArpsniffARP is the protocol used in IP networks to map network protocol addresses (most often, IP addresses) to link layer hardware addresses. When a system on a network needs to communicate with another system on the local subnet (for example, another system on the local TCP/IP subnet), it consults its cache of hardware and protocol addresses (commonly Ethernet Media Access Control, or MAC addresses) to determine if a matching system is known. Otherwise, an ARP exchange is sent to the network device hardware broadcast address, as shown in Figure 10-1. Figure 10-1. Overview of an ARP exchangeArpsniff is designed to capture both packets in the ARP packet interchanges occurring on the network, and to output the IP addresses of the machines involved. This could be useful for discovering live hosts on the network, or for some other network reconnaissance purpose. For clarity we can separate Arpsniff into five major sections of libpcap functionality to understand what we are doing at each step:
10.2.2. Identify the Network InterfaceTo capture packets from a network interface, we need to supply libpcap with a network interface to use for packet capture. We have a number of different options, including specifying a network interface, asking libpcap to automatically find an appropriate interface, obtaining a list of the available interfaces, and in recent versions of libpcap, using all available interfaces to capture traffic.
The easiest way is to let libpcap choose a suitable interface: #include <pcap.h> char *device; /* device to sniff on */ char errbuf[PCAP_ERRBUF_SIZE]; /* pcap error messages buffer */ device = pcap_lookupdev (errbuf); /* let pcap find a compatible device */ if (device == NULL) /* there was an error */ { fprintf (stderr, "%s", errbuf); exit (1); } To use the libpcap functions, we are including the pcap.h header file. This contains the libpcap function definitions as well as other handy, predefined values, such as PCAP_ERRBUF_SIZE. The prototype for pcap_lookupdev is as follows: char *pcap_lookupdev(char *errbuf) This function returns the name of an appropriate interface to be used for packet capture. For Linux this is typically eth0 or something similar, but this might be different for other operating systems. The function returns NULL and the errbuf array is populated with an error message if an error occursfor example, if no suitable interfaces were located or if the user running the tool did not have sufficient privileges to perform the operation. A number of functions within libpcap use an errbuf array in this way to return meaningful error messages to the calling tool. Instead of letting libpcap choose a suitable interface, you can allow the user to specify one. For some tools it is useful to be able to obtain a list of usable network interfaces: pcap_if_t *alldevsp; /* list of interfaces */ if (pcap_findalldevs (&alldevsp, errbuf) < 0) { fprintf (stderr, "%s", errbuf); exit (1); } while (alldevsp != NULL) { printf ("%s\n", alldevsp->name); alldevsp = alldevsp->next; } The pcap_findalldevs function takes a pcap_if_t pointer and returns a linked list of information about the interfaces found. The pcap_if_t (a type derived from pcap_if) structure contains several pieces of information that might be useful to a tool: struct pcap_if { struct pcap_if *next; char *name; /* interface name */ char *description; /* human-readable description of interface, or NULL */ struct pcap_addr *addresses; bpf_u_int32 flags; /* PCAP_IF_LOOPBACK if a loopback interface */ }; The linked list is populated with the names and descriptions of all the interfaces libpcap can use, as well as the IP address and netmask of the interfaces, as follows: struct pcap_addr { struct pcap_addr *next; struct sockaddr *addr; /* interface address */ struct sockaddr *netmask; /* netmask for that address */ struct sockaddr *broadaddr; /* broadcast address */ struct sockaddr *dstaddr; /* point-to-point destination or NULL */ }; You could use the information this returns to allow the person using the tool to select an appropriate interface to use, such as the network to which the interface is attached. 10.2.3. Open the Network InterfaceOnce we have a network interface supplied by the user, or libpcap has located an appropriate interface, we can open the interface for packet capture: pcap_t *handle; handle = pcap_open_live (device, /* device to sniff on */ BUFSIZ, /* maximum number of bytes to capture per packet */ 1, /* promisc - 1 to set card in promiscuous mode, 0 to not */ 0, /* to_ms - amount of time to perform packet capture in milliseconds */ /* 0 = sniff until error */ errbuf); /* error message buffer if something goes wrong */ if (handle == NULL) /* there was an error */ { fprintf (stderr, "%s", errbuf); exit (1); } if (strlen (errbuf) > 0) { fprintf (stderr, "Warning: %s", errbuf); /* a warning was generated */ errbuf[0] = 0; /* reset error buffer */ } pcap_t provides a packet-capture descriptor to the opened session which is used throughout the tool. pcap_t is a typedef of the pcap structure that is used internally within libpcap; however, the user should never need to know what this structure actually contains. The prototype for pcap_open_live is as follows: pcap_t *pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *errbuf) The pcap_open_live function is used to open network interfaces for packet capture, and as such it takes several parameters, as shown in Table 10-1.
10.2.4. Configure Packet-Capture OptionsOnce we have an active packet-capture interface we can determine or set a number of options before we start capturing packets from the interface. For example, we can determine the type of interface that has been opened: if (pcap_datalink (handle) != DLT_EN10MB) { fprintf (stderr, "This program only supports Ethernet cards!\n"); exit (1); } The pcap_datalink function returns the type of the underlying link layer from the pcap_t handle passed to it. The prototype for pcap_datalink is as follows: int pcap_datalink(pcap_t *p) This function will generate an error if the selected network interface was not an Ethernet interface (10MB, 100MB, 1000MB, or more). It is wise to check the data link type before trying to manipulate data captured from the network interface, as this determines what format the data is in. The data link layers that libpcap can return include network data link types (such as Ethernet), as well as encapsulation types such as the common dial-up Point to Point Protocol (PPP) and OpenBSD pflog. Table 10-2 shows supported link types as of libpcap Version 0.8.3.
Some platforms and interfaces can have multiple link types available. In this case we need to interrogate the underlying data link layer to see what link types are supported. We can do this using pcap_list_datalinks with the pcap_t handle from the opened session: int *dlt_buf; /* array of supported data link types */ int num; /* number of supported link type */ int i; /* counter for for loop */ num = pcap_list_datalinks(handle, &dlt_buf); for (i=0; i<num; i++) { printf("%d - %s - %s\n",dlt_buf[i], pcap_datalink_val_to_name(dlt_buf[i]), pcap_datalink_val_to_description(dlt_buf[i])); } This example uses three functions to enumerate the data link types, and to display human-readable names and descriptions for them. The prototypes of these functions are as follows: int pcap_list_datalinks(pcap_t *p, int **dlt_buf); const char *pcap_datalink_val_to_name(int dlt); const char *pcap_datalink_val_to_description(int dlt); In most cases, the preceding code displays only one link type and the output usually is something such as the following: > ./example > 1 - EN10MB - Ethernet However, when multiple data link types are supported, something such as the following can be displayed. This was run on FreeBSD 5.2 with an Atheros-based wireless network card: > ./example > 127 - IEEE802_11_RADIO - 802.11 plus BSD radio information header > 105 - IEEE802_11 - 802.11 > 1 - EN10MB - Ethernet In this case, in which multiple link types are returned, we can select the desired link type using pcap_set_datalink, which has the following prototype: int pcap_set_datalink(pcap_t *p, int dlt); For example, the following code is required on recent versions of FreeBSD to capture data in Radiotap format from supported wireless cards: if (pcap_set_datalink (handle, DLT_IEEE802_11_RADIO) == -1) { pcap_perror (handle, "Error on pcap_set_datalink: "); exit (1); } Now that we have determined that the link type we are capturing on is Ethernet-based, we can assume the interface has an IP address and netmask (as Arpsniff does not work on a non-IP network). We can determine the IP address and netmask as follows: bpf_u_int32 netp; /* ip address of interface */ bpf_u_int32 maskp; /* subnet mask of interface */ if (pcap_lookupnet (device, &netp, &maskp, errbuf) == -1) { fprintf (stderr, "%s", errbuf); exit (1); } The pcap_lookupnet function has the following prototype: int pcap_lookupnet(const char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf) This function returns the network address and netmask as an integer value. To convert these to a human-readable format, you can do something such as the following: char *net_addr; struct in_addr addr; addr.s_addr = netp; net_addr = inet_ntoa(addr); The pcap_lookupnet function does not take a pcap_t argument, as it can be run on an interface before it is opened for packet capture. This could be used to locate a particular interface as an alternative to using pcap_findalldevs. You also can use this information when setting a Berkeley Packet Filter (BPF) on the capture, which requires the netmask of the network to be capturing on. libpcap supports BPF filter programs for filtering incoming packets. BPF is a powerful filtering language based on a programmable state engine running pseudo-Assembly language instructions, as shown in Example 10-1. Example 10-1. tcpdump -d output for "arp" filter(000) ldh [12] (001) jeq #0x806 jt 2 jf 3 (002) ret #68 (003) ret #0 libpcap supports BPF at the kernel level for systems that have operating system support for BPF, such as AIX, and in a user-space implementation in the libpcap library for systems that do not have kernel BPF implementations. On systems that have BPF support at the kernel level, filtering can be done very quickly and efficiently, as the packets the filter drops do not have to be copied from the kernel space to the tool running in user space. Using libpcap we can generate a BPF filter from a tcpdump-style, human-readable filter string using the pcap_compile function, as shown here: char *filter = "arp"; /* filter for BPF (human readable) */ struct bpf_program fp; /* compiled BPF filter */ if (pcap_compile (handle, &fp, filter, 0, maskp) == -1) { fprintf (stderr, "%s", pcap_geterr (handle)); exit (1); } The prototype for pcap_compile is as follows: int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask) This function supports the same human-readable filter syntax used by tcpdump. Read the full syntax from the tcpdump manpage, or online at http://www.tcpdump.org. Table 10-3 shows some examples of the syntax.
Once the human-readable syntax has been compiled into the state machine pseudocode, we can set the filter on the capture session we have initiated as follows: if (pcap_setfilter (handle, &fp) == -1) { fprintf (stderr, "%s", pcap_geterr (handle)); exit (1); } Here is the prototype for the pcap_setfilter function: int pcap_setfilter(pcap_t *p, struct bpf_program *fp)q The pcap_setfilter function sets the BPF program in the kernel where BPF support is present or in a user-space implementation if there is no kernel support for BPF. After we have successfully set the filter on our capture, we can free the memory used for the filter (in this case, a rather trivial amount) as follows: pcap_freecode (&fp); Now we are ready to capture some packets from the interface we have opened, with the BPF filter we have set. For Arpsniff we have set a filter of arp, so we should only have ARP packets passed to us by the filter. 10.2.5. Capture and Process Packetslibpcap has several options for handling the capture and processing of packets. The three main functions for capturing and processing packets are shown in Table 10-4.
Also available to the user for simple tasks is the pcap_next function. This is a wrapper to the pcap_dispatch function with a cnt of 1.
pcap_next has the following prototype: const u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h) As the pcap_next function doesn't support error messages, you should use pcap_next_ex instead if capturing single packets. For Arpsniff we are going to use pcap_loop as follows: if ((r = pcap_loop (handle, -1, process_packet, NULL)) < 0) { if (r == -1) /* pcap error */ { fprintf (stderr, "%s", pcap_geterr (handle)); exit (1); } /* otherwise return should be -2, meaning pcap_breakloop has been called */ } The process_packet parameter passed to pcap_loop is the name of the function we have written to handle the packet in whichever way we want when it has been captured. Both pcap_dispatch and pcap_loop use a callback function with the same parameters as follows: void process_packet (u_char *user, const struct pcap_pkthdr *header, const u_char *packet) The callback function does not return anything, as pcap_loop would not know what to do with the returned value. As parameters, pcap_loop passes in a header with information about the packet, as well as a pointer to the body of the packet itself. The user value is the value specified in pcap_loop and is not commonly used. Now we can write the main functionality of the tool within the callback function, and we can run this every time a packet matching the filter is run. 10.2.6. Close DownOnce we are finished capturing packets, we should gracefully close down the connection before we exit the tool. Two functions can come in handy in this case. Arpsniff uses a trivial signal handler to intercept the Ctrl-C break sequence. Because the tool is in an endless loop, due to the pcap_loop function, the signal handler calls the pcap_breakloop function. This function, which is available only in recent versions of libpcap, is designed for use in signal handlers or similar tools, and allows the packet- capture loop to be interrupted smoothly and the tool to exit gracefully. pcap_breakloop takes only one argument and has the following prototype: void pcap_breakloop(pcap_t *) Now that we have exited the packet-capture loop, we can close the packet-capture handler and associated resources using the pcap_close function, which has the following prototype: void pcap_close(pcap_t *p) 10.2.7. ArpsniffExample 10-2 shows the complete code for the Arpsniff tool we have been discussing. You should be able to compile this on most Linux distributions as follows: gcc -o arpsniff arpsniff.c -lpcap The -lpcap option instructs gcc to link the final binary tool against the pcap library. Note that this has been developed on Gentoo Linux on x86, and with the removal of the pcap_breakloop call on Red Hat Enterprise Linux on x86. Although it should work on other Linux variants, it might not work on other Unix-like systems without a little tweaking. Example 10-2. Arpsniff source code#include <stdio.h> #include <unistd.h> #include <signal.h> #include <net/if.h> #include <pcap.h> #include <netinet/if_ether.h> /* ugly shortcut -- Ethernet packet headers are 14 bytes */ #define ETH_HEADER_SIZE 14 /* for the sake of clarity we'll use globals for a few things */ char *device; /* device to sniff on */ int verbose = 0; /* verbose output about device */ pcap_t *handle; /* handle for the opened pcap session */ /* gracefully handle a Control C */ void ctrl_c ( ) { printf ("Exiting\n"); pcap_breakloop (handle); /* tell pcap_loop or pcap_dispatch to stop capturing */ pcap_close(handle); exit (0); } /* usage */ void usage (char *name) { printf ("%s - simple ARP sniffer\n", name); printf ("Usage: %s [-i interface] [-l] [-v]\n", name); printf (" -i interface to sniff on\n"); printf (" -l list available interfaces\n"); printf (" -v print verbose info\n\n"); exit (1); } /* callback function to process a packet when captured */ void process_packet (u_char *user, const struct pcap_pkthdr *header, const u_char * packet) { struct ether_header *eth_header; /* in ethernet.h included by if_eth.h */ struct ether_arp *arp_packet; /* from if_eth.h */ eth_header = (struct ether_header *) packet; arp_packet = (struct ether_arp *) (packet + ETH_HEADER_SIZE); if (ntohs (eth_header->ether_type) == ETHERTYPE_ARP) /* if it is an ARP packet */ { printf ("Source: %d.%d.%d.%d\t\tDestination: %d.%d.%d.%d\n", arp_packet->arp_spa[0], arp_packet->arp_spa[1], arp_packet->arp_spa[2], arp_packet->arp_spa[3], arp_packet->arp_tpa[0], arp_packet->arp_tpa[1], arp_packet->arp_tpa[2], arp_packet->arp_tpa[3]); } } int main (int argc, char *argv[]) { char o; /* for option processing */ char errbuf[PCAP_ERRBUF_SIZE]; /* pcap error messages buffer */ struct pcap_pkthdr header; /* packet header from pcap */ const u_char *packet; /* packet */ bpf_u_int32 netp; /* ip address of interface */ bpf_u_int32 maskp; /* subnet mask of interface */ char *filter = "arp"; /* filter for BPF (human readable) */ struct bpf_program fp; /* compiled BPF filter */ int r; /* generic return value */ pcap_if_t *alldevsp; /* list of interfaces */ while ((o = getopt (argc, argv, "i:vl")) > 0) { switch (o) { case 'i': device = optarg; break; case 'l': if (pcap_findalldevs (&alldevsp, errbuf) < 0) { fprintf (stderr, "%s", errbuf); exit (1); } while (alldevsp != NULL) { printf ("%s\n", alldevsp->name); alldevsp = alldevsp->next; } exit (0); case 'v': verbose = 1; break; default: usage (argv[0]); break; } } /* setup signal handler so Control-C will gracefully exit */ signal (SIGINT, ctrl_c); /* find device for sniffing if needed */ if (device == NULL) /* if user hasn't specified a device */ { device = pcap_lookupdev (errbuf); /* let pcap find a compatible device */ if (device == NULL) /* there was an error */ { fprintf (stderr, "%s", errbuf); exit (1); } } /* set errbuf to 0 length string to check for warnings */ errbuf[0] = 0; /* open device for sniffing */ handle = pcap_open_live (device, /* device to sniff on */ BUFSIZ, /* maximum number of bytes to capture per packet */ /* BUFSIZE is defined in pcap.h */ 1, /* promisc - 1 to set card in promiscuous mode, 0 to not */ 0, /* to_ms - amount of time to perform packet capture in milliseconds */ /* 0 = sniff until error */ errbuf); /* error message buffer if something goes wrong */ if (handle == NULL) /* there was an error */ { fprintf (stderr, "%s", errbuf); exit (1); } if (strlen (errbuf) > 0) { fprintf (stderr, "Warning: %s", errbuf); /* a warning was generated */ errbuf[0] = 0; /* re-set error buffer */ } if (verbose) { printf ("Using device: %s\n", device); /* printf ("Using libpcap version %s", pcap_lib_version); */ } /* find out the datalink type of the connection */ if (pcap_datalink (handle) != DLT_EN10MB) { fprintf (stderr, "This program only supports Ethernet cards!\n"); exit (1); } /* get the IP subnet mask of the device, so we set a filter on it */ if (pcap_lookupnet (device, &netp, &maskp, errbuf) == -1) { fprintf (stderr, "%s", errbuf); exit (1); } /* compile the filter, so we can capture only stuff we are interested in */ if (pcap_compile (handle, &fp, filter, 0, maskp) == -1) { fprintf (stderr, "%s", pcap_geterr (handle)); exit (1); } /* set the filter for the device we have opened */ if (pcap_setfilter (handle, &fp) == -1) { fprintf (stderr, "%s", pcap_geterr (handle)); exit (1); } /* we'll be nice and free the memory used for the compiled filter */ pcap_freecode (&fp); if ((r = pcap_loop (handle, -1, process_packet, NULL)) < 0) { if (r == -1) /* pcap error */ { fprintf (stderr, "%s", pcap_geterr (handle)); exit (1); } /* otherwise return should be -2, meaning pcap_breakloop has been called */ } /* close our devices */ pcap_close (handle); } Example 10-3 shows a sample run of the Arpsniff tool, capturing the IP address ranges in use on this network by capturing ARP packets. Example 10-3. Sample run of the Arpsniff toolclarkju@home$ sudo arpsniff Source: 192.168.0.123 Destination: 192.168.0.1 Source: 192.168.0.1 Destination: 192.168.0.123 Source: 192.168.0.123 Destination: 192.168.0.101 Source: 192.168.0.101 Destination: 192.168.0.123 Source: 192.168.0.123 Destination: 192.168.0.138 Source: 192.168.0.138 Destination: 192.168.0.123 Source: 192.168.0.138 Destination: 192.168.0.123 Source: 192.168.0.123 Destination: 192.168.0.138 |