Network programming in Linux

Filtering captured datagrams


Introduction
Getting started with libpcap

Extracting Ethernet information
Internet Protocol (IP)

Filtering captured datagrams
Capturing datagrams offline

Address Resolution Protocol (ARP)
Internet Control Message Protocol (ICMP)
Transmission Control Protocol (TCP)
User Datagram Protocol (UDP)
Trivial File Transfer Protocol (TFTP)

Injecting datagrams with libnet
Implementing ping
Implementing traceroute

Download source code


Often times the user may only be interested in specific traffic. For instance, he may only be interested in traffic directed to a specific host. Or maybe he only wants to capture fragmented IP packets in order to detected particular attacks involving improperly fragmented packet headers. In such scenarios, blindly capturing all traffic is not appropriate since the datagrams of interest will likely be submerged into waves of non pertinent traffic.

libpcap provides filtering capabilities exclusively to pass relevant datagrams to the callback function. These filters are based on a declarative predicate syntax. A filter is a string containing a filtering expression, also known as BPF for Berkeley Packet Filters. A filtering expression selects which packets will be passed on to the callback function for processing. If no expression is given, all packets on the net will be accepted by the kernel-level filtering engine. Otherwise, only packets for which the filtering expression is "true" will be accepted.

Here are examples of filtering expressions that may be used with libpcap to filter captured datagrams:

Example Result
tcp port 80 Captures datagrams on TCP port 80.
port 80 Captures datagrams on TCP or UDP port 80.
ip Captures IPv4 packets.
tcp Captures TCP segments.
dst #.#.#.# Captures datagrams destined to the IPv4 address specified, where #.#.#.# is a valid IPv4 address.
src #.#.#.# Captures datagrams originating from the IPv4 address specified, where #.#.#.# is a valid IPv4 address.
port 80 or port 8080 Captures all traffic on (TCP or UDP) ports 80 or 8080.
src #.#.#.# and dst #.#.#.# Captures all datagrams with the source and destination IP addresses specified, where each #.#.#.# represents a valid IP address.
tcp port 80 or port 8080 and dst #.#.#.# and src #.#.#.# This example shows how to specify multiple parameters to create a filter that captures on TCP port 80, or on TCP or UDP port 8080, and on the destination and source ports, where each #.#.#.# represents a valid IP address.

For more information about BPF expressions, visit http://www.tcpdump.org/tcpdump_man.html.

libpcap provides two functions for managing filtering expressions:

  1. pcap_compile() is used to compile a string based filtering expression into a filtering program for the kernel-level packet filter.

  2. pcap_setfilter() applies a compiled filtering program to the capture process.

Before applying a filter, it must be compiled with pcap_compile(). Its prototype is

	int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int opt, bpf_u_int32 netmask)

and its parameters are

  1. p: the libpcap session handle returned by pcap_open_live().

  2. fp: a reference in which will be stored the compiled filter.

  3. str: a string containing the BPF expression to compile.

  4. opt: should the expression be optimized (opt = 1) or not (opt = 0).

  5. netmask: IP mask of the network on which the filter is to be applied.

pap_compile() returns -1 on failure; any other return value indicates success, i.e. the filtering expression in str has been compiled into fp.

Once the filtering expression has been compiled, it can be applied to the libpcap capture session with pcap_setfilter(). This function's prototype is

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

The parameters here are straightforward: the first one is the session handle and the second is the compiled filter.

Now let's add datagram filtering in our sniffer. All work is accomplished in the main program, including adding a new command line argument (-f) to provide a filtering expression:

sniff07.cpp (partial)


034
035
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058



097
098
099
100
101
102
103
104
105
106



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
...

pcap_t      *pcap_session = NULL;   // libpcap session handle

char        *strfilter = NULL;      // textual BPF filter
bpf_program  binfilter;             // compiled BPF filter program

// Function releasing all resources before ending program execution
void shutdown(int error_code) {
  // Destroy compiled BPF filter if need be
  if (strfilter != NULL)
      pcap_freecode(&binfilter);

  // Close libpcap session
  if (pcap_session != NULL)
    pcap_close(pcap_session);

  exit(error_code); // we're done!
}

// Ctrl+C interrupt handler
void bypass_sigint(int sig_no) {
  cout << endl << "*** Capture process interrupted by user..." << endl;

  shutdown(0); // we're done!
}

  ...
  
  // Process command line arguments
  while ((argch = getopt(argc, argv, "hprd:f:n:")) != EOF)
    switch (argch) {
      case 'd':           // device name
        device = optarg;
        break;

      case 'f':           // BPF filter
        strfilter = optarg;
        break;

  ...
  
  // Open a libpcap capture session
  pcap_session = pcap_open_live(device, siz, promisc, 1000, errbuf);
  if (pcap_session == NULL) {
    cerr << "error - pcap_open_live() failed (" << errbuf << ")" << endl;
    return -4;
  }

  // Compile BPF filter expression into program if one provided
  if (strfilter != NULL) {
    // Compile filter expression
    if (pcap_compile(pcap_session, &binfilter, strfilter, 1, maskp) < 0) {
      cerr << "error - pcap_compile() failed (" << pcap_geterr(pcap_session) << ")" << endl;
      shutdown(-5);    // Cleanup and quit
    }

    // Install compiled filter
    if (pcap_setfilter(pcap_session, &binfilter) < 0) {
      cerr << "error - pcap_setfilter() failed (" << pcap_geterr(pcap_session) << ")" << endl;
      shutdown(-6);    // Cleanup and quit
    }
    
    cout << "BPF filter = " << strfilter << endl;    // display applied filter
  }

  // Start capturing...
  pcap_loop(pcap_session, cnt, process_packet, NULL);
  
  ...

Here are the enhancements implementing packet filtering in the sniffer:

  • Lines #037 and #038 declare variables for handling a filter, both in string and binary (i.e. compiled) forms.

  • Since the enhanced code requires some cleanup upon termination, such as releasing resources, we add a new function to the program, shutdown(), which performs all necessary cleanup before explicitly ending execution. Whenever the program's execution needs to be terminated (such as at line #057), the shutdown() routine is called to end execution gracefully.

  • A new command line argument, -f, is added in the main() routine to accept a BPF filter.

  • The block of code at line #175 in the main() routine checks whether a BPF string has been provided and, if so, compiles it into binary form (line #177) then attaches it to the libpcap session (line #183).

Here we limit capture to ICMP or ARP datagrams using filtering:

%root> ./sniff07 -n 1 -f 'arp or icmp'
device = eth0
network ip = 172.16.179.0
network mask = 255.255.255.0
BPF filter = arp or icmp
Grabbed 98 bytes (100%) of datagram received on Mon Sep  9 15:27:08 2013
---------- Ethernet frame header ----------
destination MAC address = 00.50.56.ec.28.a3
source MAC address = 00.0c.29.19.22.3a
ether type = IPv4 [0x0800]
-------- IP packet header --------
version = IPv4
header length = 20 (IHL = 5)
type of service = 0:
total length = 84
fragment ID = 0x0000
  don't fragment = 2
  more fragments = 0
  fragment position = 0
protocol = ICMP [0x01]
time to live = 64
checksum = 0xca72
destination IP address = 24.200.247.217
source IP address = 172.16.179.132
%root> 

The captured datagram is the result of an ICMP Echo Request. Notice the BPF filter given within single quotes (-f 'arp or icmp') to force getopt() to consider 'arp or icmp' as a single command line argument value.


Home  |  Previous  |  Next

 
Copyright © 2014 Marco Lavoie