/**********************************************************************
 *               Copyright (C) 2007 Patrick McManus                    *
 *                <mcmanus@ducksong.com>                               *
 *                                                                     *
 *    This program is free software; you can redistribute it and/or    *
 *    modify it under the terms of the GNU General Public License as   *
 *    published by the Free Software Foundation; either version 2 of   *
 *    the License, or (at your option) any later version.              *
 *                                                                     *
 *    This program is distributed in the hope that it will be useful,  *
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 *    GNU General Public License for more details.                     *
 **********************************************************************/

// rev mar 03 2007 r002

/* --------------------------------------------------------------------------------------------------- *
  a silly little program that will read in a pcap capture file from stdin and calculate handshake times

  proivde the program with the IP address and port of a server you are interested in, (SOI and POI in the code)
  it will output the SYN-ACK rtt for each flow in the capture file.. for instance
    t=0  SYN
    t=2  SYN-ACK
    t=9  SYN-ACK
    t=17 ACK
     would report a rtt of 15.. note the retransmit being ignored

     this only works on pcap linktype 101 (raw IP).. which is what I get from my ppoe
     .. that's all really lame on my part to not at least take ethernet. That's a todo for me.

 * --------------------------------------------------------------------------------------------------- */

#define BSIZE 4096
#define SOI 0x44EF2AA1
#define POI 25

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <assert.h>
#include <pcap.h>
#include <arpa/inet.h>

struct halfopened
{
  struct halfopened *next, *prev;
  struct timeval ts;
  unsigned int addr;
  unsigned short port;
};

struct halfopened *head = NULL;

int countopen()
{
  
  struct halfopened *t;
  int rv;
  
  for ((rv = 0), (t= head); t; rv++, (t=t->next));
  return rv;
}

int full_read (int fd, void *d, int l)
{
  int x=0;
  int t;
  
  for (x=0;x < l;)
    {
      t = read (fd,((char *)d)+x,(l-x));
      if (t < 0)
	{
	  fprintf (stderr,"full_read failed\n");
	  abort ();
	}
      if (t == 0)
	{
	  fprintf (stderr,"EOF - %d halfopen\n",countopen());
	  exit (0);
	}
      
      x += t;
    }
  return l;
}

void registry (unsigned int addr, unsigned short port, struct timeval *ts)
{
  struct halfopened *t;
  
  for (t= head; t; t=t->next)
    {
      if ((addr == t->addr) && (port == t->port))
	break;
    }
  
  if (t == NULL)
    {
      t = (struct halfopened *) malloc (sizeof (struct halfopened));
      t->next = head;
      t->prev = NULL;
      if (head)
	head->prev = t;
      head = t;
      t->addr = addr;
      t->port = port;
      t->ts = *ts;
    }
  
  // pretend a retransmission didn't happen

  return;
}

void matchup (unsigned int addr, unsigned short port, struct timeval *ts)
{
  struct halfopened *t;
  
  for (t= head; t; t=t->next)
    {
      if ((addr == t->addr) && (port == t->port))
	break;
    }
  
  if (t == NULL) return;
  
  if (t->prev)
    t->prev->next = t->next;
  else
    {
      assert (head == t);
      head = t->next;
    }
  
  if (t->next)
    t->next->prev = t->prev;
  
  unsigned long long synack, ack; // expressed in milliseconds since the epoch
  
  synack = t->ts.tv_sec * 1000;
  synack += (t->ts.tv_usec / 1000);
  
  ack = ts->tv_sec * 1000;
  ack += (ts->tv_usec / 1000);
  
  if (ack < synack) // backwards in time?
    {
      fprintf (stderr,"backwards in time!\n");
      exit (3);
    }
  
  free (t);

  fprintf (stdout,"%lld \t%d.%d.%d.%d:%d\n",ack - synack, 
	   ((unsigned char *)&addr)[3],
	   ((unsigned char *)&addr)[2],
	   ((unsigned char *)&addr)[1],
	   ((unsigned char *)&addr)[0],
	   port);
  
}


int main()
{
  // read data from STDIN_FILENO
  unsigned char buf[BSIZE];
  struct pcap_file_header fhdr;
  unsigned char *ip, *tcp;
  unsigned int sport, dport, daddr, saddr;

  assert (sizeof (fhdr) == 24);
  assert (sizeof (unsigned int) == 4);
  assert (sizeof (unsigned short) == 2);
  
  // first we need to skip 24 bytes of file header
  full_read (STDIN_FILENO, &fhdr, 24);

  if (fhdr.linktype != 101)
    {
      // oh this is lame - todo
      fprintf (stderr,"link type %d - only support 101 - RAW right now\n",fhdr.linktype);
      exit (2);
    }
  
  
  while (1)
    {
      
      struct pcap_pkthdr hdr;

      full_read (STDIN_FILENO, &hdr, sizeof (hdr));
      
      if (hdr.caplen > BSIZE)
	{
	  fprintf (stderr,"snaplen too big - abort\n");
	  // lamely exit.. todo
	  exit (1);
	}
      
      full_read (STDIN_FILENO, buf, hdr.caplen);
      
      ip  =  (unsigned char *)buf;		// indirection in case we do something other than raw
      
      // it takes minimally 40 bytes for tcp and ip headers.. maybe more.
      if ((ip + 40) > (buf+hdr.caplen))
	{
	  fprintf (stderr,"malformed ip packet detected\n");
	  continue;
	}
      
	
      if ((ip[0] & 0xf0) != 0x40)
	{
	  fprintf (stderr,"skipping non IPv4 packet\n");
	  continue;
	}
      
      if (ip[9] != 0x06)
	{
	  fprintf (stderr,"skipping non TCP packet\n");
	  continue;
	}
      tcp = ip + 4 * (ip[0] & 0x0f);
      
      // make sure we have enough data.. the first check might not have been enough depending
      // on ip header size
      if ((tcp + 20) > (buf+hdr.caplen))
	{
	  fprintf (stderr,"malformed tcp packet detected\n");
	  continue;
	}


      saddr = ntohl (((unsigned int *)ip)[3]);
      daddr = ntohl (((unsigned int *)ip)[4]);

  
      sport = ntohs (((unsigned short *)tcp)[0]);
      dport = ntohs (((unsigned short *)tcp)[1]);

      
      unsigned int synack = 0;
      unsigned int ackonly = 0;
      
      // flags is tcp[13]
      switch (tcp[13] & 0x12)
	{
	case 0x12:
	  synack = 1;
	  break;
	case 0x10:
	  ackonly = 1;
	  break;
	}

      if (synack && (SOI == saddr) && (POI == sport))
	{
	  registry (daddr,dport,&hdr.ts);
	}
      else if (ackonly && (SOI == daddr) && (POI == dport))
	{
	  matchup (saddr, sport, &hdr.ts);
	}
      continue;
      
    }
  
  return 0;
}

  
  
