Easy Sniff


Here is a program called Easy Sniff. Easy Sniff is a packet analyzer that I wrote on FreeBSD. It makes use of the Berkeley Packet Filter (BPF). The BPF is an interface to the raw data packets that arrive at the network card, bypassing any packet decomposition which happens on the way up to the client application, which usually sees nothing else but the actual data. This program is a bare bones implementation of what tcpdump does. With a little bit of work, the program could just as well be modified to work as a packet injector.

If you want more information on the BPF interface you can go here.

Feel free to make use of this code for research purposes. Just remember to mention my name in the sources :).

Anyways, here goes the code for EasySniff:

/*
 * Copyright (c) 2009 Oliver Mahmoudi
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted providing that the following conditions 
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>

#include <net/if.h>		/* struct ifreq */
#include <net/bpf.h>		/* BIOCSETIF */
#include <net/ethernet.h>
#include <netinet/in.h>	
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>

#define  OPTS      "12hespxc:n:"

#define  BPFHDRSIZ (sizeof(struct bpf_hdr))
#define  ETHHDRSIZ (sizeof(struct ether_header))
#define  IPHDRSIZ  (sizeof(struct ip))
#define  TCPHDRSIZ (sizeof(struct tcphdr))
#define  UDPHDRSIZ (sizeof(struct udphdr))

#define USAGE \
	"usage: ./easysniff [-12esphx] [-n counts] [-c capturefile] device\n \
        type: ./easysniff -h for help\n"

/* globals */
char *fbuff;			/* char array for read() */
FILE *fp;			/* for cap_file */
struct bpf_hdr *p;		/* */
struct bpf_stat bpf_sta;	/* */
int n;				/* bytes read */
int pfd;			/* bpf file descriptor */
int sflag;			/* flag for printing bpfstats */
int nflag;			/* flag for packet count */
int cflag;			/* cflag for cap_file */
int pflag;			/* force the if into promiscous mode */
int xflag;			/* print the packet in hex flag */
int eflag;			/* don't print the ethernet data */
int oneflag;			/* alternate print MAC address 1 */
int twoflag;			/* alternate print MAC address 2 */
int threeflag;			/* default version for print MAC address */

/* functions */
void	printhelp(void);
void	printpacketinhex(char *, int);
void	printinhex(char *, char *, int);
void	prntbpfstats(char *);
void	parse_eth_header(char *, int);
void 	parse_ip_header(char *, int);
/* void parse_icmp_header(char *, int); */
void	parse_tcp_header(char *, int);
void	parse_udp_header(char *, int);
void	parse_tcp_data(char *, int);
void	parse_udp_data(char *, int);
void	exit_handler();

void
printhelp(void)
{
  printf("\t-1 prints the MAC address in an alternate form \n");
  printf("\t-2 prints the MAC address in yet another alternate form\n");
  printf("\t-s prints the bpf statistics \n");
  printf("\t-e turns the printing of the ethernet header information off\n");
  printf("\t-p takes the interface out of promiscuous mode\n");
  printf("\t-n the number of packets you wish to capture on the interface\n");
  printf("\t-c the capture file to which the captured data will be written\n");
  printf("\t-h prints this help screen\n");
  printf("\t-x prints the packet in hex format\n");
  printf("\n\tdevice is the NIC you wish to listen on: e.g. ed0\n");
}

void
printpacketinhex(char *packet, int len)
{
  char *p = packet;

  printf("\n\n--------Start-of-Packet--------\n\n");

  while(len--) {
    printf("%.2x ", *p);
    p++;
  }
	
  printf("\n\n--------End-of-Packet--------\n\n");
}

void
printinhex(char *mesg, char *p, int len)
{
  printf(mesg);

  while(len--) {
    printf("%.2x ", *p);
    p++;
  }
}

void
prntbpfstats(char *readbuff)
{
  char *newbuff = readbuff;
  struct bpf_hdr *buf;

  buf = (struct bpf_hdr *)newbuff;
  printf("Read occured at: %d:%d\n",
    buf->bh_tstamp.tv_sec,
    buf->bh_tstamp.tv_usec);
  printf("Length of the captured portion: %d\n",
    buf->bh_caplen);
  printf("Original length of packet: %d\n", buf->bh_datalen);
  printf("Length of bpf header: %d\n\n", buf->bh_hdrlen);

  if(cflag) {
    fprintf(fp, "Read occured at: %d:%d\n", 
      buf->bh_tstamp.tv_sec,
      buf->bh_tstamp.tv_usec);
    fprintf(fp, "Length of the captured portion: %d\n",
      buf->bh_caplen);
    fprintf(fp, "Original length of the packet: %d\n", 
      buf->bh_datalen);
    fprintf(fp, "Length of the bpf header: %d\n\n", 
      buf->bh_hdrlen); 
  } 
}

void
parse_eth_header(char *packet, int len)
{
  struct ether_header *ethernet_h;
  int i = ETHER_ADDR_LEN;

  printf("Decoding the Ethernet Header:\n");

  if(len >= (p->bh_hdrlen + ETHHDRSIZ)) {
    ethernet_h = (struct ether_header *)(packet + p->bh_hdrlen);

    /* print the MAC Address alternate version 1 */

    if(oneflag) { 
      printinhex("Destination MAC: ", ethernet_h->ether_dhost, 6);
      printf("\n");
      printinhex("Source MAC: ", ethernet_h->ether_shost, 6);
      printf("\n");
    }	

    /* print the MAC Address alternate version 2 */
	
    if(twoflag) {
      do {
        printf("Destination MAC: %s%x", 
          i == ETHER_ADDR_LEN ? " " : ":",
          ethernet_h->ether_dhost);
      } while(--i > 0); 
      printf("\n");

      do {
        printf("Source MAC: %s%x",
          i == ETHER_ADDR_LEN ? " " : ":",
          ethernet_h->ether_shost);
      }while(--i > 0);
      printf("\n");
		}

      /* 
       * print the MAC Address version 3. This is the default.
       */

    if(threeflag) {
      printf("Destination MAC: %s\n",
        ether_ntoa((struct ether_addr *)&ethernet_h->ether_dhost));
      printf("Source MAC:      %s\n",
        ether_ntoa((struct ether_addr *)&ethernet_h->ether_shost));
      } 
		
    printf("Protocol:        dec %d hex %x",
      ntohs(ethernet_h->ether_type),
      ntohs(ethernet_h->ether_type));

    if(ntohs(ethernet_h->ether_type) == ETHERTYPE_IP)
      printf(" -> IP\n");
    else if(ntohs(ethernet_h->ether_type) == ETHERTYPE_ARP)
      printf(" -> ARP\n");
    else
      printf(" -> neither IP nor ARP\n");
  }
  else
    printf("the packet does not contain an ethernet header\n");
}

void
parse_ip_header(char *packet, int len) 
{
  int type;
  struct ether_header *ethernet_h;
  struct ip *ip_h;

  printf("\nDecoding the IP Header:\n");

  ethernet_h = (struct ether_header *)(packet + p->bh_hdrlen);
  type = ntohs(ethernet_h->ether_type);

  if(type == ETHERTYPE_IP)
    if(len >= (p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ)) {
      ip_h = (struct ip *)(packet + p->bh_hdrlen + ETHHDRSIZ);
      printf("Source address:      %s\n", inet_ntoa(ip_h->ip_src.s_addr));
      printf("Destination address: %s\n", inet_ntoa(ip_h->ip_dst.s_addr));
      printf("Protocol:            %d ", ip_h->ip_p);	

      switch(ip_h->ip_p) {
        case IPPROTO_TCP:
          printf("-> TCP\n");	
          parse_tcp_header(fbuff, n);
          break;
        case IPPROTO_UDP:
          printf("-> UDP\n");
          parse_udp_header(fbuff, n);
          break;
        case IPPROTO_ICMP:
          printf("-> ICMP\n");	
          break;
        default:
          printf("neither TCP nor UDP nor ICMP\n");
      }

      printf("\n");
      }
      else 
        printf("the IP packet is too short\n");
      }
      else
        printf("the packet does not contain an ip header\n");
}

void
parse_tcp_header(char *packet, int len)

  struct tcphdr *tcph;

  printf("\nDecoding the TCP Header:\n");
  if(len >= (p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ + TCPHDRSIZ)) {
    tcph = (struct tcphdr *)(packet + p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ);
    printf("Source Port:      %d\n", ntohs(tcph->th_sport));
    printf("Destination Port: %d\n", ntohs(tcph->th_dport));
    printf("\n");
		
    /* call the data parsing function */
    parse_tcp_data(fbuff, len);
  }
  else
    printf("the tcp packet is too short\n");
}

void
parse_udp_header(char *packet, int len)
{
  struct udphdr *udph;

  printf("Decoding the UDP Header:\n");
  if(len >= (p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ + UDPHDRSIZ)) {
    udph = (struct udphdr *)(packet + p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ);
    printf("Source Port: 	  %d\n", ntohs(udph->uh_sport));
    printf("Destination Port: %d\n", ntohs(udph->uh_dport));
    printf("\n");
    /* call the data parsing function */
    parse_udp_data(fbuff, len);
    }
  else
    printf("the udp packet is too short\n");
}
 
void
parse_tcp_data(char *packet, int len)
{
  char *data_packet;
  int data_length;
  struct ip *ip_h;

  printf("Decoding the Data:\n");

  /*  is there data in the packet ? */
  if(len > (p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ + TCPHDRSIZ)) {
    ip_h = (struct ip *)(packet + p->bh_hdrlen + ETHHDRSIZ);
    data_packet = (char *)(packet + p->bh_hdrlen + ETHHDRSIZ + ip_h->ip_hl*4 + TCPHDRSIZ);
    data_length = ntohs(ip_h->ip_len) - (ip_h->ip_hl*4 + TCPHDRSIZ);
      while(data_length--) {
      /* print it */
      printf("%c", *data_packet);
      data_packet++;
    }
  }
  else	
    printf("no data in the tcp packet\n");
}

void
parse_udp_data(char *packet, int len)
{
  char *data_packet;
  int packet_length;
  struct ip *ip_h;

  printf("Decoding the Data:\n");

  /* is there data in the packet ? */
  if(len > (p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ + UDPHDRSIZ)) {
    ip_h = (struct ip *)(packet + p->bh_hdrlen + ETHHDRSIZ);

    data_packet = (packet + p->bh_hdrlen + ETHHDRSIZ + ip_h->ip_hl*4 + UDPHDRSIZ);
    packet_length = ntohs(ip_h->ip_len) - (ip_h->ip_hl*4 + UDPHDRSIZ);
    while(packet_length--) {
      /* print it */
      printf("%c", *data_packet);
      data_packet++;
    }
  }
  else
    printf("no data in the udp packet\n");
}

int
main(int argc, char **argv)
{
  int ch, num, buflen, counts, packetcount = 0;
  char file_path[100];
  char *cap_file = NULL, *geten;
  struct ifreq ifrq;

  if(getuid() != 0) {
    printf("sorry, you need to be root to run this program\n");
    return(1);
  }

  eflag = cflag = nflag = sflag = pflag = xflag = oneflag = twoflag = 0;
  threeflag = 1;

  while((ch = getopt(argc, argv, OPTS)) != -1)
    switch(ch) {
      case '1':
        oneflag = 1;
        threeflag = 0;
        break;
      case '2':
        twoflag = 1;
        threeflag = 0;
        break;
      case 'e':
        eflag = 1;
        break;
      case 'c':
        cap_file = optarg;
        cflag = 1;
        if((geten = getenv("HOME")) == NULL) {
          printf("couldn't determine the users HOME environment variable\n");
          return(1);
        }
        sprintf(file_path,"%s/%s", geten, cap_file);
        if((fp = fopen(file_path, "r+")) == NULL) {
          printf("couldn't create the capture file\n");
          return(1);
        }
        break;
      case 'n':
        counts = atoi(optarg);
        nflag = 1;
        if((counts < 0) || (counts == 0)) {

          printf("you cannot capture %d packets\n", counts);
          return(1);
        }
        break;
      case 's':
        sflag = 1;
        break;
      case 'p':
        pflag = 1;
        break;
      case 'h':
        printhelp();
        return(0);
        break;
      case 'x':
        xflag = 1;
        break;
      case '?':
      default:
        fputs(USAGE, stderr);
        return(1);
    }

  argc -= optind;
  argv += optind;

  if(argc != 1) {
    fputs(USAGE, stderr);
    return(1);
  }

  /* zero the interface request structure */
  bzero(&ifrq, sizeof(ifrq));
  bzero(&bpf_sta, sizeof(bpf_sta));

  /* open /dev/bpfx */
  if((pfd = open("/dev/bpf0", O_RDWR)) < 0) {
    printf("couldn't open /dev/bpf\n");
    return(1);
  }

  /* bind the bpf the to interface with the BIOCSETIF ioctl */

  strncpy(ifrq.ifr_name, argv[0], IFNAMSIZ);

  if(ioctl(pfd, BIOCSETIF, &ifrq) < 0) {
    printf("the BIOCSETIF ioctl failed\n");
    return(1);
  }

  /* get the required buffer length for the bpf */
	
  if(ioctl(pfd, BIOCGBLEN, &buflen) < 0) {
    printf("the BIOCGBLEN ioctl failed\n");
    return(1);
  }

  /* force the interface into promiscuous mode */
  if(!pflag)
    if(ioctl(pfd, BIOCPROMISC) < 0) {
      printf("the BIOCPROMISC ioctl failed");
      return(1);
    }

  /*
   * turn on immediate reading
   * num being one means turn BIOCIMMEDIATE on
   * num being two means turn BIOCIMMEDIATE off
   */

  num = 1;
  if(ioctl(pfd, BIOCIMMEDIATE, &num) < 0) {
    printf("the BIOCIMMEDIATE ioctl failed\n");
    return(1);
  }

  /* 
   * Get the interface which the bpf is listening on.
   * Actually this is not necessary as the interface has
   * been specifically set with the BIOCSETIF ioctl.
   * We'll do it anyways. Sorta like a safety check.
   */
	
  if(ioctl(pfd, BIOCGETIF, &ifrq) < 0) {
    printf("the BIOCGETIF ioctl has failed\n");
    return(1);
  }

  printf("listening on %s\n\n", ifrq.ifr_name);
  /* printf("buffer length %d\n\n", buflen); */

  fbuff = (char *)malloc(buflen);
  bzero(fbuff, buflen);

  /* set our signal handlers */
  (void)signal(SIGINT, exit_handler);
  (void)signal(SIGTERM, exit_handler);

  /* now we are able to read from the bpf */

  while((n = read(pfd, fbuff, buflen)) > 0) {
    printf("\n\n---------- New Packet Captured ----------\n");
    printf("Packet %d\n", ++packetcount);
    printf("Number of bytes read %d\n", n);

    if(cflag) {
      fprintf(fp, "\n\n---------- New Packet Captured ----------\n");
      fprintf(fp, "Number of bytes read %d\n", n);
    }

    if(xflag)
      printpacketinhex(fbuff, n);

    if(sflag)
      prntbpfstats(fbuff);
    else
      printf("\n");

    p = (struct bpf_hdr *)fbuff;

    /* parse the ethernet header and get the protocol type */

    if(!eflag)
      parse_eth_header(fbuff, n);

    /* 
     * parse the ip header, determine the type of protocol
     * and call the appropriate transport layer function
     */

    parse_ip_header(fbuff, n);

    if(nflag) {
      counts--;
        if(!counts)
          break;
    }
  }  /* while() */
	
  exit_handler();

  /* not reached */
  return(0);
}

void
exit_handler()
{
  if((ioctl(pfd, BIOCGSTATS, &bpf_sta)) < 0) {
    printf("couldn't get bpf statistics\n");
    exit(1);
  }

  printf("\n");
  printf("Capture Statistics:\n");
  printf("number of packets received: %d\n", bpf_sta.bs_recv);
  printf("number of packets dropped:  %d\n", bpf_sta.bs_drop);

  if(cflag) {
    fprintf(fp, "number of packets received: %d\n", bpf_sta.bs_recv);
    fprintf(fp, "number of packets dropped: %d\n", bpf_sta.bs_drop);
  }

  /* fflush */

  close(pfd);
  if(cflag)
    fclose(fp);

  exit(0);
}

To compile and link, simply issue:

% gcc -o easysniff easysniff.c

on the command line.

And run it with:

% ./easysniff -s -n 5 rl0

to capture 5 packets on the interface "rl0" and additionally print the BPF statistics.

Here is a gzipped tarball of the EasySniff Source Code.

SHA256 sum: 2974a55451254a78e754a85c419dfb281093a4564e1ba3d3b620d695ffaf3bbf