/************************************************************************
halflife's little tcp dumper program

This is a program designed to show you how simple libpcap is to use.
It lets you specify a filter, and displays some useful information
based on the headers of the packets. It is designed more of a tutorial
on using libpcap than as a actual working program. This version is
linux specific, but only because of the tcp, ip, udp, icmp header files.
***********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pcap.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/udp.h>

/* some useful constants from arny's sniffer */
#define		SNAPLEN		128
#define		PROMISC		1
#define		TIMEOUT		1024
#define		OPTIMIZE	1

/* function to print the packet */
void	process_packet(u_char *, struct pcap_pkthdr *, u_char *);
/* signal handler */
void cleanup(int);
/* wtf is this, anyways... wont link without it */
int fddipad;
/* pcap file descriptor */
pcap_t *ip_socket;

/* sets everything up, and then sits in a loop */
main(int argc, char **argv)
{
   /* used by pcap_open_live() to return a error */
   char errbuf[PCAP_ERRBUF_SIZE];
   /* interface to use (this code only works (correctly) with ethernet) */
   char interface[]="eth0";
   /* bpf program */
   struct bpf_program prog;   
   /* network and netmask */
   u_long network;
   u_long netmask; 
   /* clean up when we get some signals */
   signal(SIGHUP, cleanup);
   signal(SIGINT, cleanup);
   signal(SIGQUIT, cleanup);
   signal(SIGABRT, cleanup);
   signal(SIGKILL, cleanup);
   signal(SIGSEGV, cleanup);   
   /* simple argument check */
   if(argc != 2)
   {
      fprintf(stderr, "%s \"filter\"\n", argv[0]);   
      fprintf(stderr, "ie: %s \"tcp port 23 or tcp port 21\"\n", argv[0]);
      exit(1);
   }
   /* get the network number & mask of interface */
   if(pcap_lookupnet(interface, &network, &netmask, errbuf) < 0)
   {
      perror("pcap_lookupnet()");
      exit(1);
   }
   /* open the interface, returning a pcap filedescriptor type thing */
   ip_socket=pcap_open_live(interface, SNAPLEN, PROMISC, TIMEOUT, errbuf);
   if(ip_socket == NULL)
   {
      fprintf(stderr, "pcap_open_live(): %s\n", errbuf);
      exit(1);
   }
   /* compile the filter */
   if(pcap_compile(ip_socket, &prog, argv[1], OPTIMIZE, netmask) < 0)
   {
      pcap_perror(ip_socket, "pcap_compile()");
      exit(1);
   }
   /* apply the filter */
   if(pcap_setfilter(ip_socket, &prog) < 0)
   {
      pcap_perror(ip_socket, "pcap_setfilter()");
      exit(1);
   }
   /* some info to stdout, just the filter string and pid */
   printf("using filter string: \"%s\"\n", argv[1]);
   printf("pid: [%d]\n", getpid());
   printf("==================================================================\n");
   /* go in a loop, processing packets forever */
   if(pcap_loop(ip_socket, -1, (pcap_handler)process_packet, NULL) < 0)
   {
      pcap_perror(ip_socket, "pcap_loop()");
      exit(1);
   }
   exit(0);
}

/***************************************************************************
this processes the packet, it's arguments are as follows:
user 			This is used to pass user defined data (I THINK!)
pkthdr			This is a pcap specific header
mypacket		This is the packet, including link level header
***************************************************************************/
void process_packet(u_char *user, struct pcap_pkthdr *pkthdr, u_char *mypacket)
{
   /* ip, tcp, udp, icmp pointers */
   struct iphdr 	*ip;
   struct tcphdr 	*tcp;
   struct icmphdr 	*icmp;
   struct udphdr	*udp;
   /* variable for options */
   int option_length=0;
   /* inet_ntoa sucks - needs this totally worthless struct */
   struct in_addr ipaddr;   
      
   /* set ip to the beginning of the ip header */
   ip = (struct iphdr *)(mypacket + 14);
   /* account for IP options */
   option_length = (ip->ihl - 5);
   option_length *= 4;
   /* set tcp/udp/icmp pointers */
   tcp = (struct tcphdr *)(mypacket + 14 + sizeof(struct iphdr) + option_length);
   udp = (struct udphdr *)tcp;
   icmp = (struct icmphdr *)udp;
   
/**************************************************************************
This is the printer for the TCP packets. It prints the flags, the
packet length, the sequence number, the acknowledgement number,
the source address, the dest address, the source port, the dest
port, and the window size.
**************************************************************************/   
   if(ip->protocol == 6)
   {
      ipaddr.s_addr = ip->saddr;
      printf("TCP:%s:%u -> ", inet_ntoa(ipaddr), ntohs(tcp->source));
      ipaddr.s_addr = ip->daddr;      
      printf("%s:%u ", inet_ntoa(ipaddr), ntohs(tcp->dest));
      printf("seq: 0x%x ", ntohl(tcp->seq));
      printf("ack: 0x%x\n", ntohl(tcp->ack_seq));
      printf("TCP:window: %5u\tflags: ", ntohs(tcp->window));
      if(tcp->urg == 1) printf("U");
      else printf("-");
      if(tcp->ack == 1) printf("A");
      else printf("-");
      if(tcp->psh == 1) printf("P");
      else printf("-");
      if(tcp->rst == 1) printf("R");
      else printf("-");
      if(tcp->syn == 1) printf("S");
      else printf("-");
      if(tcp->fin == 1) printf("F");
      else printf("-");
      printf("\tPktLen: %u\n", ntohs(ip->tot_len));
      fflush(stdout);
      return;
   }
/**************************************************************************
this is the printer for UDP packets. These packets are pretty simple,
so there is very little to print. All we print is the source address,
dest address, source port, and dest port.
**************************************************************************/   
   if(ip->protocol == 17)
   {   
      ipaddr.s_addr = ip->saddr;
      printf("UDP:%s:%u -> ", inet_ntoa(ipaddr), ntohs(udp->source));
      ipaddr.s_addr = ip->daddr;
      printf("%s:%u\t%u\n", inet_ntoa(ipaddr), ntohs(udp->dest), ntohs(ip->tot_len));
      fflush(stdout);
      return;
   }
/***************************************************************************
This is the ICMP packet printer, We print the type of icmp packet, along
with the source and dest ip address. Kinda messy, but it all works - I
hope!
****************************************************************************/   
   if(ip->protocol == 1)
   {
      ipaddr.s_addr = ip->saddr;
      printf("ICMP:%s -> ", inet_ntoa(ipaddr));
      ipaddr.s_addr = ip->daddr;
      printf("%s ", inet_ntoa(ipaddr));
      switch(icmp->type)
      {
         case ICMP_ECHOREPLY: puts("ping reply");break;
         case ICMP_DEST_UNREACH: 
         switch(icmp->type)
         {
            case ICMP_NET_UNREACH: puts("net unreachable");break;
            case ICMP_HOST_UNREACH: puts("host unreachable");break;
            case ICMP_PROT_UNREACH: puts("protocol unreachable");break;
            case ICMP_PORT_UNREACH: puts("port unreachable");break;
            case ICMP_FRAG_NEEDED: puts("fragmentation needed");break;
            case ICMP_SR_FAILED: puts("source route failed");break;
            case ICMP_NET_UNKNOWN: puts("net unknown");break;
            case ICMP_HOST_UNKNOWN: puts("host unknown");break;
            default: puts("unknown unreachable");break;
         }
         break;
         case ICMP_SOURCE_QUENCH: puts("source quench");break;
         case ICMP_REDIRECT:
         switch(icmp->type)
         {
            case ICMP_REDIR_NET: puts("redirect net");break;
            case ICMP_REDIR_HOST: puts("redirect host");break;
            default: puts("unknown redirect");break;
         }
         break;
         case ICMP_ECHO: puts("ping");break;
         case ICMP_TIME_EXCEEDED: puts("time exceeded");break;
         case ICMP_PARAMETERPROB: puts("parameter problem");break;
         case ICMP_TIMESTAMP: puts("timestamp request");break;
         case ICMP_TIMESTAMPREPLY: puts("timestamp reply");break;
         case ICMP_INFO_REQUEST: puts("information request");break;
         case ICMP_INFO_REPLY: puts("information reply");break;
         case ICMP_ADDRESS: puts("address mask request");break;
         case ICMP_ADDRESSREPLY: puts("address mask reply");break;
         default: break;
      }
   }
}


void cleanup(int s)
{
   pcap_close(ip_socket);
   exit(0);
}
   
