/*  -*- Mode: C;  -*- */

/******************************************************************************
*                                                                             *
*   Copyright 2005 University of Cambridge Computer Laboratory.               *
*                                                                             *
*   This file is part of Nprobe.                                              *
*                                                                             *
*   Nprobe 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.                                       *
*                                                                             *
*   Nprobe 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.                              *
*                                                                             *
*   You should have received a copy of the GNU General Public License         *
*   along with Nprobe; if not, write to the Free Software                     *
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
*                                                                             *
******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <string.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <assert.h>
#ifdef __alpha__
#include <sys/mbuf.h>
#include <machine/endian.h>
#endif

#include <net/route.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in_systm.h>
#undef __STDC__
#include <netinet/ip.h>
#include <netinet/udp.h>
#ifdef __alpha__
#include <netinet/ip_var.h>
#endif
#ifdef __linux__
#define __FAVOR_BSD
#endif
#include <netinet/tcp.h>
#ifdef __alpha__
#include <net/if_llc.h>
#include <netinet/if_ether.h>
#endif
#include <netinet/if_fddi.h>

#ifdef __linux__
//#include <linux/if_ether.h>
#include <net/ethernet.h>
#include <netinet/ip_icmp.h>

#include "linux_tweaks.h"

#endif

#include "list.h"
#include "pkt.h"
#include "interface.h"
#include "flows.h"
#include "service.h"
#include "http.h"
#include "tcp.h"
#include "udp.h"
#include "udp_ns.h"
#include "pool.h"
#include "if_nprobe.h"
#include "timeouts.h"
#include "output.h"
#include "icmp.h"
#include "sundry_records.h"

#ifdef PRINT_OUT
#include "print_util.h"
#include "cprintf.h"
#endif

#include "probe_config.h"


/* global counters */
counters_t counters; /* why isn't this in counters.c? --cpk25 */

#ifdef TCPDUMP_FED

struct timeval curr_tm_tv;   /* packet time stamp driven */
int first_pkt = 1;
int pno = 0;                 /* keep track for debugging */

void 
do_pkt(bufrec_t *bp, listhdr_t *host_hashtbl)
{

  int can_do;
  prec_t *pp;

#ifdef STRIP_DEBUG
  printf("wire len %d ", bp->plength);
#endif

  counters.wire.pkts++;
  counters.wire.octs += bp->plength;

  pp = get_prec_t();

  pp->brecp = bp;
  pp->buf = buf_start(pp);   // slightly less gross macro
  pp->len = pp->wirelen;     // gross macro
  pp->trunc = 0;
 
  /* Strip link level encapsulation */
  can_do = bp->link_data->linkstripper(pp);
  
  
#ifdef STRIP_DEBUG
  printf("ether type %d encaps len %d\n", can_do, pp->len);
#endif
  
  /* S'be only getting unfragmented IP pkts - but check */
  if (can_do == ETHERTYPE_IP)
    {
      do_ip(pp, host_hashtbl);
    }
  else
    {
      counters.notIP.pkts++;
      counters.notIP.octs += pp->len;

      if (dump_pkt_types & DUMP_NON_IP_PKTS)
	pkt_dump(pp, HTTP_ERR_NONE, NULL, ATM_DATA);
      
      FREE_BUFF(bp->base);
      FREE_BUFREC(bp);
      FREE_PREC(pp);
    }
 

  return;
}

#endif /* #ifdef TCPDUMP_FED */

int 
do_icmp(prec_t *pp, us_clock_t tm)
{
  struct ip *ipp;
  struct tcphdr *tcpp;
  int hlen;
  struct icmphdr *ihp = (struct icmphdr *)pp->buf;
  unsigned char *buf = pp->buf;
  int len = pp->len;
  unsigned char type = ihp->type;
  unsigned char code = ihp->code;
  struct icmp_rec *rp;

  int pkt_dumped = 0;

  BUMP_GLOBAL(TRAFF_ICMP);

  if (dump_pkt_types & DUMP_ICMP_PKTS)
    {
      pkt_dump(pp, HTTP_ERR_NONE, NULL, ATM_DATA);
      pkt_dumped = 1;
    }


  if (len < sizeof(struct icmphdr))
    {
      BUMP_DROPS(ICMP_len);
      goto drop;
    }

  /* only interested in unreachable messages */
  switch(type)
    {
    case ICMP_UNREACH: BUMP_GLOBAL(TRAFF_ICMP_UNREACH); break;
    case ICMP_ECHOREPLY:
      /* DROP THROUGH */
    default: goto drop; break;
    }

  len -= sizeof(struct icmphdr);
  buf +=  sizeof(struct icmphdr);

  if (len < sizeof(struct ip))
    {
      BUMP_DROPS(ICMP_len);
      goto drop;
    }

  ipp = (struct ip *)buf;
  /* only interested if response to TCP */
  if (ipp->ip_p != IPPROTO_TCP)
    goto drop;

  hlen =  ipp->ip_hl*4;
  len -= hlen;

  if (len < 4)
    {
      BUMP_DROPS(ICMP_len);
      goto drop;
    }

  pp->buf += hlen;
  tcpp = (struct tcphdr *)buf;

  /* this is a one off - so we can write straight into the output buffer */
  rec_dump_start();

  rp = (struct icmp_rec *)outp;
  rp->tm = tm;
  rp->type = type;
  rp->code = code;
  rp->src_addr = *((unsigned int *)&ipp->ip_src);
  rp->dst_addr = *((unsigned int *)&ipp->ip_dst);
  rp->src_port = tcpp->th_sport;
  rp->dst_port = tcpp->th_dport;
  rp->ipproto = ipp->ip_p;

  outp += sizeof(struct icmp_rec);

  rec_dump_end(REC_ICMP_UNREACH);

 drop:
  return 0;
}

#ifdef __alpha__

static int
in_cksum(const struct ip *ip)
{
  register const u_short *sp = (u_short *)ip;
  register u_int_t sum = 0;
  register int count;
  
  /*
   * No need for endian conversions.
   */
  for (count = ip->ip_hl * 2; --count >= 0; )
    sum += *sp++;
  while (sum > 0xffff)
    sum = (sum & 0xffff) + (sum >> 16);
  sum = ~sum & 0xffff;
  
  return (sum);
}

#endif /* __alpha__ */


/* 
 * Called to handle packet at IP and higher protocol levels 
 * - if TCPDUMP_FED lower level encapsulation will have been checked and 
 *   IP packet confirmed 
 * - if PROBE_FED have received this buffer with data pointer at start of 
 *   *assumed* IP header - check validity by assuming LLC/SNAP header 
 *   preceeds with OUI fields of zero and checking type field 
 *   (this is not an exhaustive check).
 *
 * TODO - enlarge and ammend if non-LLC encapsulations to be encountered 
 */

int 
do_ip(prec_t *pp, listhdr_t *host_hashtbl)
{
  struct ip iph, *ipp;
  unsigned short iplen, hlen, ipoff;

  us_clock_t time_now;

  static us_clock_t last_pkt = 0ULL;

#ifdef PROBE_FED
  if (pp->buf[-2] != 0x08 || pp->buf[-1] != 0x00)
    {
      BUMP_DROPS(notIP);

      if (dump_pkt_types & DUMP_NON_IP_PKTS)
	pkt_dump(pp, HTTP_ERR_NONE, NULL, ATM_DATA);

      goto drop;
    }
#endif

  BUMP_GLOBAL(TRAFF_IP);

  if (pp->len < sizeof(struct ip))
    {
#ifdef PRINT_OUT
      printf("[truncated IP hdr]\n");
#endif
      BUMP_DROPS(IPHdr);
      goto drop;
    }

  /* may not be aligned - if so get into aligned header */
  if (((unsigned int)pp->buf & 0xffffffff) & (sizeof(int) - 1))
    {
      memcpy((void *)&iph, (void *)pp->buf, sizeof(struct ip));
      ipp = &iph;
    }
  else
    {
      ipp = (struct ip *)pp->buf;
    }
#if 0
printf("SRC: NBO %s  Host %s\n", inet_ntoa(htonl(*(unsigned short *)&(ipp->ip_src))), inet_ntoa(ipp->ip_src));
printf("DEST: NBO %s  Host %s\n", inet_ntoa(htonl((unsigned short *)&(ipp->ip_dst))), inet_ntoa(ipp->ip_dst)); 
return 0;
#endif

  if (ipp->ip_v != IPVERSION)
    {
      BUMP_DROPS(IPversion);
      goto drop;
    }


  hlen = ipp->ip_hl*4;
  iplen = ntohs(ipp->ip_len);
  ipoff = ntohs(ipp->ip_off);
      
  
  /* S'not find fragmented packets - but check */
  if ((ipoff & IP_MF) || (ipoff & 0x1fff) != 0)
    {
#ifdef PRINT_OUT
      printf("Fragmented IP\n");
#endif
      BUMP_DROPS(IPFrag);
      goto drop;
    }

#ifdef CKSUM
#if 0
if(ipp->ip_p == 1 )
  {
    int i;
    printf("ICMP hlen=%d csum=%04x csumslow=%04x pass=%04x\n",
	   hlen, ipp->ip_sum, in_cksum((struct ip *)ipp),
           ip_fast_csum((char *)ipp, (unsigned int)hlen/4)) ;
    for (i = 0; i < 20; i++)
      printf("%02x ", ((char *)ipp)[i] & 0xff);
    printf("\n");
  }
#endif /* 0 */

  if (ip_fast_csum((char *)ipp, (unsigned int)hlen/4))
    {
      pkt_dump(pp, IP_ERR_HDRCHKSUM, NULL, 
#ifdef PROBE_FED
	       *((unsigned int *)buf_head(pp))
#else
      pp->atmdata
#endif
	       );
      BUMP_DROPS(IPSum);
      goto drop;
    }
#endif /* ifdef CKSUM */
      
  
  /* S'not find fragmented packets - but check */
  if ((ipoff & IP_MF) || (ipoff & 0x1fff) != 0)
    {
#ifdef PRINT_OUT
      printf("Fragmented IP\n");
#endif
      BUMP_DROPS(IPFrag);
      goto drop;
    }

  /* check length we have against IP claimed length */
  if (pp->len != iplen)
    {
      /* min. length ether frame? */
	if (pp->len > iplen && (IS_ETHER_MIN_FRAME(pp) || IS_FDDI_MIN_FRAME(pp)))
	{
	  BUMP_SPECIFIC_OCTS(IPLen_media_minpkt, pp->len - iplen);
	  pp->len = iplen;
	}
#ifdef TCPDUMP_FED
      else if (pp->wirelen == pp->brecp->link_data->snaplen)
	{
#ifdef PRINT_OUT
	  printf("[captured %d of %d] ", pp->len, iplen);
#endif
	  pp->trunc = iplen - pp->len;
	  BUMP_SPECIFIC_OCTS(TCPDump_trunc, pp->trunc);
	}
#endif
      else if (pp->len < iplen)
	{
#if 0
	  char errbuf[50];
	  sprintf(errbuf, "packet length error %d s'be %d (IP)",
		  pp->len, iplen);
	  error(errbuf, NULL);
#endif
	  BUMP_DROPS(IPLen);
	  goto drop;
	}
      else
	{
#if 0
	  char errbuf[250];
	  char *cp = errbuf;
	  cp += sprintf(errbuf, "%s>",
			inet_ntoa(*((struct in_addr *)&(ipp->ip_src))));
	  sprintf(cp, "%s packet length error %d s'be %d (IP)",
		  inet_ntoa(*((struct in_addr *)&(ipp->ip_dst))), 
		  pp->len, iplen);
	  inform(F_BLUE, errbuf);
#endif
	  BUMP_SPECIFIC_OCTS(IP_pkt_too_long, pp->len - iplen);
	  pp->len = iplen;
	}
    }


  /* S'not find IP options - but check */
  if (hlen > sizeof(struct ip))
    {
      if (pp->len < hlen)
	{
#ifdef PRINT_OUT
	  printf("[truncated IP options]\n");
#endif
	  BUMP_DROPS(IPHdr_opts);
	  goto drop;
	}
#ifdef PRINT_OUT
      else
	{ 
	  printf("IP options present : hlen %d, 1st option %02x",
		 hlen, pp->buf[20]);
	}
#endif
    }

#if 0
  /* Any tos stuff? */
  if (ipp->ip_tos)
    printf("IP tos 0x%x ", (int)ipp->ip_tos);
#endif

  pp->buf += hlen;
  pp->len -= hlen;

#ifdef STRIP_DEBUG
  printf("IP proto %d payload len %d ", ipp->ip_p, pp->len);
#endif

#ifndef NIC_STAMP
  /*
   * Drive the clock and check timeouts
   */
  TV_TO_US(pp->tstamp, time_now);
  pp->arr_tm = time_now;
#else
#if defined(ACENIC)
  time_now = pp->arr_tm;
  pp->tstamp.tv_sec = (long)(time_now/1000000);
  pp->tstamp.tv_usec = (long)(time_now%1000000);
#elif defined(SK98)
  time_now = pp->arr_tm;
  pp->tstamp.tv_sec = (long)(time_now/1000000);
  pp->tstamp.tv_usec = (long)(time_now%1000000);  
#else
  XXX
#endif
#endif

#ifdef TCPDUMP_FED
  /*
   * Use time stamps to drive clock as substitute for current system time
   *  - use first packet time as run start
   */
  curr_tm_tv = pp->tstamp;
  if (first_pkt)
    {
      counters.start = pp->tstamp;
      first_pkt = 0;
    }
  else
    {
      //assert (time_now - last_pkt < 10000000);
      ;
    }
  last_pkt = time_now;
  
#endif

  check_timeouts(time_now);

#if 0
  fprintf(stderr, "tstamp: %ld  %ld\n", pp->tstamp.tv_sec,  pp->tstamp.tv_usec);
  gettimeofday(&ts_tmp, NULL);
  fprintf(stderr, "tstamp: %ld  %ld\n", ts_tmp.tv_sec,  ts_tmp.tv_usec);
#endif


#ifdef FLOW_FILT
extern int ffilt();

  if (ffilt(ipp))
    {
#ifdef PRINT_OUT
      printf("FLOW FILTER\n");
#endif
      BUMP_DROPS(ffilter);
      goto drop;
    }

#endif
      
  

  /* find hash value for this host/host flow */
  pp->flowlist = &host_hashtbl[hash_flow(ipp)];

  switch (ipp->ip_p)
    {
    case IPPROTO_TCP:
      /* find hash value for this host/host flow */
      pp->flowlist = &host_hashtbl[hash_flow(ipp)]; 
      TIME_NP_CALL("do_ip - TCP", &call_times.tcp, do_tcp(pp, ipp, time_now));
      goto done;		/* TCP stuff responsible for freeing buffers */
      break;
    case IPPROTO_UDP:
      /* find hash value for this host/host flow */
      pp->flowlist = &host_hashtbl[hash_flow(ipp)]; 
      TIME_NP_CALL("do_ip - UDP", &call_times.udp, do_udp(pp, ipp, time_now));
      goto done;
      break;
    case IPPROTO_ICMP: 
      do_icmp(pp, time_now);
      goto drop;
      break;
    default: 
      BUMP_DROPS(TRAFF_IP_OTHER);

      if (dump_pkt_types & DUMP_IP_PKTS)
	pkt_dump(pp, HTTP_ERR_NONE, NULL, ATM_DATA);
      goto drop;
      break;
    }

  BUMP_DROPS(not_TCPUDP);

 drop:
      FREE_BUFF(buf_start(pp));
      FREE_BUFREC(pp->brecp);
      FREE_PREC(pp);

 done:

  return 0;
} 

/*
 * end webtraffic.c 
 */
  


  
