Jan 2009

Using Libpcap Part 3: Full Source and Final Touches

In the previous two texts reading and manipulating packets with libpcap 1 and 2; a simple packet reader and handler was constructed leveraging the libpcap library. In this the final part of the series the full source from the examples is provided along with a simple Makefile plus a small bonus, a rudimentry packet injector.

Makefile

Note that the injector target is included:

CC=gcc
LIBS=-lpcap
BINS=nject nread

all: Linux

Linux linux:
        ${CC} -DLINUX nject.c ${LIBS} -o nject
        ${CC} -DLINUX nread.c ${LIBS} -o nread

osx osX OSX FreeBSD freebsd:
        ${CC} -DFREEBSD nject.c ${LIBS} -o nject
        ${CC} -DFREEBSD nread.c ${LIBS} -o nread

clean:
        rm -f a.out ${BINS}

pkt.h

#ifndef _PKT_H
#define _PKT_H

#define _BSD_SOURCE 1

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <arpa/inet.h>
#include <net/ethernet.h>

#ifdef LINUX
#include <netinet/ether.h>
#endif

#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>

#include <fcntl.h>
#include <getopt.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <pcap.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

struct nread_ip {
    u_int8_t        ip_vhl;          /* header length, version    */
#define IP_V(ip)    (((ip)->ip_vhl & 0xf0) >> 4)
#define IP_HL(ip)   ((ip)->ip_vhl & 0x0f)
    u_int8_t        ip_tos;          /* type of service           */
    u_int16_t       ip_len;          /* total length              */
    u_int16_t       ip_id;           /* identification            */
    u_int16_t       ip_off;          /* fragment offset field     */
#define IP_DF 0x4000                 /* dont fragment flag        */
#define IP_MF 0x2000                 /* more fragments flag       */
#define IP_OFFMASK 0x1fff            /* mask for fragmenting bits */
    u_int8_t        ip_ttl;          /* time to live              */
    u_int8_t        ip_p;            /* protocol                  */
    u_int16_t       ip_sum;          /* checksum                  */
    struct  in_addr ip_src, ip_dst;  /* source and dest address   */
};

struct nread_tcp {
    u_short th_sport; /* source port            */
    u_short th_dport; /* destination port       */
    tcp_seq th_seq;   /* sequence number        */
    tcp_seq th_ack;   /* acknowledgement number */
#if BYTE_ORDER == LITTLE_ENDIAN
    u_int th_x2:4,    /* (unused)    */
    th_off:4;         /* data offset */
#endif
#if BYTE_ORDER == BIG_ENDIAN
    u_int th_off:4,   /* data offset */
    th_x2:4;          /* (unused)    */
#endif
    u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
    u_short th_win; /* window */
    u_short th_sum; /* checksum */
    u_short th_urp; /* urgent pointer */
};
#endif

nread.c

#include "pkt.h"

static short int eflag;  /* Ethernet flag    */
static short int vflag;  /* Verbosity flag   */

/*
 * copy_argv - Copy the rest of an argument string into a new buffer for
 *             processing.
 */
char * copy_argv (char **argv)
{
    char **p;
    u_int len = 0;
    char *buf;
    char *src, *dst;

    p = argv;

    if (*p == 0)
        return 0;

    while (*p)
        len += strlen(*p++) + 1;

    buf = (char *)malloc(len);
    if (buf == NULL) {
        fprintf(stdout,"copy_argv: malloc");
        exit (1);
    }

    p = argv;
    dst = buf;
    while ((src = *p++) != NULL) {
        while ((*dst++ = *src++) != '\0')
            ;
        dst[-1] = ' ';
    }
    dst[-1] = '\0';

    return buf;
}

/* Ethernet Handler */
u_int16_t ethernet_handler (u_char *args, const struct pcap_pkthdr* pkthdr,
                                              const u_char* packet)

{
  u_int caplen = pkthdr->caplen; /* length of portion present from bpf  */
  u_int length = pkthdr->len;    /* length of this packet off the wire  */
  struct ether_header *eptr;     /* net/ethernet.h                      */
  u_short ether_type;            /* the type of packet (we return this) */
  eptr = (struct ether_header *) packet;
  ether_type = ntohs(eptr->ether_type);

  if (caplen < 14) {
    fprintf(stderr,"Packet length is less than header length\n");
    return -1;
  }

  if (eflag) {
    fprintf(stdout,"eth: ");
    fprintf(stdout,
      "%s ",ether_ntoa((struct ether_addr*)eptr->ether_shost));
    fprintf(stdout,
       "%s ",ether_ntoa((struct ether_addr*)eptr->ether_dhost));

    /* get type and use as the beginning of the message line */
    if (ether_type == ETHERTYPE_IP) {
        fprintf(stdout,"(ip)");
    } else  if (ether_type == ETHERTYPE_ARP) {
        fprintf(stdout,"(arp)");
    } else  if (eptr->ether_type == ETHERTYPE_REVARP) {
        fprintf(stdout,"(rarp)");
    } else {
        fprintf(stdout,"(?)");
    }
  }

  return ether_type;
}

/* IP Handler */
u_char* ip_handler (u_char *args,const struct pcap_pkthdr* pkthdr,
                                             const u_char* packet)
{
    const struct nread_ip* ip;   /* packet structure         */ 
    const struct nread_tcp* tcp; /* tcp structure            */
    u_int length = pkthdr->len;  /* packet header length  */
    u_int hlen, off, version;       /* offset, version       */
    int len;                        /* length holder         */

    ip = (struct nread_ip*)(packet + sizeof(struct ether_header));
    length -= sizeof(struct ether_header);
    tcp = (struct nread_tcp*)(packet + sizeof(struct ether_header) +
                        sizeof(struct nread_ip));
   
        hlen    = IP_HL(ip);         /* get header length */ 
    len     = ntohs(ip->ip_len); /* get packer length */
    version = IP_V(ip);          /* get ip version    */

    if (hlen < 5 ) {
        fprintf(stderr,"Alert: %s bad header length %d\n",
                 inet_ntoa(ip->ip);
    }

    if (length < len)
       fprintf(stderr,"Alert: %s truncated %d bytes missing.\n");

    off = ntohs(ip->ip_off);

        if ((off & 0x1fff) == 0 ) { /* aka no 1's in first 13 bits */
        if (vflag > 3)
            fprintf(stdout,"ip: ");

        if (vflag > 0)
            fprintf(stdout,"%s:%u->%s:%u ",
                    inet_ntoa(ip->ip_src), tcp->th_sport,
                    inet_ntoa(ip->ip_dst), tcp->th_dport);

        if (vflag > 1)
            fprintf(stdout,
                "tos %u len %u off %u ttl %u prot %u cksum %u ",
                    ip->ip_tos, len, off, ip->ip_ttl,
                    ip->ip_p, ip->ip_sum);

        if (vflag > 2)
            fprintf(stdout,"seq %u ack %u win %u ",
                    tcp->th_seq, tcp->th_ack, tcp->th_win);

        if (vflag > 3)
            fprintf(stdout,"%s", payload);

        if (vflag > 0)
            printf("\n");
        }

    return NULL;
}

/* Callback */
void pcap_callback(u_char *args, const struct pcap_pkthdr* pkthdr,
                                             const u_char* packet)
{
    u_int16_t type = ethernet_handler(args, pkthdr, packet);

    if (type == ETHERTYPE_IP) {
        ip_handler(args, pkthdr, packet);
     } else if (type == ETHERTYPE_ARP) {
        /* noop */
    } else if (type == ETHERTYPE_REVARP) {
        /* noop */
    }
}


int main (int argc, char **argv)
{
  char *oper; /* Filter or Operation      */
  int npkts;  /* Number of polls          */
  char *dev;  /* Device                   */

  dev    = NULL;
  eflag  = 0;
  npkts  = -1;
  oper   = NULL;
  vflag  = 3;


  while (1) {
    static struct option long_options[] = {
      {"ethernet",  no_argument,       0, 'e'},
      {"interface", required_argument, 0, 'i'},
      {"polls",     required_argument, 0, 'p'},
      {"verbose",   required_argument, 0, 'v'},
      {0,0,0,0}
    };

    int option_index = 0;

    c = getopt_long (argc, argv, "ei:p:v:",
                        long_options, &option_index);

    if (c == -1)
      break;

    switch (c) {
      case 'e':
        eflag = 1;
        break;
      case 'i':
        dev = optarg;
        break;
      case 'p':
        npkts = atoi(optarg);
        break;
      case 'v':
        vflag = atoi(optarg);
        break;
      default:
        break;
    }
  }

  if (getuid()) {
        printf("Error! Must be root ... exiting\n");
        return (1);
  }

  dev = pcap_lookupdev(errbuf);

  if (dev == NULL) {
      printf("%s\n", errbuf);
      return (1);
  }

  descr = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);

  if (descr == NULL) {
      printf("pcap_open_live(): %s\n", errbuf);
      return (1);
  }

  pcap_lookupnet(dev, &net, &mask, errbuf);

  oper = copu_argv(oper);

  if (oper) {
      if (pcap_compile(descr, &filter, oper, 0, net) == -1) {
          errorlog(opmode, PACKAGE, "Error calling pcap_compile");
          exit (1);
      }

      if (pcap_setfilter(descr, &filter))  {
          errorlog(opmode, PACKAGE, "Error setting filter");
          exit (1);
      }
  }

  pcap_loop(descr, -1, pcap_callback, args); /* Loop pcap */

  return 0;
}

There are few missing items - most notably a usage message which is easy enough to implement.

A Packet Data Injector

Following is a very primitive packet injector. It is just good enough to cause errors to throw using nread in conjunction. The code is heavily commented for convienence:

#include "pkt.h"

#define PACKAGE "nject"

int main (int argc, char **argv)
{
    char *dev;       /* Network Device(s) driver */
    pcap_t *descr;   /* Session(s) description   */
    void * contents; /* Data to inject           */
    int delay;       /* Delay in seconds         */
    int npkts;       /* How many packets?        */
    int i;           /* Injector loop counter    */
    int c;           /* Shared getopt counter    */
    int vflag;       /* Verbosity flag           */

    /* Reasonable Defaults */
    contents  = NULL;
    delay     = 0;
    dev       = NULL;
    npkts     = 1;
    vflag     = 0;
    descr     = NULL;

    /*
     * Options-
     * c -contents  A string to cram into the packet header and payload
     * d -delay     Delay between injections in seconds
     * i -interface The device
     * p -packets   Number of packets to inject
     * u -usage     Print usage message
     * v -verbose   Be verbose
     */
    while (1) {
        static struct option long_options[] = {
            {"contents", required_argument,  0, 'c'},
            {"delay",    required_argument,  0, 'd'},
            {"packets",  required_argument,  0, 'p'},
            {"usage",    no_argument,        0, 'u'},
            {"verbose",  no_argument,        0, 'v'},
            {0,0,0,0}
        };

        int option_index = 0;

        c = getopt_long (argc, argv, "c:d:i:p:uv",
                    long_options, &option_index);

        if (c == -1)
            break;

        switch (c) {
            case 'c':
                contents = optarg;
                break;
            case 'd':
                delay = atoi(optarg);
                break;
            case 'i':
                dev = optarg;
                break;
            case 'p':
                npkts = atoi(optarg);
                break;
            case 'u':
                usage();
                return 0;
                break;
            case 'v':
                vflag = 1;
            default:
                break;
        }
    }

        /* Only root may fake up data on the interface */
    if (getuid()) {
        printf("Error! Must be root ... exiting\n");
        return (1);
    }

        if (dev == NULL) {
       /* Try to lookup the device if it is not defined */
           dev = pcap_lookupdev(errbuf);

        if (dev == NULL) {
            printf("%s\n", errbuf);
            return (1);
        }
    }

    /* Set the pcap description */
    descr = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);

    if (descr == NULL) {
        printf("pcap_open_live(): %s\n", errbuf);
        return (1);
    }


    /* Here we loop the injector */
    for (i = 1; i <= npkts; i++) {
        if (vflag)
            printf("Injecting packet %i on %s: %i second delay\n",
                    i, dev, delay);

        pcap_sendpacket (descr, contents, sizeof(contents));
        sleep(delay);
    }

    return 0;
}


/*
 * usage - Simple usage print. Prototyped in ntools.h
 */
void usage(void)
{
    printf(PACKAGE " [option][arguments]\n"
           PACKAGE " [-c|--contents string][-d|--delay seconds]\n"
           PACKAGE " [-p|--packets npackets][-u|--usage][-v|--verbose]\n"
                   "Options:\n"
                   "   -c|--contents string  Packet contents.\n"
                   "   -d|--delay seconds    Delay between injections.\n"
                   "   -i|--interface  dev   Use the specified interface.\n"
                   "   -p|--packets number   Number of packets to inject.\n"
                   "   -u|--usage            Print usage and exit.\n"
                   "   -v|--verbose          Be verbose.\n"
    );
}

Summary

Hopefully this series provides enough of a glimpse into not only how to leverage libpcap but understanding that programmers and hackers alike do not always have to jump through a lot of hoops to get at certain data. If there is a particular system related data set that is needed more than likely someone else has too.

prev