Network Security Tools/Modifying and Hacking Security Tools/Writing Network Sniffers

From WikiContent

< Network Security Tools | Modifying and Hacking Security Tools
Revision as of 21:39, 11 March 2008 by Docbook2Wiki (Talk)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search
Network Security Tools

An important function of many security tools is to capture network traffic and then either reassemble it or extract information from the network packets flowing across the network. Common examples of such tools include password sniffers such as dsniff (http://monkey.org/~dugsong/dsniff/) and Ettercap (http://ettercap.sourceforge.net/), and diagnostic, troubleshooting, and monitoring tools such as ntop (http://www.ntop.org) and Snort (http://www.snort.org).

This chapter provides a quick and practical introduction to packet capture using the commonly available libpcap library on wired and wireless networks, and is intended to accelerate and simplify the process of creating a packet-capturing tool.

Contents

Introduction to libpcap

libpcap is an open source C-language library for capturing network packets. libpcap is available for a number of different platforms, including most Unix and Unix-like platforms (such as Linux and BSD), as well as for Windows.

Although libpcap is primarily a packet-capturing tool, it also can create and manipulate packets from saved files, which can then be used in the wide variety of tools that support the libpcap format.

Why Use libpcap?

libpcap hides much of the complexity inherent in network packet capture. Packet capture is possible using native network functionality on most platforms; however, the interfaces and semantics required for capturing packets are not for the faint of heart. For example, the following is a fragment of code for packet capture from a tool I wrote for Linux some years ago:[1]

struct sockaddr_nl nl_addr;
int fd;
int recvlen;
unsigned char msgbuf[3000];

fd = socket (PF_NETLINK, SOCK_RAW, 0x02)

memset (&nl_addr, 0, sizeof (struct sockaddr_nl));
nl_addr.nl_family = (sa_family_t) PF_NETLINK;
nl_addr.nl_pid = (unsigned int) getpid ( );
nl_addr.nl_groups = 0x02;

bind (fd, (struct sockaddr *) &nl_addr, sizeof (struct sockaddr_nl)
recvlen = recv (fd, msgbuf, MAX_BUFFER_SIZE, 0)

As you can see, this is not the friendliest of code. It uses BSD socket calls to the Linux-only netlink(3) interface to pass packets from the kernel to the user tool.

libpcap hides the complexity of getting packets from the operating system, and it gives the tool developer a consistent interface for developing tools, regardless of the tool's intended operating system. In turn, this makes writing portable code much simpler, and it makes your tools much more useful.

Installing libpcap

You can obtain the latest version of libpcap from http://www.tcpdump.org. libpcap is easy to compile from the source code:

> tar zxvf libpcap-0.8.3.tar.gz
> cd libpcap-0.8.3
> ./configure
> make
> make install

Many Linux distributions also include libpcap as an optional package that you can install with the distribution, or add afterward. Because libpcap's functionality changes between versions, you should use the latest version of the libraries available for your distribution or compile the library from source for your own development.

Warning

If you are compiling libpcap from source, make sure you uninstall previous versions of libpcap to avoid problems with mismatched files. You will need to remove the following files from the libraries directory (commonly /usr/lib/ or /usr/local/lib/ ):

  • libpcap.a
  • libpcap.so.*.*

You will also need to remove the following files from the include files directory (commonly /usr/include/ or /usr/local/include/ ):

  • pcap.h
  • pcap-bpf.h
  • pcap-namedb.h

You might also have to add the path the libpcap libraries are installed to into the library search path (commonly /etc/ld.so.conf for Linux systems).

To develop the examples in this chapter, we'll be using libpcap Version 0.8.3. Although many of the examples work with earlier versions of libpcap, some functionality might not be available.

Getting Started with libpcap

Now 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.

Warning

Some of the operations we will undertake with libpcap work only if you are running as the root user. Therefore, tools written that use libpcap (as per the examples) commonly need to be run by the root user, or be SETUID root. Your tool should be careful of what it does with input and captured packets so that it is not vulnerable to buffer overflows and other security vulnerabilities. A well-written tool should generally drop privileges after functions requiring root privileges have been performed.

Overview of Arpsniff

ARP 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 exchange

Overview of an ARP exchange

Arpsniff 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:

  1. Identify the network interface.
  2. Open the network interface.
  3. Configure packet-capture options.
  4. Capture and process packets.
  5. Close down gracefully.

Tip

Note that Arpsniff captures interchanges on the network only if you are not in a switched environment. If you are in a switched environment you might see only one of the packets—i.e., the packet sent to the subnet broadcast address. Several techniques exist for capturing all packets on a switched environment. The Ettercap sniffer uses several of these techniques, including ARP poisoning. Visit http://ettercap.sourceforge.net/ for more information.

Identify the Network Interface

To 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.

Tip

libpcap does not support all network interfaces. Most Ethernet cards will work, as will most wireless cards while capturing packets on the network you are associated to. libpcap generates an error for any network interface supplied to it that it cannot determine how to open.

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 occurs—for 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.

Open the Network Interface

Once 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.

Table 10-1. Parameters to pcap_open_live

Parameter Description
device
The interface on which to capture traffic. This is either a string such as eth0, or any, or NULL, and it can be used to capture traffic from all interfaces on recent Linux systems.
snaplen
The maximum number of bytes to capture per packet (snapshot length). If this is less than the length of the packet, the packet is truncated. Note that this has nothing to do with Ethernet SNAP headers.
promisc
Flag to determine whether the interface should be put into promiscuous mode. Promiscuous mode instructs the network interface to capture all traffic on a shared medium network (such as Ethernet), regardless of whether it was intended for the system running the tool. Note that the interface could be in promiscuous mode for some other reason, and it might not be supported for all network interfaces.
to_ms
Timeout in milliseconds before a read operation returns. This is not supported on all platforms. A value of 0 causes the read to wait until an error occurs.
errbuf
Error buffer. If an error or warning occurs, this is populated with a human-readable error message.


Tip

Although all options are present for all platforms supported by libpcap, some options will work only if supported by the underlying operating system or device drivers. In particular, promiscuous mode might not work as expected on all devices. A good example is wireless network devices. Most wireless network devices will allow libpcap to capture wireless traffic in promiscuous mode in Unix-like operating systems, allowing a tool to capture packets on the network to which the user is associated. On Windows, this is not supported by all drivers. To capture all packets, including those not on the network to which a user is associated, special driver support is required. This is covered later in this chapter.

Configure Packet-Capture Options

Once 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.

Table 10-2. Link layers supported by libpcap

Data link type Description
DLT_EN10MB Ethernet devices, including 10MB, 100MB, 1000MB, and up
DLT_IEEE802_11 802.11 wireless devices; can include all the different variants of 802.11, including 802.11, 802.11a, 802.11b, 802.11g, and so on
DLT_NULL BSD loop-back encapsulation
DLT_IEEE802 802.5 token ring devices
DLT_ARCNET ARCNET devices
DLT_SLIP Serial Line Internet Protocol (SLIP; predecessor to PPP)
DLT_PPP PPP
DLT_SLIP_BSDOS BSD/OS SLIP
DLT_PPP_BSDOS BSD/OS PPP
DLT_ATM_CLIP Linux Classical IP (CLIP) over ATM
DLT_FDDI Fiber Distributed Data Interface (FDDI; data over fiber optic cable)
DLT_ATM_RFC1483 RFC 1483 encapsulated Asynchronous Transfer Mode (ATM)
DLT_RAW Raw IP packet
DLT_PPP_SERIAL PPP in HDLC framing (RFC 1662 or Cisco PPP with HDLC framing)
DLT_PPP_ETHER PPP over Ethernet (PPPoE); commonly used in DSL networks
DLT_C_HDLC Cisco PPP with HDLC framing
DLT_FRELAY Frame relay devices
DLT_LOOP OpenBSD loop-back encapsulation
DLT_ENC OpenBSD encapsulated IP
DLT_LINUX_SLL Linux cooked capture encapsulation
DLT_LTALK Apple LocalTalk
DLT_PFLOG OpenBSD pflog firewall log
DLT_PRISM_HEADER 802.11 Prism monitor mode devices
DLT_IP_OVER_FC RFC 2625 IP over Fiber Channel
DLT_SUNATM Sun raw ATM devices
DLT_IEEE802_11_RADIO BSD wireless with Radiotap header
DLT_APPLE_IP_OVER_IEEE1394 Apple IP over IEEE-1394 (FireWire)
DLT_IEEE802_11_RADIO_AVS AVS wireless monitor mode devices
DLT_ARCNET_LINUX Linux ARCNET devices
DLT_LINUX_IRDA Linux IRDA devices


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.

Table 10-3. Example of human-readable filters

Filter syntax Description
udp or arp Only UDP or ARP packets are passed.
icmp[icmptype] != icmp-echo
All ICMP packets that are not echo requests/replies.
host 192.168.0.12
All packets to/from 192.168.0.12.
ip proto 47
Only IP protocol 47 (GRE) packets.


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.

Capture and Process Packets

libpcap 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.

Table 10-4. libpcap packet-capture functions

Function Prototype Description
pcap_next_ex
int pcap_next_ex 
(pcap_t *p,
struct pcap_pkthdr **pkt_header,
const u_char **pkt_data)
Reads the next packet from the capture session, returning success or failure. The following values are returned:1Packet was read.0Timeout expired.-1An error occurred.-2Packets are being read from a saved file, and no more packets are available.If the packet was read, the pkt_header and pkt_data pointers are set to the packet header and packet data, respectively.
pcap_dispatch
int pcap_dispatch
(pcap_t *p,
int cnt,
pcap_handler callback,
u_char *user)
Reads up to cnt packets from the session. A cnt value of -1 reads all packets in the buffer. pcap_dispatch uses a callback function (discussed in a bit) to process packets, and returns the number of packets processed. pcap_dispatch returns when a read timeout occurs on supported platforms.The user value is a user-specified value to be passed to the callback function, and can be NULL.
pcap_loop
int pcap_loop
(pcap_t *p,
int cnt,
pcap_handler callback,
u_char *user)
Reads cnt packets from the session. pcap_loop uses a callback function to process packets, loops forever until cnt packets are processed (a value of -1 loops forever), and returns the following:0cnt packets read.-1An error occurred.-2Loop was terminated by pcap_breakloop.The user value is a user-specified value to be passed to the callback function, and can be NULL.


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.

Warning

Read timeouts specified in pcap_open_live are not supported consistently across platforms, and as such you can't rely on pcap_dispatch returning after the read timeout on all platforms. For this reason you should not use pcap_dispatch as a polling mechanism.

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.

Close Down

Once 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)

Arpsniff

Example 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 tool

clarkju@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

libpcap and 802.11 Wireless Networks

As shown in Table 10-2, libpcap supports packet capture from a wide variety of link types, including several link types related to 802.11 wireless networks.

The Arpsniff tool we just demonstrated was designed to work only on Ethernet networks (or more specifically, Ethernet II networks). We check the link type of the network interface because we receive different types of packet frames from the interface depending on the link type reported. For example, Arpsniff is expecting to receive an Ethernet II frame, containing an ARP packet. In this case, we know the Ethernet II frame has a header consisting of 14 bytes, as shown in Figure 10-2.

Figure 10-2. Ethernet II header format

Ethernet II header format

Had Arpsniff been capturing packets from an 802.11 wireless network, however, something such as the 802.11 packet header shown in Figure 10-3 would have been present.

Figure 10-3. Header format

Header format

In addition to expecting the correct packet framing for the data link type we are using, there is one other major obstacle to successful packet capture from wireless networks, and that is monitor mode.

802.11 Monitor Mode

In 802.11 wireless networks you are generally interested in capturing all information on a particular frequency or channel, regardless of the network the traffic belongs to. Unfortunately, putting an 802.11 wireless card into promiscuous mode does not allow you to capture all packets on a channel; rather, it allows you to capture only the packets on the network you are attached to on that channel. To capture all packets on a channel, you need to put the card into a special mode known as monitor or rfmon mode.

Tip

Monitor mode is the mode for monitoring traffic, usually on a particular channel. A lot of wireless hardware is capable of entering monitor mode, but the ability to set the wireless hardware into monitor mode depends on support within the wireless driver. As such, you can force many cards into monitor mode in Linux, but in Windows you will probably need to write your own wireless network card driver.

Table 10-5 shows some ways to make common 802.11 wireless cards enter monitor mode. A good reference for cards capable of entering monitor mode is available from the web site for the Kismet tool, located at http://www.kismetwireless.net.

Table 10-5. Example commands to enter monitor mode

Driver/card Operating system Command
Cisco Aironet Linux Echo "mode: y" > '/proc/driver/aironet/<device>/Config'
HostAP Linux iwconfig <device> mode monitor
Orinoco (patched) Linux iwpriv <device> monitor 1 <channel>
Madwifi Linux iwconfig <device> mode monitor
Wlan-ng Linux wlanctl-ng <device> lnxreq_wlansniff channel=<channel> enable=true
Radiotap FreeBSD ifconfig <device> monitor up


Another complication when capturing packets from a wireless card in monitor mode is that there is less consistency in packet-framing format. In addition to the 802.11 packet header shown in Figure 10-3, many wireless drivers return custom headers detailing a number of pieces of information about the captured packet, such as signal strength and noise values. The two most common of these are the Prism header and the AVS header.

The Prism monitor mode header was originally developed as part of the linux-wlan-ng project (http://www.linux-wlan.com/), and was designed for use when developing drivers for the Prism II 802.11b card for Linux. Now this format is supported on a wide variety of drivers, and it is supported by libpcap as the DLT_PRISM_HEADER link type. The Prism monitor mode header is of the following format:

struct prism_value
{
  uint32 did;
  uint16 status;
  uint16 len;
  uint32 data;
};

struct prism_header
{
  uint32 msgcode;
  uint32 msglen;
  u8char devname[16];
  struct prism_value hosttime;
  struct prism_value mactime;
  struct prism_value channel;
  struct prism_value rssi;
  struct prism_value sq;
  struct prism_value signal;
  struct prism_value noise;
  struct prism_value rate;
  struct prism_value istx;
  struct prism_value frmlen;
};

The AVS capture header is a newer development designed to replace the Prism monitor mode header. In addition to providing additional information, the AVS capture header can capture information about 802.11a and 802.11g packet-capture sources. The doc/capturefrm.txt file in the linux wlan-ng driver package is available from ftp://ftp.linux-wlan.org/pub/linux-wlan-ng/.

libpcap supports this format as DLT_IEEE802_11_RADIO_AVS. The AVS capture header is 64 bytes in length, and the format is as follows:

struct AVS_header
{
  uint32 version;
  uint32 length;
  uint64 mactime;
  uint64 hosttime;
  uint32 phytype;
  uint32 channel;
  uint32 datarate;
  uint32 antenna;
  uint32 priority;
  uint32 ssi_type;
  int32 ssi_signal;
  int32 ssi_noise;
  uint32 preamble;
  uint32 encoding;
};

Most recent wireless drivers that are capable of entering monitor mode support either a Prism monitor mode header or the AVS capture header, or both. Where possible you should use the AVS capture format, as this is better documented (i.e., it is documented, period) and is designed to be extensible to support newer technologies.

Adapting Arpsniff to 802.11

To adapt Arpsniff to capture information from a wireless packet-capture source, we need to make a few changes to the application logic. We assume the wireless device used in this example supports the AVS wireless capture format.

First of all, we need to specify the sizes of some of the additional frames captured:

/* ugly shortcuts - Defining our header types */
#define ETH_HEADER_SIZE 14
#define AVS_HEADER_SIZE 64                 /* AVS capture header size */
#define DATA_80211_FRAME_SIZE 24           /* header for 802.11 data packet */
#define LLC_HEADER_SIZE 8                  /* LLC frame for encapsulation */

We are specifying additional header sizes because of the additional headers our ARP packet has when capturing from a wireless source due to RFC 1042 IP encapsulation, as shown in Figure 10-4.

Figure 10-4. ARP packet format on 802.11 from an AVS capture source

ARP packet format on 802.11 from an AVS capture source

To determine the type of packet embedded in the 802.11 packet, we need to have a definition for the LLC header so that we can extract the ether_type value:

/* SNAP LLC header format */
struct snap_header           
{
  u_int8_t dsap; 
  u_int8_t ssap;
  u_int8_t ctl;
  u_int16_t org; 
  u_int8_t org2;
  u_int16_t ether_type;          /* ethernet type */              
} _ _attribute_ _ ((_ _packed_  _));

Now we can alter the process_packet function to work with a captured 802.11 packet from an AVS wireless source:

/* callback function to process a packet when captured */
void
process_packet (u_char * args, const struct pcap_pkthdr *header,
    const u_char * packet)
{
  struct ether_header *eth_header;  /* in ethernet.h included by if_eth.h */
  struct snap_header *llc_header;   /* RFC 1042 encapsulation header */
  struct ether_arp *arp_packet;     /* from if_eth.h */

  if (wired)     /* global flag - wired or wireless? */
  {
    eth_header = (struct ether_header *) packet;
    arp_packet = (struct ether_arp *) (packet + ETH_HEADER_SIZE);
    if (ntohs (eth_header->ether_type) != ETHERTYPE_ARP) return;
  } else {      /* wireless */
    llc_header = (struct snap_header *) 
          (packet + AVS_HEADER_SIZE + DATA_80211_FRAME_SIZE);
    arp_packet = (struct ether_arp *) 
          (packet + AVS_HEADER_SIZE + DATA_80211_FRAME_SIZE + LLC_HEADER_SIZE);
    if (ntohs (llc_header->ether_type) != ETHERTYPE_ARP) return;
  }

  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]);
}

You might have noticed that we have introduced a global flag called wired which we are going to use to determine a packet's framing type. We will set this further down in Arpsniff when we check the link type using pcap_datalink:

  /* find out the datalink type of the connection */
  if (pcap_datalink (handle) == DLT_EN10MB)
  {
    wired = 1;     /* ethernet link */
  } else {
    if (pcap_datalink (handle) == DLT_IEEE802_11_RADIO_AVS)
    {
      wired = 0;  /* wireless */
    } else {
      fprintf (stderr, "I don't support this interface type!\n");
      exit (1);
    }
  }

Once we have made the preceding changes, Arpsniff is ready to capture ARP packets from a wireless network interface in monitor mode. The full source code for the updated version of Arpsniff is included in Example 10-4.

Example 10-4. Arpsniff2 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
#define AVS_HEADER_SIZE 64                 /* AVS capture header size */
#define DATA_80211_FRAME_SIZE 24           /* header for 802.11 data packet */
#define LLC_HEADER_SIZE 8                  /* LLC frame for encapsulation */

/* SNAP LLC header format */
struct snap_header           
{
  u_int8_t dsap; 
  u_int8_t ssap;
  u_int8_t ctl;
  u_int16_t org; 
  u_int8_t org2;
  u_int16_t ether_type;          /* ethernet type */              
} __attribute__ ((__packed_  _));

/* 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 */
int wired=0;        /* flag for wired/wireless */

/* 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 * args, const struct pcap_pkthdr *header,
    const u_char * packet)
{
  struct ether_header *eth_header;  /* in ethernet.h included by if_eth.h */
  struct snap_header *llc_header;   /* RFC 1042 encapsulation header */
  struct ether_arp *arp_packet;     /* from if_eth.h */

  if (wired)     /* global flag - wired or wireless? */
  {
    eth_header = (struct ether_header *) packet;
    arp_packet = (struct ether_arp *) (packet + ETH_HEADER_SIZE);
    if (ntohs (eth_header->ether_type) != ETHERTYPE_ARP) return;
  } else {      /* wireless */
    llc_header = (struct snap_header *) 
          (packet + AVS_HEADER_SIZE + DATA_80211_FRAME_SIZE);
    arp_packet = (struct ether_arp *) 
          (packet + AVS_HEADER_SIZE + DATA_80211_FRAME_SIZE + LLC_HEADER_SIZE);
    if (ntohs (llc_header->ether_type) != ETHERTYPE_ARP) return;
  }

  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)
  {
    wired = 1;     /* ethernet link */
  } else {
    if (pcap_datalink (handle) == DLT_IEEE802_11_RADIO_AVS)
    {
      wired = 0;  /* wireless */
    } else {
      fprintf (stderr, "I don't support this interface type!\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);
}

Warning

If the wireless network is using encryption, we are not going to be able to intercept all traffic in a readable format. Unfortunately, we cannot be in monitor mode and have the wireless card decrypting data for us, so any data requiring decryption should be captured while not in monitor mode, or else the tool will have to implement decryption for the captured data.

libpcap and Perl

The libpcap examples we have demonstrated so far have been in C, as the libpcap library is a C library. However, many interfaces and wrappers to libpcap exist for higher-level languages, such as Perl and Python. Using a high-level language has a number of advantages for developers not familiar with C, and for developers looking to quickly throw together a tool that works, without necessarily requiring it to be robust, scalable, or even stable. A tool written in Perl or Python is generally a lot smaller than an equivalent tool written in C.

Using a high-level language can also have some disadvantages, in that less commonly used functionality and new functionality within libpcap might not be supported properly, or even at all. Also, high-level languages are not a realistic option for tools requiring high throughput of packet processing, so you would not want to write a network IDS in a high-level language.

For the Perl scripting language, the package for libpcap is called Net::Pcap . If a Net::Pcap package is not available for your Linux distribution, you should be able to install Net::Pcap as follows:

               perl -MCPAN -e 'install Net::Pcap'

This downloads the source code for the package and automatically builds the package in most cases. If you are using Windows, your install process might be different. The functions in Net::Pcap are very similar to the C functions, as are the parameters passed to the functions. Documentation of these functions is available on the module's home page, at http://search.cpan.org/~kcarnut/Net-Pcap-0.05/Pcap.pm.

Arpsniff in Perl

The following is a quick demonstration of Net::Pcap functionality and a quick reimplementation of the major functionality of the Arpsniff tool in Perl. Note that this tool also uses the NetPacket::Ethernet and NetPacket::ARP packages to easily decompose the packets it captures:

#!/usr/bin/env perl

use Net::Pcap;
use NetPacket::Ethernet;
use NetPacket::ARP;

my $errbuf;

# find a network device
$device = Net::Pcap::lookupdev(\$errbuf);
if (defined $errbuf) {die "Unable to find device: ", $errbuf;}

# open device
$handle = Net::Pcap::open_live($device, 2000, 1, 0, \$errbuf);
if (!defined $handle) {die "Unable to open ",$device, " - ", $errbuf;}

# find netmask so we can set a filter on the interface
Net::Pcap::lookupnet(\$device, \$netp, \$maskp, \$errbuf) 
    || die "Can't find network info"; 

# set filter on interface
$filter = "arp";
Net::Pcap::compile($handle, \$fp, $filter, 0, $maskp) 
    && die "Unable to compile BPF";
Net::Pcap::setfilter($handle, $fp) && die "Unable to set filter";

# start sniffing
Net::Pcap::loop($handle, -1, \&process_packet, '') 
    || die "Unable to start sniffing";

# close
Net::Pcap::close($handle);

sub process_packet
{
  my ($user, $header, $packet) = @_;

  my $eth_data = NetPacket::Ethernet::strip($packet);
    
  my $arp = NetPacket::ARP->decode($eth_data);

  # convert hex number to IP dotted - from rob_au at perlmonks
  my $spa = join '.', map { hex } ($arp->{'spa'} =~ /([[:xdigit:]]{2})/g);
  my $tpa = join '.', map { hex } ($arp->{'tpa'} =~ /([[:xdigit:]]{2})/g);

  print "Source: ",$spa,"\tDestination: ",$tpa, "\n";
}

libpcap Library Reference

Although the Arpsniff tool demonstrates a majority of the functionality that most tools require, the libpcap library has a lot of functionality we have not yet explored. This section provides a high-level reference, by functionality type, to all the functionality present in libpcap.

Lookup Functions

The following functions are used to provide information about available interfaces.

pcap_lookupdev

Prototype: char *pcap_lookupdev(char *errbuf)

Synopsis

Purpose: pcap_lookupdev finds the first usable interface (active and supported by libpcap) for use with pcap_open_live and pcap_lookup_net returned by the operating system. The function returns a string containing the device's name if successful. If not successful, the function returns NULL and errbuf contains a human-readable error message. pcap_lookupdev is not recommended in situations where multiple network interfaces are in use. Note that if the calling user does not have appropriate privileges, this function might not return a device even though usable devices are present. You can find an example of using pcap_lookupdev in the Section 10.2.2 section earlier in this chapter.


pcap_findalldevs

Prototype: int pcap_findalldevs(pcap_if_t **alldevsp, char *errbuf)

Synopsis

Purpose: pcap_findalldevs finds all usable (active and supported by libpcap) interfaces for use with pcap_open_live. If successful, the function returns 0 and alldevsp points to a linked list of pcap_if_t structures with interface details. If not successful, the function returns -1 and errbuf is populated with a human-readable error message. You can find an example of utilizing the information returned by pcap_findalldevs in the Section 10.2.2 section earlier in this chapter.


pcap_lookupnet

Prototype: int pcap_lookupnet(const char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf)

Synopsis

Purpose: pcap_lookupnet returns the network address and network mask of the device supplied in the device parameter. The function returns 0 if successful and netp and maskp point to the network interface address and netmask, respectively. If an error occurs, pcap_lookupnet returns -1, and errbuf is populated with a human-readable error message. You can find an example of using the pcap_lookupnet function in the Section 10.2.4 section earlier in this chapter.


pcap_freealldevs

Prototype: void pcap_freealldevs(pcap_if_t *alldevs)

Synopsis

Purpose: pcap_freealldevs frees a linked list of interface information returned by the pcap_findalldevs function.


Packet-Capture Functions

The following are functions for capturing packets and manipulating live capture sources.

pcap_open_live

Prototype: pcap_t *pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *errbuf)

Synopsis

Purpose: pcap_open_live is used to open a live packet-capturing session from the network interface device (for example, eth0 on Linux, or le0 on a Sun Sparc). device can be NULL or any on recent Linux systems, in which case all interfaces are used for packet capture. snaplen specifies the length in bytes to be captured. If you want to capture the entire packet, set this to more than the packet size, including headers for the link type you are capturing from (65,535 should be sufficient). promisc should be 1 if the interface should be put into promiscuous mode or 0 otherwise. When a network interface is in promiscuous mode, and promiscuous mode is supported by the underlying data link layer, it captures all traffic on the network, regardless of whether it is intended for the host running the packet capture. This does not have any effect if the device is set to NULL or any. The parameter to_ms specifies a read timeout in milliseconds for when read operations should not necessarily return immediately when a packet is seen, therefore allowing us to capture multiple packets in one read operation. to_ms is not supported on all platforms (for unsupported systems, this value is ignored), and is useful mostly if you're going to use pcap_dispatch in nonblocking mode. A value of 0 causes libpcap to wait until packets arrive. pcap_open_live returns NULL is the open fails, and errbuf is set to a human-readable error message. errbuf should be of at least PCAP_ERRBUF_SIZE size. pcap_open_live can also return a warning message in errbuf on a successful call. errbuf should be checked after the successful call to determine if errbuf is still a zero length string, and any warnings should be returned to the user. You can find an example of using the pcap_open_live function in the Section 10.2.3 section earlier in this chapter.


pcap_next

Prototype: const u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)

Synopsis

Purpose: pcap_next reads the next packet available on the buffer. This is a wrapper to pcap_dispatch called with a cnt of 1. If successful, pcap_next returns a pointer to the captured packet. If the read was not successful for any reason, no packet was available due to a timeout, or no packets passed a filter, pcap_next returns NULL. No packet header information is returned for this function. Because no error messages are returned by pcap_next, it is more suited to simple uses and for reading from dump files. Use pcap_next_ex if you need error handling.


pcap_next_ex

Prototype: int pcap_next_ex(pcap_t *p, struct pcap_pkthdr **pkt_header, const u_char **pkt_data)

Synopsis

Purpose: pcap_next_ex returns the next packet available on the buffer. If successful, the function returns 1, and pkt_header and pkt_data point to the captured packet's libpcap capture information header and the packet, respectively. If not successful, the function returns 0 if the timeout expired, -1 if an error occurred reading the packet, or -2 if the packet is being read from a saved file and there are no more packets to read.


pcap_loop

Prototype: int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)

Synopsis

Purpose: pcap_loop enters a loop for processing cnt packets from the opened capture (live or saved file). Unlike pcap_dispatch, pcap_loop does not observe read timeouts. If cnt is set to a negative number, the loop continues forever. The function specified by callback is the name of a function of the prototype void callback (u_char *user, const struct pcap_pkthdr *header, const u_char* packet). This function is called for each packet captured. The user parameter is a user-specifiable value that is passed to the callback function when it is invoked, and can be NULL. The function pcap_loop returns 0 if cnt packets were successfully read, -1 on an error, and -2 if a call to pcap_breakloop occurred before packets have been captured. If an error has occurred, you can use pcap_perror( ) or pcap_geterr( ) to obtain the error message. You can find an example of using the pcap_loop function in the Section 10.2.5 section earlier in this chapter.


pcap_dispatch

Prototype: int pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user)

Synopsis

Purpose: pcap_dispatch captures and processes packets while observing read timeouts specified in pcap_open_live. The cnt parameter specifies the maximum number of packets that are to be processed. When reading from a live capture, 0 up to cnt packets can be processed depending on the status of the buffer. A cnt value of -1 processes all packets in the buffer or the entire file, if used on a saved file. The function specified by callback is the name of a function of the prototype void callback (u_char *user, const struct pcap_pkthdr *header, const u_char* packet). This function is called for each packet. The user parameter is a user-specifiable value that is passed to the callback function when it is invoked, and can be NULL. The function returns the number of packets processed if successful. 0 is returned if no packets were read (due to a read timeout, or if in nonblocking mode and no packets were available to be read), -1 if an error occurred, or -2 if a call to pcap_breakloop was made before any packets were captured. If an error has occurred, you can use pcap_perror( ) or pcap_geterr( ) to obtain the error message. Note that because of the way pcap_dispatch behaves with different platforms, it might not necessarily return immediately after a read timeout.


pcap_setnonblock

Prototype: int pcap_setnonblock(pcap_t *p, int nonblock, char *errbuf)

Synopsis

Purpose: pcap_setnonblock allows you to set the status of a live capture as blocking or nonblocking. The nonblock parameter should be 1 to set the status to nonblocking and 0 to set the status to blocking (default). pcap_setnonblock is intended for use with pcap_dispatch, and when the live capture is set to nonblocking, pcap_dispatch returns immediately if no packets are available for processing, without observing any read timeouts. If the live capture is set to block, the capture waits for packets to arrive. pcap_setnonblock returns 0 on success and -1 on an error with a human-readable error message returned in errbuf.


pcap_getnonblock

Prototype: int pcap_getnonblock(pcap_t *p, char *errbuf)

Synopsis

Purpose: pcap_getnonblock returns the current blocking status of 1 (nonblocking) or 0 (blocking; the default). If an error occurs, the function returns -1 with the errbuf containing a human-readable error message. pcap_getnonblock always returns 0 on saved files.


pcap_set_datalink

Prototype: int pcap_set_datalink(pcap_t *p, int dlt)

Synopsis

Purpose: pcap_set_datalink sets the data link type on the underlying data link layer to the value in dlt (refer to Table 10-2 for example data link types) where the underlying data link layer supports multiple link types. Support for multiple link types is not available on all link types or platforms. You can obtain a list of all possible data link types supported for an interface using pcap_list_datalinks. The function returns 0 on success and -1 on failure.


pcap_compile

Prototype: int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, intoptimize, bpf_u_int32 netmask)

Synopsis

Purpose: pcap_compile is used to compile the tcpdump format filter string str into a BPF filter fp. optimize controls whether the resulting code is optimized (value 1) or not (value 0). netmask is the IPv4 netmask of the network interface being used for capture. This value can be obtained using pcap_lookupnet, or it can be 0, in which case some tests for IPv4 broadcast addresses will not work correctly. pcap_compile returns 0 on success, -1 on error. You can find an example of using pcap_compile in the Section 10.2.4 section earlier in this chapter.


pcap_compile_nopcap

Prototype: int pcap_compile_nopcap(int snaplen, int linktype, struct bpf_program *fp,char *str, int optimize, bpf_u_int32 netmask)

Synopsis

Purpose: pcap_compile_nopcap is a wrapper to pcap_compile that allows us to compile BPF filters without a pcap_t structure. snaplen and linktype specify the capture length and the link type (as per Table 10-2) and are used as arguments to pcap_open_dead. pcap_compile_nopcap returns 0 on success and -1 on error.


pcap_setfilter

Prototype: int pcap_setfilter(pcap_t *p, struct bpf_program *fp)

Synopsis

Purpose: pcap_setfilter is used to set a compiled BPF filter on a capture session. fp contains the compiled BPF program as generated by pcap_compile. pcap_setfilter returns 0 on success and -1 on error. You can find an example of using pcap_setfilter in the Section 10.2.4 section earlier in this chapter.


pcap_freecode

Prototype: void pcap_freecode(struct bpf_program *fp)

Synopsis

Purpose: pcap_freecode is used to free the memory used by a compiled BPF filter. You can find an example of using pcap_freecode in the Section 10.2.4 section earlier in this chapter.


pcap_breakloop

Prototype: void pcap_breakloop(pcap_t *)

Synopsis

Purpose: pcap_breakloop is used to signal pcap_dispatch or pcap_loop (and pcap_next on some platforms) to exit. A flag is set that is checked within many of the functions, and therefore pcap_dispatch and pcap_loop might not exit immediately but can capture up to one more packet.


pcap_fileno

Prototype: int pcap_fileno(pcap_t *p)

Synopsis

Purpose: pcap_fileno returns the file descriptor for the capture session. This returns the handle for when a live capture is in progress and -1 if the session is using a saved file.


pcap_close

Prototype: void pcap_close(pcap_t *p)

Synopsis

Purpose: pcap_close closes the open session and any associated file handles.


pcap_open_dead

Prototype: pcap_t *pcap_open_dead(int linktype, int snaplen)

Synopsis

Purpose: pcap_open_dead is used to create a pcap_t handle, without opening a live capture or saved file. This is commonly used for compiling BPF code. linktype and snaplen specify the link type as per Table 10-2 and the capture length.


Save and Dump File Functions

The following functions are for saving and reading packets from files on disk.

pcap_open_offline

Prototype: pcap_t *pcap_open_offline(const char *fname, char *errbuf)

Synopsis

Purpose: pcap_open_offline is used to open a libpcap-format saved file as a packet source. The fname string holds a filename appropriate for the underlying platform, and can be "-" to denote STDIN. On success a pcap_t handle is returned that can be used to return packets using any of the libpcap functions for capturing packets (such as pcap_next_ex). On error, the function returns NULL, and errbuf is populated with an appropriate human-readable error message.


pcap_dump_open

Prototype: pcap_dumper_t *pcap_dump_open(pcap_t *p, const char *fname)

Synopsis

Purpose: pcap_dump_open is used to open a file for saving packets to a disk file. The fname string holds an appropriate filename for the underlying platform, or can be "-" to denote STDOUT. pcap_dump_open returns a pcap_dumper_t handle on success that can used for calling pcap_dump, or NULL on error.


pcap_dump

Prototype: void pcap_dump(u_char *user, struct pcap_pkthdr *h, u_char *sp)

Synopsis

Purpose: pcap_dump writes a packet with the libpcap packet header h, and the packet body sp, to the saved file opened with pcap_dump_open. If called directly, the user value should be the pcap_dumper_t handle opened by pcap_dump_open. pcap_dump can also be called from pcap_loop or pcap_dispatch to dump captured packets directly to a file.


pcap_dump_close

Prototype: void pcap_dump_close(pcap_dumper_t *p)

Synopsis

Purpose: pcap_dump_close closes the saved file associated with the p handle.


pcap_dump_flush

Prototype: int pcap_dump_flush(pcap_dumper_t *p)

Synopsis

Purpose: pcap_dump_flush is used to flush the file output buffer to disk therefore writing any packets output on dump session p using pcap_dump, but not yet written to disk. pcap_dump_flush returns 0 on success, or -1 on error.


pcap_major_version

Prototype: int pcap_major_version(pcap_t *p)

Synopsis

Purpose: pcap_major_version returns the major version of the libpcap library used to write a saved file opened with pcap_open_offline.


pcap_minor_version

Prototype: int pcap_minor_version(pcap_t *p)

Synopsis

Purpose: pcap_minor_version returns the minor version of the libpcap library used to write a saved file opened with pcap_open_offline.


pcap_file

Prototype: FILE *pcap_file(pcap_t *p)

Synopsis

Purpose: pcap_file returns the file handle to a saved file opened with pcap_open_offline. pcap_file returns the handle on success, or NULL if the pcap_t handle p relates to a live capture opened with pcap_open_live.


pcap_is_swapped

Prototype: int pcap_is_swapped(pcap_t *p)

Synopsis

Purpose: pcap_is_swapped returns 1 if the saved file referred to by p is in a different byte order than the byte order used in the underlying platform, or 0 if it is the same. If the saved file is in a different byte order, most platforms provide the ntohs( ) and ntohl() functions for converting network order to host order (i.e., big endian to little endian) and/or htons() and htonl( ) for converting host order to network order (i.e., little endian to big endian).


Status Functions

These functions are used to interrogate interfaces for information.

pcap_datalink

Prototype: int pcap_datalink(pcap_t *p)

Synopsis

Purpose: pcap_datalink returns the type of the underlying data link layer of a session. This can be compared to the predefined list of values included in Table 10-2, or converted to a human-readable string using pcap_datalink_val_to_name or pcap_datalink_val_to_description. You can find an example of using pcap_datalink in the Section 10.2.4 section earlier in this chapter.


pcap_list_datalinks

Prototype: int pcap_list_datalinks(pcap_t *p, int **dlt_buf);

Synopsis

Purpose: pcap_list_datalinks lists all data link types supported by a capture device. Where multiple types are supported, a particular data link type can be selected with pcap_set_datalink. pcap_list_datalinks returns the number of supported data links on success, with dlt_buf pointing to an array of data link type values. The function returns -1 on error. The data link type values can be converted to human-readable link types (such as the values in Table 10-2) using the pcap_datalink_val_to_name or pcap_datalink_val_to_description functions. You can find an example of using pcap_list_datalinks in the Section 10.2.4 section earlier in this chapter.


pcap_snapshot

Prototype: int pcap_snapshot(pcap_t *p)

Synopsis

Purpose: pcap_snapshot returns the number of bytes captured per packet (snapshot length) of the opened session. This is the value specified at the pcap_open_live call to open the interface.


pcap_stats

Prototype: int pcap_stats(pcap_t *p, struct pcap_stat *ps)

Synopsis

Purpose: pcap_stats is used to return capture statistics for all packets captured since the start of the capture. pcap_stats is relevant only for live captures because statistics are not stored in saved files. The pcap_stat structure returned contains the members ps_recv (number of packets received), ps_drop (number of packets dropped), ps_ifdrop (number of packets dropped by the interface; this is not supported on all platforms), and, on Windows platforms, bs_capt (number of packets reaching the application). Exactly what is measured for packets received and dropped depends on the platform. For example, when using BPF filters, some platforms count all packets received, while others count only the packets passing the filter. pcap_stats returns -1 on error or when statistics are not supported and it returns 0 on success.


pcap_lib_version

Prototype: const char *pcap_lib_version(void)

Synopsis

Purpose: pcap_lib_version returns a string containing the description of the libpcap version in use. For libpcap version 0.8.3 this is something such as libpcap version 0.8.3.


pcap_datalink_name_to_val

Prototype: int pcap_datalink_name_to_val(const char *name);

Synopsis

Purpose: pcap_datalink_name_to_val returns the numeric value of a data link type when supplied as a string. The string name is a data link type, minus the DLT_, as described in Table 10-2. The numeric value is returned on success, or -1 on error.


pcap_datalink_val_to_name

Prototype: const char *pcap_datalink_val_to_name(int dlt);

Synopsis

Purpose: pcap_datalink_val_to_name returns the data link name as per Table 10-2 when supplied with the numeric value in dlt. This returns the name on success, or NULL on error. You can find an example of using pcap_datalink_val_to_name in the Section 10.2.4 section earlier in this chapter.


pcap_datalink_val_to_description

Prototype: const char *pcap_datalink_val_to_description(int dlt)

Synopsis

Purpose: pcap_datalink_val_to_description returns a short text description when supplied with the numeric value in dlt. This description is contained in the array dlt_choices in pcap.c of the libpcap source code. This returns the name on success, or NULL on error. You can find an example of using pcap_datalink_val_to_description in the Section 10.2.4 section earlier in this chapter.


Error-Handling Functions

libpcap supplies three functions for determining and reporting errors, as shown in the following.

pcap_geterr

Prototype: char *pcap_geterr(pcap_t *p)

Synopsis

Purpose: pcap_geterr returns the error text for the last libpcap error that has occurred. This requires that the pcap_t handle p has not been closed using pcap_close. pcap_geterr is used to obtain human-readable error messages for all libpcap functions that do not supply this ability through the use of an errbuf parameter.


pcap_strerror

Prototype: char *pcap_strerror(int error)

Synopsis

Purpose: pcap_strerror is an implementation of strerror(1) for platforms that do not have their own implementation. pcap_strerror returns an operating system error message for a given error code.


pcap_perror

Prototype: void pcap_perror(pcap_t *p, char *prefix)

Synopsis

Purpose: pcap_perror prints the last libpcap error message to STDERR in human-readable format, prefixed by the string prefix .


Notes

  1. If you're familiar with the netlink(3) interface you know how old this code really is.
Personal tools