
/* tcpmon2.c */
/* Andrew Davison, May 1998, (ad@ratree.psu.ac.th) */

/* Monitor Linux TCP packets using the libpcap library */
/* Can include a tcpdump-like filter to restrict the output. */
/* Based on tcpmon.c */

/* Compilation:
     \gcc -Wall -o tcpmon2 tcpmon2.c -lpcap
   Usage:
      ./tcpmon2 [filter] | more
   e.g.
      ./tcpmon2 | more
      ./tcpmon2 "port 80" | more
      ./tcpmon2 "port 80 and src catsix" | more
*/


#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/ip.h>

#include <pcap.h>

/* On Red Hat Linux release 5.0 (Hurricane); Kernel 2.0.32 */
#include <netinet/if_ether.h>
#include <netinet/tcp.h>

/* On Debian Linux release 1.1; Kernel 2.0.24 */
/*
#include <linux/if_ether.h>
#include <linux/tcp.h>
*/


#define BUFLEN 1600      /* max length of packet data */
#define ADDRLEN 100      /* max length of an address */
#define COLWIDTH 75      /* width of output for packet data */


typedef struct {         /* TCP Packet data structure */
  struct ethhdr eth;        /* ethernet header */
  struct iphdr ip;          /* IP header */
  struct tcphdr tcp;        /* TCP header */
  char data[BUFLEN];        /* data part */
} etherpkt;


int get_filter(int argc, char *argv[], char **filter);
void apply_filter(char *netdev, pcap_t *pd, char *filter);
void print_pack(u_char *user, const struct pcap_pkthdr *hp, 
                                             const u_char *pktdata);
void print_packet(etherpkt *ep);
void host_lookup(unsigned long int in, char hname[]);
void print_data(int datalen, char *data);
void clean_up(int);


/* globals */
pcap_t *pd;           /* packet descriptor */


void main(int argc, char *argv[])
{
  char *netdev, *filter;
  char ebuf[PCAP_ERRBUF_SIZE];      /* for pcap function errors */

  /* Determine the device name */
  if ((netdev = pcap_lookupdev(ebuf)) == NULL) {
    fprintf(stderr, "%s\n", ebuf);
    exit(1);
  }

  /* Open connection to device, returning a packet descriptor */
  if ((pd = pcap_open_live(netdev, 256, 1, 0, ebuf)) == NULL) {
    fprintf(stderr, "%s\n", ebuf);
    exit(1);
  }

  /* Setup signal handlers */
  signal(SIGHUP, SIG_IGN);
  signal(SIGINT, clean_up);
  signal(SIGTERM, clean_up);
  signal(SIGKILL, clean_up);
  signal(SIGQUIT, clean_up);

  fprintf(stderr, "listening to %s\n", netdev);
  fflush(stderr);

  if (get_filter(argc, argv, &filter))
    apply_filter(netdev, pd, filter);
    
  /* Go into an infinite loop reading packets. If the filter
     criteria passes a packet then call print_pack() 
  */
  pcap_loop(pd, -1, print_pack, NULL);
  
  fprintf(stderr, "pcap_loop() error\n");    /* should not reach here */
  clean_up(-1);
}



int get_filter(int argc, char *argv[], char **filter)
/* Read a filter from the command line if possible. */
{
  if ((argc != 1) && (argc != 2)) {
    fprintf(stderr, "Usage tcpmon2 [<filter>]\n");
    clean_up(-1);
  }
  if (argc == 2) {
    *filter = argv[1];
    fprintf(stderr, "filter: \"%s\"\n", *filter);
    return 1;
  }
  return 0;
}



void apply_filter(char *netdev, pcap_t *pd, char *filter)
/* Install the filter as filter code attached to the packet
   descriptor pd
*/
{
  u_int32_t localnet, netmask;
  char ebuf[PCAP_ERRBUF_SIZE];
  struct bpf_program fcode;

  /* Get the devices network number and netmask */
  if (pcap_lookupnet(netdev, &localnet, &netmask, ebuf) == -1) {
    fprintf(stderr, "%s\n", ebuf);
    return;
  }

  /* Compile the filter, placing the result in fcode */
  if (pcap_compile(pd, &fcode, filter, 1, netmask) < 0) {
    fprintf(stderr, "pcap_compile: %s\n", pcap_geterr(pd));
    return;
  }
  
  /* Attach the filter code to the packet descriptor */
  if (pcap_setfilter(pd, &fcode) == -1) {
    fprintf(stderr, "pcap_setfilter: %s\n", pcap_geterr(pd));
    return;
  }
}



void print_pack(u_char *user, const struct pcap_pkthdr *hp, 
                                          const u_char *pktdata)
/* Print the packet stored in pktdata */
{
  etherpkt *ep;

  ep = (etherpkt *)pktdata;
  if (ep->eth.h_proto == ntohs(ETH_P_IP))       /* is an IP packet? */
    print_packet(ep); 
}



void print_packet(etherpkt *ep)
/* Print the packet pointed to by ep, but only if it is a TCP packet.
   Almost the same as in tcpmon.c (note: ep is a pointer here).
*/
{
  int sport, dport;
  char saddr[ADDRLEN], daddr[ADDRLEN];
  struct iphdr *ip;
  struct tcphdr *tcp;

  /* Use ip and tcp to refer to the IP and TCP headers, and
     correct for the 2-byte hole in etherpkt after the ethhdr field
  */
  ip = (struct iphdr *)(((unsigned long)&(ep->ip))-2);
  tcp = (struct tcphdr *)(((unsigned long)&(ep->tcp))-2);

  if(ip->protocol != IPPROTO_TCP)     /* not a TCP packet? */
    return;

  sport = ntohs(tcp->source);         /* get the port numbers */
  dport = ntohs(tcp->dest);
  host_lookup(ip->saddr, saddr);      /* get the addresses */
  host_lookup(ip->daddr, daddr);

  printf("---------------------------------------\n");
  printf("%s [%d] -> %s [%d]\n", saddr, sport, daddr, dport);
  putchar('\t');
  print_data( htons(ip->tot_len)-sizeof(ep->ip)-sizeof(ep->tcp), 
                                                        ep->data-2);
}


void host_lookup(unsigned long int addr, char hname[])          
/* Convert the dotted decimal address in addr to a name. 
   First try to get the host name, otherwise settle for
   the string version of the dotted decimal.
   Same as in tcpmon.c.
*/
{
  struct in_addr iaddr;
  struct hostent *he;

  iaddr.s_addr = addr; 
  if ((he = gethostbyaddr((char *)&iaddr,
                 sizeof(struct in_addr), AF_INET)) == NULL)
    strcpy(hname, inet_ntoa(iaddr));          
  else
    strcpy(hname, he->h_name); 
}



void print_data(int datalen, char *data)
/* Print the packet data in a COLWIDTH wide column.
   Only print printable characters, '.' otherwise.
   Replace a CR or LF by a newline.
   Start every line with a tab to emulate the output
   from tcpdump/tcpshow.
   Same as in tcpmon.c.
*/
{
  int i;
  int width = 0;         /* width of output */
  
  for(i=0; i < datalen; i++) {
    if ((data[i] == 13) || (data[i] == 10)) {   /* CR or LF */
      printf("\n\t"); 
      width = 5; 
    }

    if(isprint(data[i]))
      printf("%c", data[i]);
    else
      printf(".");
    width++;

    if(width > COLWIDTH) {
      printf("\n\t");
      width = 5;
    }
  }
  printf("\n");
  fflush(NULL);
}


void clean_up(int signo)
/* Report packet statistics and close the packet descriptor */
{
  struct pcap_stat stat;

  fflush(stdout);
  putc('\n', stderr);

  if (pcap_stats(pd, &stat) < 0)
    fprintf(stderr, "pcap_stats: %s\n", pcap_geterr(pd));
  else {
    fprintf(stderr, "%d packets received by filter\n", stat.ps_recv);
    fprintf(stderr, "%d packets dropped by kernel\n", stat.ps_drop);
  }

  pcap_close(pd);
  exit(0);
}


