/**********************************************************************
 *               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.                     *
 **********************************************************************/

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

   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.

   * output RTT, QUERYNAME
   * script for freq of querynames, uniq count, avg, median, stddev

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

#define BSIZE 4096

#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 id;
};

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 id, struct timeval *ts)
{
    struct halfopened *t;
    
    for (t= head; t; t=t->next)
    {
        if ((id == t->id) && (addr == t->addr))
          break;
    }
  
    if (t == NULL)
    {
        t = (struct halfopened *) malloc (sizeof (struct halfopened));
        t->next = head;
        t->prev = NULL;
        if (head)
          head->prev = t;
        head = t;
    }

    // overwrite any stale data
    t->id = id;
    t->addr = addr;
    t->ts = *ts;
    

    return;
}

void matchup (unsigned int addr, unsigned short id, struct timeval *ts)
{
    struct halfopened *t;
  
    for (t= head; t; t=t->next)
    {
        if ((id == t->id) && (addr == t->addr))
          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 req, resp; // expressed in milliseconds since the epoch
  
    req = t->ts.tv_sec * 1000;
    req += (t->ts.tv_usec / 1000);
  
    resp = ts->tv_sec * 1000;
    resp += (ts->tv_usec / 1000);
  
    if (resp < req) // backwards in time?
    {
        fprintf (stderr,"backwards in time!\n");
        exit (3);
    }
  
    free (t);

    fprintf (stdout,"%12lld \t%8d\t%d.%d.%d.%d\n",resp-req, t->id,
             ((unsigned char *)&addr)[3],
             ((unsigned char *)&addr)[2],
             ((unsigned char *)&addr)[1],
             ((unsigned char *)&addr)[0]);

    return;
}


int main()
{
    // read data from STDIN_FILENO
    unsigned char buf[BSIZE];
    struct pcap_file_header fhdr;
    unsigned char *ip, *udp;
    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 28 bytes for udp and ip headers.. 
        if ((ip + 28) > (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] != 0x11)
        {
            fprintf (stderr,"skipping non UDP packet\n");
            continue;
        }
        udp = 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 ((udp + 8) > (buf+hdr.caplen))
        {
            fprintf (stderr,"malformed udp-2 packet detected\n");
            continue;
        }


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

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

        if ((sport != 53) && (dport !=53))
        {
            fprintf (stderr,"this isn't dns.. skipping\n");
            continue;
        }
        unsigned char *dns = udp + 8;

        if ((dns+4) > (buf + hdr.caplen))
        {
            // I need at least 4 bytes of the dns payload
            fprintf (stderr, "need at least 4 bytes payload\n");
            continue;
        }
        
        unsigned int response;
        unsigned short dnsid;
        
        response = dns[2] & 0x80;
        dnsid = ntohs (((unsigned short *)dns)[0]);
        
        if (response == 0)
        {
            registry (daddr, dnsid, &hdr.ts);
        }
        else
        {
            matchup (saddr, dnsid, &hdr.ts);
        }
        
    }
  
    return 0;
}

  
  
