/*  -*- 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/time.h>
#include <sys/resource.h>
#include <sys/socket.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>
#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 <net/ethernet.h>

#include "linux_tweaks.h"

#endif

#include <assert.h>

#include "list.h"
#include "pkt.h"
#include "interface.h"
#include "flows.h"
#include "http.h"
#include "tcp.h"
#include "service.h"
#include "udp.h"
#include "udp_ns.h"
#include "timeouts.h"
#include "print_util.h"

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

#include "output.h"
#include "pool.h"
#include "tcp_test.h"
#include "sundry_records.h"

#include "config.h"

/* TMP */
int held = 0, held_empty = 0;


#ifdef SEQ_DEBUG
#include "print_util.h"
/*
 * Just for debugging purposes - print list of held pkts 
 */

void 
phl(listhdr_t *lp)
{
  tcp_heldpkt_t *hpp;
  
  L_WALK(hpp, lp, links, tcp_heldpkt_t)
    {
      unsigned int seq = hpp->seq;
      unsigned int len = hpp->len;
      unsigned int firstseq = hpp->tsp->firstseq;
      unsigned int printseq = seq - firstseq;
      fprintf(stdout, "\t %#x %u+%u=%u (%u+%u=%u) %s %s\n", 
	      (unsigned int)hpp, printseq, len, 
	      printseq+len, 
	      seq, len, seq+len, IS_ON_TIMEO_Q(hpp) ? "*" : "",
	      (hpp->links.lnext != lp 
	         && seq+len != ((tcp_heldpkt_t *)hpp->links.lnext)->seq) ? 
	      "-" : "");
    }
  
  return;
}

void 
ptconn(tcp_conn_t *tconnp) 
{

  fprintf(stdout, "%s:%s<>%s:%s\n", 
	  get_hname((char *)&((host_flow_t *)tconnp->flow_common.hconnp)->srcaddr),
	  tcpudp_port_string(ntohs(tconnp->flow_common.inner.srcport), FLOW_TCP),
	  get_hname((char *)&((host_flow_t *)tconnp->flow_common.hconnp)->dstaddr),
	  tcpudp_port_string(ntohs(tconnp->flow_common.inner.dstport), FLOW_TCP));
  
  return;
}

#endif

void 
held_pkt_list_insert_head(listhdr_t *lp, tcp_heldpkt_t *hpp)
{
  assert(!IS_ON_HELD_LIST(hpp) && !IS_ON_TIMEO_Q(hpp));
  L_INS_HEAD(lp, hpp, links, tcp_heldpkt_t);
  hpp->status |= PKT_ON_HELD_LIST;
  hpp->tsp->nheld++;

  assert(lp->lnext == (list_t *)hpp && hpp->links.lprev == lp);

  return;
}

void 
held_pkt_list_insert_tail(listhdr_t *lp, tcp_heldpkt_t *hpp)
{
  assert(!IS_ON_HELD_LIST(hpp) && !IS_ON_TIMEO_Q(hpp));
  L_INS_TAIL(lp, hpp, links, tcp_heldpkt_t);
  hpp->status |= PKT_ON_HELD_LIST;
  hpp->tsp->nheld++;

  assert(lp->lprev == (list_t *)hpp && hpp->links.lnext == lp);

  return;
}

void 
held_pkt_list_insert_after(listhdr_t *lp, tcp_heldpkt_t *hpp, 
			   tcp_heldpkt_t *after)
{
   assert(!IS_ON_HELD_LIST(hpp) && !IS_ON_TIMEO_Q(hpp) && IS_ON_HELD_LIST(after));

   L_INS_AFTER(lp, after, hpp, links, tcp_heldpkt_t);
   hpp->status |= PKT_ON_HELD_LIST;
   hpp->tsp->nheld++;

   assert(hpp->links.lprev == (list_t *)after 
	  && after->links.lnext == (list_t *)hpp);

   return;
 }

void 
held_pkt_list_insert_before(listhdr_t *lp, tcp_heldpkt_t *hpp, 
			   tcp_heldpkt_t *before)
{
   assert(!IS_ON_HELD_LIST(hpp) && !IS_ON_TIMEO_Q(hpp) && IS_ON_HELD_LIST(before));

   L_INS_BEFORE(lp, before, hpp, links, tcp_heldpkt_t);
   hpp->status |= PKT_ON_HELD_LIST;
   hpp->tsp->nheld++;

   assert(hpp->links.lnext == (list_t *)before 
	  && before->links.lprev == (list_t *)hpp);

   return;
 }

void 
held_pkt_list_rem(listhdr_t *lp, tcp_heldpkt_t *hpp)
{
  assert(IS_ON_HELD_LIST(hpp) && !IS_ON_TIMEO_Q(hpp));

  L_REM(lp, hpp, links, tcp_heldpkt_t);
  hpp->status &= ~PKT_ON_HELD_LIST;
  hpp->tsp->nheld--;

  /* XXX TMP */
  hpp->links.lprev = hpp->links.lnext = NULL;

  return;
}
   

  

void 
tcp_hold(tcp_conn_t *tconnp, tcp_simplex_flow_t *tsp, 
	 prec_t *pp, unsigned int seq, unsigned int ack, int len, unsigned char flags)
{
  int diff;
  tcp_heldpkt_t *hpp, *memp;
  listhdr_t *lp = &tsp->held_list;

  hpp = get_tcp_heldpkt_t();

  hpp->seq = seq;
  hpp->ack = ack;
  hpp->len = len;
  hpp->status = 0U;
  hpp->tcp_flags |= flags;
  hpp->tsp = tsp;
  hpp->tconnp = tconnp;
  hpp->arr_tm = pp->arr_tm;

  /* if packet is empty don't need to hang on to buffer - have all we need */
  if (len == 0)
    {
      BUMP_INFO(held_epkts);
      hpp->pp = NULL;
      FREE_INBUF(pp);
    }
  else
    {
      BUMP_INFO(held_pkts);
      hpp->pp = pp;
    }
      

/* TMP */
if (len) held++; else held_empty++;

  /* 
   * Insert into simplex tcp flow's held packet list 
   * - want to maintain an ordered list of held packets. Most common situation 
   * is subsequent packets arriving after a sequence gap, so walk list in 
   * reverse to find position. 
   */

  /* 
   * Some TCP implementation out there (x.x.hotmail.com if no other) 
   * sends RST with a rubbish SEQ  which will break this
   * - patch up SEQ 
   */

  if(flags & TH_RST)
    {
      if ((tsp->state & TSP_SEQUENCING) && seq != tsp->solidseq)
	{
	  /* a silly one  */
#ifdef FOO
	  printf("DEAR-ME - here's another\n");
	  report_tcp_conn(tconnp, 0);
#endif /* FOO */
	  hpp->seq = seq = tsp->solidseq;
	}
    }

  if (tsp->state & TSP_SEQUENCING) /* must be a gap or patched RST */
    assert((int)(seq - tsp->solidseq) > 0 || (flags & TH_RST));
      
  /* XXXTODO - make sure a RST stays at end of list? */
  L_WALK_REV(memp, lp, links, tcp_heldpkt_t)
    {
      diff = memp->seq - seq;
      if (diff <= 0L)
	/* this packet is same seq or later - we're there */
	break;
    }
  
  if (memp == (tcp_heldpkt_t *)lp)
    {
      /* this is first or only pkt on list */
      held_pkt_list_insert_head(lp, hpp);
    }
  else
    {
      held_pkt_list_insert_after(lp, hpp, memp);
    }

#ifdef SEQ_DEBUG
  {
    assert(((int)(hpp->seq - ((tcp_heldpkt_t *)hpp->links.lprev)->seq)) >= 0 
	   || hpp->links.lprev == lp);
    assert(((int)((tcp_heldpkt_t *)hpp->links.lnext)->seq - hpp->seq) >= 0 
	   || hpp->links.lnext == lp);

  assert(!L_EMPTY(lp));
  assert(IS_ON_HELD_LIST(hpp));
  assert(hpp->links.lnext != NULL && hpp->links.lprev != NULL);
  }
#endif

  if (hpp->links.lprev == lp)
    {
      /* first on list - insert into timeo_q */
      seq_timeo_q_insert(hpp);
      
      if (hpp->links.lnext != lp)
	/* something already there - remove from timeo_q? */
	{
	  memp = (tcp_heldpkt_t *)hpp->links.lnext;
	  assert(IS_ON_TIMEO_Q(memp));
	  seq_timeo_q_rem(memp);
	}
    }

  if (hpp->links.lnext != lp)
    /* not last on list - must be out of order */
    {
      tsp->ooo_pkts++;
      tsp->ooo_bytes += len;
    }

  TCP_TEST_PKT_HOLD(tconnp, tsp, hpp);

  return;
}

/*
 * Examine held packet list for the connection, can any packets now be 
 * processed?
 * Only call if list known not empty, first on list s/be on seq timeo_q
 */

/* TODO - does this need return value? */


int 
tcp_catch_up(tcp_simplex_flow_t *tsp, tcp_conn_t *tconnp, int way, int why)
{
  listhdr_t *lp = &tsp->held_list;
  tcp_heldpkt_t *tmp = NULL, *memp = (tcp_heldpkt_t *)lp->lnext;
  int diff = (int)(memp->seq - tsp->solidseq);

  unsigned int tm;

  assert(!L_EMPTY(lp));
  assert(IS_ON_TIMEO_Q(memp));

  if (diff > 0)
    /* still a gap */
    return 0;
  else
    /* at least one pkt to do */
    seq_timeo_q_rem(memp);

  while ((diff = (int)(memp->seq - tsp->solidseq)) <= 0)
    {
      unsigned int seq = memp->seq;
      int len = memp->len;
      unsigned char flags = memp->tcp_flags;
      tcp_simplex_flow_t *tsp = 
	(way == SERVER ? &tconnp->tcp.server : &tconnp->tcp.client);

      if (diff < 0)
	{
	  /* lower seq */
	  unsigned int end = seq + len;
	  int pastend = (int)(end - tsp->solidseq);
	  if (pastend <= 0)
	    {
	      /* all seen already */
	      tsp->duplicate_pkts++;
	      if (len)
		{
		  tsp->duplicate_bytes += len;
		  FREE_INBUF(memp->pp);
		}
	    }
	  else
	    {
	      /* some not previously seen */
	      tm = (why & CATCHUP_USE_CURRENT_TM) ? tsp->rst_us : 
		(unsigned long)(memp->arr_tm - memp->TCP_OPEN_TM);
	      tcp_pkt(memp->pp, tconnp, tsp, way, seq, len, memp->ack, flags,
		      tm);
	    }
	}
      else
	{
	  /* in sequence - no gap */
	  tm = (why & CATCHUP_USE_CURRENT_TM) ? tsp->rst_us : 
	    (unsigned long)(memp->arr_tm - memp->TCP_OPEN_TM);
	  tcp_pkt(memp->pp, tconnp, tsp, way, seq, len, memp->ack, flags, tm);
	}

      tmp = (tcp_heldpkt_t *)memp->links.lnext;

      held_pkt_list_rem(lp, memp);
      recycle_tcp_heldpkt_t(memp);/* buffer and pkt record freed by tcp_pkt() */
      
      if (tmp == (tcp_heldpkt_t *)lp)
	{
	  /* end of list */
	  break;
	}
      else
	{
	  /* more to check - carry on */
	  memp = tmp;
	} 
    } /* end while */

  /* 
   * Have now either emptied list or come across another gap 
   * - in which case put on timeo_q
   */

  if (!L_EMPTY(lp))
    seq_timeo_q_insert((tcp_heldpkt_t *)lp->lnext);

  return 0;
}

/*
 * Called to tidy up a tcp connection record 
 * - to get here must either have seen 
 *   (a) a full, effective, or forced TCP close, 
 *   (b) the connection has timed out, or 
 *   (c) we're tidying up at the end of a run.
 */
void 
free_held_list(tcp_conn_t *tconnp)
{
  int i;
  listhdr_t *lp;

  for (i = SERVER, lp = &tconnp->tcp.client.held_list; 
       i < CLIENT+1;
       i++, lp = &tconnp->tcp.server.held_list)
    {
      tcp_heldpkt_t *tmp, *memp = (tcp_heldpkt_t *)lp->lnext;

      /* 
       * tidying up in cases (b) and (c) will have emptied the held lists 
       * - the only reason to be holding a packet is a re-transmitted FIN ACK 
       *   - left on the list because packet processing terminated as case (a)
       */

      while (memp != (tcp_heldpkt_t *)lp)
	{
	  prec_t *pp = memp->pp;
	  assert((TCP_STATE & TCP_FULL_CLOSE)
		 && memp->ack == (memp->tsp->state & TSP_CLIENT) ? 
		 memp->tconnp->tcp.server.solidseq 
		 : memp->tsp->solidseq);
	  memp->tsp->duplicate_pkts++;
	  memp->tsp->duplicate_bytes += memp->len;
	  tmp = memp;
	  memp = (tcp_heldpkt_t *)memp->links.lnext;
	  if (pp)
	    FREE_INBUF(pp);
	  if (IS_ON_TIMEO_Q(tmp))
	    seq_timeo_q_rem(tmp);
	  held_pkt_list_rem(lp, tmp);
	  recycle_tcp_heldpkt_t(tmp);
	}

      lp->lnext = lp->lprev = lp;
    }

  return;
}

#ifndef FINAL_REPORT

/*
 * TCP sequence gap timeout has occurred 
 * - packet not yet removed from seq timeo_q
 */

void 
tcp_seq_timeout(tcp_heldpkt_t *hpp, int why)
{
  tcp_simplex_flow_t *tsp = hpp->tsp;
  tcp_conn_t *tconnp = hpp->tconnp;
  int way = tsp->state & (TSP_CLIENT | TSP_SERVER);
  int gap;
  unsigned int tm;

  assert(!L_EMPTY(&tsp->held_list));
  assert(IS_ON_TIMEO_Q(hpp));
  assert(way);

#if defined SEQ_DEBUG && !defined BIN_REPORT
  if (hpp != (tcp_heldpkt_t *)tsp->held_list.lnext)
    {
      fprintf(repfile, "%#x %u %u\n", hpp, hpp->seq, hpp->len);
      phl(&tsp->held_list);
      fprintf(repfile, "\n");
    }
#endif

  assert(hpp == (tcp_heldpkt_t *)tsp->held_list.lnext);
  assert(tsp->state & TSP_SEQUENCING);

  gap = hpp->seq - tsp->solidseq; /* must be a gap to be here */
  tsp->gap_bytes += gap;
  tsp->gap_pkts++;
  
  /* 
   * Inform service of seq gap 
   * - service MUST mark whether recovered or not and treat all 
   * subsequent packets accordingly 
   */
  tm = (why & CATCHUP_USE_CURRENT_TM) ? tsp->rst_us 
    : (unsigned long)(hpp->arr_tm - hpp->TCP_OPEN_TM);
  TCP_SERV_SYNC(hpp, tconnp, tsp, gap, way, tm);

  /* patch up seq */
  tsp->solidseq += gap;

  /* now process packet and any continuous followers */
  TIME_NP_CALL("TCP: catchup timeout", &call_times.tcp_catchup, 
	       tcp_catch_up(tsp, tconnp, way, why));
  
  return;
}

#endif /* ifndef FINAL_REPORT */


/*
 * end tcp_seq.c 
 */




