
/*  -*- 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 <unistd.h>
#include <sys/fcntl.h>
#include <errno.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>
#include <netinet/udp.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"

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

#include "report.h"
#include "output.h"
#include "if_nprobe.h"
#include "pool.h"
#include "writer.h"
#include "print_util.h"
#include "interesting.h"
#include "sundry_records.h"

#include "tcp_test.h"

#include "config.h"
#include "probe_config.h"


us_clock_t started_us;

struct tcp_closing tcp_closing[2] =
  {
    {
      TCP_CLOSING_STATE,
      TCP_VALID_CLOSING_STATES
    },
    {
      TCP_CLOSING_STATE_QUICKCLOSE,
      TCP_VALID_CLOSING_STATES_QUICKCLOSE
    }
  };

unsigned int tcp_closing_state, tcp_valid_closing_states;

/*
 * For de-bugging only 
 */

void 
p_conn_status(unsigned int status)
{
  if (status & TCP_SERVER_SEEN) printf("SERVER ");
  if (status & TCP_CLIENT_SEEN) printf("CLIENT ");
  if (status & TCP_SERV_SYN) printf("S_SYN ");
  if (status & TCP_CLI_SYN) printf("C_SYN ");
  if (status & TCP_SERV_ACKSYN) printf("S_ACKSYN ");
  if (status & TCP_CLI_ACKSYN) printf("C_ACKSYN ");
  if (status & TCP_SERV_FIN) printf("S_FIN ");
  if (status & TCP_CLI_FIN) printf("C_FIN ");
  if (status & TCP_SERV_ACKFIN) printf("S_ACKFIN ");
  if (status & TCP_CLI_ACKFIN) printf("C_ACKFIN ");
  if (status & TCP_CLI_RST) printf("C_RST ");
  if (status & TCP_SERV_RST) printf("S_RST ");
  if (status & TCP_CLI_SERV_ERR) printf("C_SERV_ERR ");
  if (status & TCP_SERV_SERV_ERR) printf("S_SERV_ERR ");
  if (status & TCP_FULL_CLOSE) printf("CLOSE ");
  if (status & TCP_EFFECTIVE_CLOSE) printf("EFF_CLOSE ");
  if (status & TCP_TIMEO) printf("TIMEO ");
  if (status & TCP_RUNEND) printf("RUNEND ");
  if (status & TCP_FORCED_CLOSE) printf("FORCED_CLOSE");
  if (status & TCP_FORCED_OPEN) printf("FORCED_OPEN");
  if (status & TCP_FORCED_ALT) printf("FORCED_ALT");
  if (status & TCP_QUICK_CLOSE) printf("QUICK ");
  if (status & TCP_IS_ON_CONNLIST) printf("*CONNLIST ");
  if (status & TCP_CONN_ERROR) printf("ERROR ");
  printf("\n");
  return;
}

void 
p_simplex_state(unsigned int state)
{
  if (state & TSP_SERVER) printf("**Server:-\n");
  if (state & TSP_CLIENT) printf("**Client:-\n ");
  if (state & TSP_SYN) printf("SYN ");
  if (state & TSP_FIN) printf("FIN ");
  if (state & TSP_RST) printf("RST ");
  if (state & TSP_SEQ_TIMEO) printf("SEQ_TIMEO ");
  if (state & TSP_SEQ_TIMEO_FORCED) printf("SEQ_TIMEO_FORCED ");
  if (state & TSP_SEQ_TIMEO_QL) printf("SEQ_TIMEO_QL ");
  if (state & TSP_SEQ_TIMEO_ACK) printf("SEQ_TIMEO_ACK ");
  if (state & TSP_DUP_SYN) printf("DUP_SYN ");
  if (state & TSP_SYN_TOO_LATE) printf("SYN_TOO_LATE ");
  if (state & TSP_MAVERICK_SYN) printf("_MAVERICK_SYN ");
  if (state & TSP_FORCED_OPEN) printf("FORCED_OPEN ");
  if (state & TSP_FORCED_CLOSE) printf("_FORCED_CLOSE ");
  if (state & TSP_FORCED_ALT) printf("_FORCED_ALT ");
  if (state & TSP_ACKING) printf("ACKING ");
  if (state & TSP_SEQUENCING) printf("SEQUENCING ");
  printf("\n");

  return;
}

void
p_simplex(tcp_simplex_flow_t *s, unsigned int start, unsigned int first, 
	  unsigned int last)
{
   unsigned int syn = s->syn_us;
   unsigned int fin = s->fin_us;
   unsigned int rst = s->rst_us;
   unsigned int firstdata = s->firstdata_us;
   unsigned int lastdata = s->lastdata_us;

  p_simplex_state(s->state);
  printf("1stACK     \t%12u\n", s->firstack);
  printf("laststACK  \t%12u\n", s->lastack);
  printf("1stSEQ     \t%12u\n", s->firstseq);
  printf("solidSEQ   \t%12u\n", s->solidseq);
  printf("%u pkts %u epkts %u bytes\n", s->tot_pkts, s->tot_e_pkts, 
	 s->tot_bytes);

  printf("FIRST       ");
  if (first == 0LL) printf("\tX\n"); else printf("\t%8d\t%d\n", first-start, first);
  printf("SYN         ");
  if (syn == 0LL) printf("\tX\n"); else printf("\t%8d\t%d\n", syn-start, syn);
  printf("Firstdata   ");
  if (firstdata == 0LL) printf("\tX\n"); else printf("\t%8d\t%d\n", firstdata-start, firstdata);
  printf("Lastdata    ");
  if (lastdata == 0LL) printf("\tX\n"); else printf("\t%8d\t%d\n", lastdata-start, lastdata);
  printf("FIN         ");
  if (fin == 0LL) printf("\tX\n"); else printf("\t%8d\t%d\n", fin-start, fin);
  printf("RST         ");
  if (rst == 0LL) printf("\tX\n"); else printf("\t%8d\t%d\n", rst-start, rst);
  printf("LAST        ");
  if (last == 0LL) printf("\tX\n"); else printf("\t%8d\t%d\n", last-start, last);

  return;
}

void 
p_http_status(unsigned int s)
{
  if(s & HTTP_GOT_REQ_HDR) printf("GOT_REQ_HDR ");
  if(s & HTTP_GOT_REP_HDR) printf("GOT_REP_HDR ");
  if(s & HTTP_IN_REQ_HDR) printf("IN_REQ_HDR ");
  if(s & HTTP_IN_REP_HDR) printf("IN_REP_HDR ");
  if(s & HTTP_SEEN_REQ_HDR) printf("SEEN_REQ_HDR ");
  if(s & HTTP_SEEN_REP_HDR) printf("SEEN_REP_HDR ");
  if(s & HTTP_SIMPLE_REQUEST) printf("SIMPLE_REQUEST ");
  if(s & HTTP_KEEP_ALIVE) printf("KEEP_ALIVE ");
  if(s & HTTP_CLOSE) printf("CLOSE ");
  if(s & HTTP_CLI_KEEP_ALIVE) printf("CK_ALIVE ");
  if(s & HTTP_SERV_KEEP_ALIVE) printf("SK_ALIVE ");
  if(s & HTTP_CLI_CLOSE) printf("CLI_CLOSE ");
  if(s & HTTP_SERV_CLOSE) printf("SERV_CLOSE ");
  if(s & HTTP_WAS_PERSISTENT) printf("PERSISTENT ");
  printf("\n");

  return;
}

int
p_inf_status(unsigned short s)
{
  if(s & TRANS_ERR) printf("ERR ");
  if(s & TRANS_INCOMPLETE) printf("INCOMPLETE ");
  if(s & TRANS_FINISHED) printf("FINISHED ");
  if(s & TRANS_LINKS_CHARS_EX) printf("LINKS_CHARS_EX ");
  if(s & TRANS_CHUNKED) printf("CHUNKED ");
  if(s & TRANS_HDR_FRAG) printf("HDR_FRAG ");
  if(s & TRANS_RESYNCHED) printf("RESYNCHED ");
  if(s & TRANS_LOST_SYNCH) printf("LOST_SYNCH ");
  if(s & TRANS_OBJ_INTERRUPTED) printf("INTERRUPTED ");
  if(s & TRANS_DUMMY_UNSYNCH) printf("DUMMY_UNSYNCH ");
  if(s & TRANS_DUMMY_ERR) printf("DUMMY_ERR ");
  printf("\n");

  return (s & TRANS_ERR) ? 1 : 0;
}

void 
p_inf(http_transinf_t *t)
{
  if (p_inf_status(t->status))
    printf("error %s\n", http_err_string(t->error));
  
  printf("type %hu bodylen %d recdlen %u recdpkts %u\n", t->content_type, 
	 t->body_len, t->recd_len, t->recd_pkts);

  return;
}

void 
p_trans(http_trans_t *tp, unsigned int start)
{
  http_trans_inner_t *p = &tp->inner;
  http_trans_sinf_t *sp = &p->sinf;
  http_trans_cinf_t *cp = &p->cinf;

  printf("reqstart         ");
  if (cp->reqstart_us != 0LL) printf("\t%8d\t%8d\n", cp->reqstart_us-start, cp->reqstart_us);
  else printf("\tX\n");

  printf("reqend         ");
  if (cp->reqend_us != 0LL) printf("\t%8d\t%8d\n", cp->reqend_us-start, cp->reqend_us);
  else printf("\tX\n");

  printf("repstart    ");
  if (sp->repstart_us != 0LL) printf("\t%8d\t%8d\n", sp->repstart_us-start, sp->repstart_us);
  else printf("\tX\n");

  printf("repend      ");
  if (sp->repend_us != 0LL) printf("\t%8d\t%8d\n", sp->repend_us-start, sp->repend_us);
  else printf("\tX\n");

  printf("last sdata  ");
  //if (p->last_sdata_pkt_us != 0LL) printf("\t%8ld\t%8ld\n", p->last_sdata_pkt_us-start, p->last_sdata_pkt_us);
  if (p->sinf.repend_us != 0LL) printf("\t%8d\t%8d\n", p->sinf.repend_us-start, p->sinf.repend_us);
  else printf("\tX\n");

  printf("**Client:-\n");
  if (p->c.ti.status & TRANS_VAL)
    p_inf(&p->c.ti);
  else
    printf("Not valid\n");
  printf("**Server:-\n");
  if (p->s.ti.status & TRANS_VAL)
    p_inf(&p->s.ti);
  else
    printf("Not valid\n");

  return;
}
  
void
p_http_data(struct tcp_conn *tconnp, unsigned int start)
{
  int i = 1;
  http_trans_t *tp;

  printf("****HTTP;-\n");
  printf("%hd trans %hd dummytrans\n", HTTP_NTRANS, HTTP_NTRANS_DUM);
  p_http_status(HTTP_STATUS);

  for(tp = tconnp->su.http.trans; tp != NULL; tp = tp->next, i++)
    {
      printf("trans #%d\n", i);
      p_trans(tp, start);
    }

  return;
}
  

void 
tcpdetails(struct tcp_conn *tconnp)
{
  unsigned int start = MIN(TCP_CFIRST_TM, TCP_SFIRST_TM);
  unsigned int end = MAX(TCP_CLAST_TM, TCP_SLAST_TM);

  if (start == 0LL)
    start = MAX(TCP_CFIRST_TM, TCP_SFIRST_TM);

  printf("****TCP-:\n");
  print_hostnames = 0;
  printf("%s:%hu -> ", get_hname((char *)&tconnp->flow_inner.srcaddr), ntohs(tconnp->flow_inner.srcport));
  printf("%s:%hu\n", get_hname((char *)&tconnp->flow_inner.dstaddr), ntohs(tconnp->flow_inner.dstport));
  print_hostnames = 1;
  printf("%s:%hu -> ", get_hname((char *)&tconnp->flow_inner.srcaddr), ntohs(tconnp->flow_inner.srcport));
  printf("%s:%hu\n", get_hname((char *)&tconnp->flow_inner.dstaddr), ntohs(tconnp->flow_inner.dstport));
  p_conn_status(TCP_STATE);
  if (TCP_LAST_ARR_TM != 0LL)
    printf("Last arrival\t%8lu\n", (unsigned long)(TCP_LAST_ARR_TM - TCP_FIRST_ARR_TM));
  else
    printf("Last arrival\t       X\n");
  if (start != 0LL)
    printf("Start\t%8d\n", start - start);
  else
    printf("Start       \t       X\n");
  if (end != 0LL)
    printf("End  \t%8d\n", end - start);
  else
    printf("End         \t        X\n");

  if (TCP_STATE & TCP_CLIENT_SEEN)
    p_simplex(&tconnp->tcp.client, start, TCP_CFIRST_TM, TCP_CLAST_TM);

  if (TCP_STATE & TCP_SERVER_SEEN)
    p_simplex(&tconnp->tcp.server, start, TCP_SFIRST_TM, TCP_SLAST_TM);

  if (tconnp->flow_inner.serv_type & TCP_SERV_HTTP)
    {
      p_http_data(tconnp, start);
    }
  return;
}
  

#if defined BIN_REPORT


/*
 * Binary output routine - dump TCP connection data on close  
 * - ** called by service level dump routine **
 */

/* TODO - alignment? - yes for alpha */
void 
tcp_dump(struct tcp_conn *tconnp, int client_seen, int server_seen)
{
  DUMP_INT(outp, tconnp->hdrs.conn_id, unsigned long);
  DUMP_STRUCT(outp, &tconnp->flow_common.inner, flow_inner_t);

  /* data on tcp connection */
  if (client_seen)
    {
     //TCP_CRST_TM = TCP_CLAST_TM; /* one fits both */
     DUMP_STRUCT(outp, &tconnp->tcp.client, tcp_simplex_flow_t);
   }
  if (server_seen)
    {
      //TCP_SRST_TM = TCP_SLAST_TM;
      DUMP_STRUCT(outp, &tconnp->tcp.server, tcp_simplex_flow_t);
    }

  assert (tconnp->hdrs.nheld <= MAX_TCP_DUMPHDRS_HELD);
  DUMP_INT(outp, tconnp->hdrs.atm, us_clock_t);
  DUMP_INT(outp, tconnp->hdrs.nheld, int);
  if (tconnp->hdrs.nheld)
    DUMP_MEM(outp, tconnp->hdrs.hdrs, sizeof(tcp_dumphdr_t)*tconnp->hdrs.nheld);
  
  return;
}

/*
 * Dump notification of new TCP connection  
 */

void 
dump_new_conn(struct tcp_conn *tconnp, us_clock_t us_time)
{
  TCP_OPEN_TM = us_time;
  TCP_HDRS_ATM = us_time;
  rec_dump_start();
  DUMP_INT(outp, tconnp->hdrs.conn_id, unsigned long);
  DUMP_STRUCT(outp, &tconnp->flow_common.inner, flow_inner_t);
  rec_dump_end(TCP_SERV_OPEN_REC_TYPE);

  return;
}

/*
 * Interim dump collection of TCP headers
 * - used when hdrs buffer is full
 */

void 
dump_hdrs(struct tcp_conn *tconnp)
{
  rec_dump_start();
  assert (TCP_HDRS_HELD == MAX_TCP_DUMPHDRS_HELD);
  DUMP_INT(outp, tconnp->hdrs.conn_id, unsigned long);
  DUMP_INT(outp, tconnp->hdrs.atm, us_clock_t);
  DUMP_MEM(outp, &TCP_HDRS, sizeof(tcp_dumphdr_t)*TCP_HDRS_HELD);
  rec_dump_end(TCP_SERV_HDRS_REC_TYPE);

  return;
}

/* 
 * Record interesting fields of TCP hdr.
 */

void 
rec_tcp_hdr(struct tcp_conn *tconnp, struct tcphdr *tcpp, unsigned int seq, 
	    unsigned int ack, unsigned short window, int way, 
	    us_clock_t tm, unsigned short len)
{
  tcp_dumphdr_t *drecp;

  if (TCP_HDRS_HELD == MAX_TCP_DUMPHDRS_HELD)
    {
      dump_hdrs(tconnp);
      TCP_HDRS_HELD = 0;
      TCP_HDRS_ATM = tm;
    }

  drecp = &TCP_HDRS[TCP_HDRS_HELD++];

  drecp->rtm = (long)(tm - TCP_HDRS_ATM);
  drecp->seq = seq;
  drecp->ack = ack;
  drecp->window = window;
  drecp->len = len;
  drecp->flags = tcpp->th_flags;
  drecp->way = (unsigned char)way;

  return;
}

#endif /* ifdef BIN_REPORT */

/*
 * Get and return pointer to new port/port flow record, link into hash list
 */

tcp_conn_t *
new_tcp_conn(host_flow_t *hflow, int port_indx, struct tcphdr *tcpp, int way)
{
  static long current_conn_id = 0L;
  tcp_conn_t *connp;
  listhdr_t *conns = &hflow->flow_conns[port_indx];

  connp = get_tcp_conn_t();
  connp->hdrs.conn_id = current_conn_id++;
  hflow->n_conns++;
  
  if (way == CLIENT)
    {
      connp->flow_common.inner.srcport = tcpp->th_sport;
      connp->flow_common.inner.dstport = tcpp->th_dport;
      connp->flow_common.inner.srcaddr = hflow->srcaddr;
      connp->flow_common.inner.dstaddr = hflow->dstaddr;
    }
  else
    {
      connp->flow_common.inner.srcport = tcpp->th_dport;
      connp->flow_common.inner.dstport = tcpp->th_sport;
      connp->flow_common.inner.srcaddr = hflow->dstaddr;
      connp->flow_common.inner.dstaddr = hflow->srcaddr;
    }

  connp->flow_common.hconnp = hflow;
  connp->flow_common.port_indx = port_indx;
  L_INS_HEAD(conns, connp, flow_common.hlist, tcp_conn_t);
  connp->flow_common.inner.state |= TCP_IS_ON_CONNLIST; 
  return connp;
}

void 
free_tcp_conn(tcp_conn_t *tconnp)
{
  int i;
  host_flow_t *hfp = (host_flow_t *)tconnp->flow_common.hconnp;
  list_t *connlist = &hfp->flow_conns[tconnp->flow_common.port_indx];

  assert(TCP_STATE & tcp_valid_closing_states);

  //TCP_CLOSE_TM = tconnp->flow_common.last_arr_tm;
  
  if (!TCP_BOTH_SEEN)
    {
      BUMP_CTR(tcp_one_way_only);
      if (!TCP_SERVER_SEEN)
	BUMP_CTR(tcp_cli_only);
      else
	BUMP_CTR(tcp_serv_only);
    }

  TCP_SERV_DUMP(tconnp);

  free_trans(tconnp);
  free_held_list(tconnp);

  L_REM(&tcp_conn_timeo_q, tconnp, flow_common.conn_timeo_q, tcp_conn_t);


  if (tconnp->flow_common.inner.state & TCP_IS_ON_CONNLIST)
    {
      L_REM(connlist, tconnp, flow_common.hlist, tcp_conn_t);
      if (tconnp->alt_next != tconnp)
	/* at least one alternate */
	{
	  /* put on conn list */
	  L_INS_HEAD(connlist, tconnp->alt_next, flow_common.hlist, tcp_conn_t);
	  tconnp->alt_next->flow_common.inner.state |= TCP_IS_ON_CONNLIST;
	}
    }
  
  if (tconnp->alt_next != tconnp)
    /* remove from alts list */
    {
      tconnp->alt_prev->alt_next = tconnp->alt_next;
      tconnp->alt_next->alt_prev = tconnp->alt_prev;
    }
  /*
   * If dumping payloads each simplex flow dump fd is closed on seeing a FIN
   *  - but in case of timeouts, missing FINs, or quickclose this will not 
   *  apply - this catch-all takes care of it
   */

  for (i= 0; i<CLIENT; i++)
    {
      if (tconnp->payload_dump_fd[i] > 0)
	{
	  if (close(tconnp->payload_dump_fd[i]) != 0)
	    error("free_tcp_conn()", "close");
	  tconnp->payload_dump_fd[i] = -1;
	  ndumpfiles += 1;
	}
    }
  

  recycle_tcp_conn_t(tconnp);

  if (--(hfp->n_conns) == 0)
    /* no more tcp conns for this host/host flow */
    free_host_flow(hfp);

  return;
}


/*
 * Attempt to process any outstanding packets on TCP connection 
 */

void 
tconn_cleanup(tcp_conn_t *tconnp, int why)
{
  
  int i;
  listhdr_t *lp;

  for (i = 0, lp = &tconnp->tcp.client.held_list; 
       i < 2;
       i++, lp = &tconnp->tcp.server.held_list)
    {
      while (lp->lnext != lp)
	{
	  tcp_seq_timeout((tcp_heldpkt_t *)lp->lnext, why);
	}
    }

  return;
}

#define CHECK_OPT_LEN(len) \
  if ((len) != optlen) \
    {                  \
      BUMP_CTR(tcp_opt_fail);  \
      return TCP_ERR_BAD_OPTLEN; \
    }

#define CHECK_SACK_OPT_LEN \
  if ((optlen-2)%8 != 0) \
    {                  \
      BUMP_CTR(tcp_opt_fail);  \
      return TCP_ERR_BAD_OPTLEN; \
    }

#define CHECK_OPT_FLAGS(flag) \
  if (!(flags & (flag))) \
    {                  \
      BUMP_CTR(tcp_opt_fail);  \
      return TCP_ERR_BAD_OPTFLAGS; \
    }

int 
do_tcp_options(struct tcp_conn *tconnp, tcp_simplex_flow_t *tsp,  
	       char *optp, int hlen, unsigned char flags)
{
  int olen = hlen - sizeof(struct tcphdr);
  int optlen;
  unsigned char opt;

  for ( ;
       olen > 0;
       olen -= optlen, optp += optlen)
    {
      opt = optp[0];

      if (opt == TCPOPT_EOL)	/* 0 */
	{
	  olen--;
	  break;
	}

      if (opt == TCPOPT_NOP)	/* 1 */
	{
	  optlen = 1;
	  continue;
	}
      else
	{
	  optlen = optp[1];
	  if (optlen <=	0)
	    {
	      fprintf(stderr, "******* bad tcp opt opt %d len %d\n", opt, optlen);
	      return TCP_ERR_BAD_OPTLEN;
	    }
	}
       
      switch (opt)
	{
	case TCPOPT_MAXSEG:	/* 2 */
	  tsp->mss = ntohs(*((unsigned short *)&optp[2]));
	  CHECK_OPT_LEN(4);
	  break;
	case TCPOPT_WINDOW:	/* 3 */
	  CHECK_OPT_LEN(3);
	  CHECK_OPT_FLAGS(TH_SYN);
	  if (!(flags & TH_ACK))
	    {
	      tconnp->wshift_tmp = optp[2];
	    }
	  else
	    {
	      tsp->wshift = optp[2];
	      tsp->reverse->wshift = tconnp->wshift_tmp;
	    }
	  break;
	case TCPOPT_TSTAMP:	/* 8 */
	  CHECK_OPT_LEN(10);
	  break;
	case TCPOPT_SACKOK:	/* 4 */
	  CHECK_OPT_LEN(2);
	  CHECK_OPT_FLAGS(TH_SYN);
	  break;
	case TCPOPT_SACK:	/* 5 */
	  CHECK_SACK_OPT_LEN;
	  break;
	default:
	  break;
	}
    }
#if 0
// may just mean the padding is wrong - we use hlen to step over, assume this is right, so just record as interesting
    if (olen != 0) */
      return TCP_ERR_BAD_OPTLEN; */
    else */
      return 0; */
#endif
  if (olen != 0)
    INTERESTING(&tconnp->flow_common.inner, tsp->state & 0x3, 
		"TCP options claimed length %d actually %d ", 
		hlen - sizeof(struct tcphdr), hlen - sizeof(struct tcphdr)-olen);

  return 0;
  
}


/* 
 * process an ordered TCP packet (may be preceeded by gaps) 
 * - solidseq already adjusted 
 */
void 
tcp_pkt(prec_t *pp, struct tcp_conn *tconnp, tcp_simplex_flow_t *tsp, int way, unsigned int seq, int len, unsigned int ack, unsigned char flags, unsigned int tm)
{
  int lastpkt = 0;
  unsigned int highseq = tsp->solidseq;
  int olap = (int)(highseq - seq);
  unsigned int newhigh = seq + len;

  //assert(tsp->solidseq == seq || (int)(tsp->solidseq - seq) < len);
  assert(olap >= 0 && SEQ_GTE(newhigh, highseq));

  if (olap)
    {
      tsp->duplicate_bytes += olap;
      tsp->duplicate_pkts++;
      ADJUST(pp, olap);
    }

  TCP_TEST_PKT_DO(tconnp, tsp, way, seq, len, flags);

  if (flags & TH_FIN)
    {
      /* service level may need to know this */
      tsp->state |= TSP_FIN;
      tsp->fin_us = tm;
      TCP_STATE |= (way == SERVER ? TCP_SERV_FIN : TCP_CLI_FIN);
      newhigh += 1; /* now marks ack expected */
      lastpkt++; 

      /* Close payload dump file asap to free up fd for reuse */
      if (tconnp->payload_dump_fd[way-1] > 0)
	{
	  if (close(tconnp->payload_dump_fd[way-1]) != 0)
	    error("dump_payload 1", "close");
	  tconnp->payload_dump_fd[way-1] = -1;
	  ndumpfiles += 1;
	}
    }
#if 0
  if (flags & TH_ACK)
    {
      if (tsp->state & TSP_ACKING)
	{
	  tsp->lastack = MAX_SEQ(ack, tsp->lastack);
	}
      else
	{
	  tsp->state |= TSP_ACKING;
	  tsp->firstack = ack;
	  tsp->lastack = ack;
	}
      if (tsp->reverse->state & TSP_SEQUENCING)
	if (ack == tsp->reverse->firstseq + 1)
	  {
	    tsp->reverse->state |= TSP_ACKSYN;
	    tsp->reverse->acksyn_us = tm;
	  }
    }
#endif
  /* reset ? */
  if (flags & TH_RST)
    {
      lastpkt++;
      TCP_STATE |= (way == SERVER ? TCP_SERV_RST : TCP_CLI_RST);
      tsp->state |= TSP_RST;
      tsp->rst_us = tm;
    }

  if (TCP_BOTH_SEEN)
    {
      tcp_simplex_flow_t *other_tsp = tsp->reverse;
	
      if (tsp->state & TSP_RST)
	{
	  if (other_tsp->state & (TSP_FIN | TSP_RST))
	    {
	      TCP_STATE |= TCP_EFFECTIVE_CLOSE;
	      BUMP_CTR(tcp_eff_close);
	    }
	  else if ((other_tsp->state & TSP_ACKING) 
		   && SEQ_GTE(other_tsp->lastack, newhigh))
	    {
	      TCP_STATE |= TCP_EFFECTIVE_CLOSE;
	      BUMP_CTR(tcp_eff_close);
	    }
	}
      else if ((tsp->state & TSP_FIN))
	{
	  if ((other_tsp->state & TSP_FIN) 
	      && (flags & TH_ACK) 
	      && tsp->lastack == other_tsp->solidseq
	      && other_tsp->lastack == newhigh)
	    {
	      /* closed */
	      TCP_STATE |= TCP_FULL_CLOSE;
	      BUMP_CTR(tcp_full_close);
	    }
	  else if (other_tsp->state & TSP_RST)
	    {
	      TCP_STATE |= TCP_EFFECTIVE_CLOSE;
	      BUMP_CTR(tcp_eff_close);
	    }
	}
      else if ((other_tsp->state & TSP_RST) 
	       && (flags & TH_ACK) 
	       && SEQ_GTE(tsp->lastack, other_tsp->solidseq))
	{
	  TCP_STATE |= TCP_EFFECTIVE_CLOSE;
	      BUMP_CTR(tcp_eff_close);
	}
    }
  else 
    {
      /* only one way seen */
      if (tsp->state & TSP_RST)
	{
	  TCP_STATE |= TCP_EFFECTIVE_CLOSE;
	  BUMP_CTR(tcp_eff_close);
	}
      else if (tsp->state & TSP_FIN && tcp_quickclose)
	{
	  TCP_STATE |= TCP_QUICK_CLOSE;
	  BUMP_CTR(tcp_quick_close);
	}
      else
	{
	  /* 
	   * just can't tell - this side may carry on ACKing 
	   * - will eventually just have to time out 
	   */
	  ;
	}
    }

  /* set effective time for service level */
  tconnp->service_tm = tm;
     
  if (pp)
    {
      if (pp->len)
	{
	  /*
           * Here's where we deal with ordered TCP payload
           */
	  if (tconnp->serv_control->cflags & DUMP_PAYLOAD)
	    {
	      /*
               * Dumping TCP simplex payloads -
               *  Only valid if can start at first payload packet (fd = 0)
               *   if can't open a file then mark by setting fd = -1 to 
               * avoid trying again on subsequent packets
               */
	      if (tconnp->payload_dump_fd[way-1] == 0)
		{
		  /* first packet - not started dumping yet */
		  
		  char path[512];
		  int pathlen = strlen(payload_dirnm);
		  int rc;

		  if (ndumpfiles > 0)
		    {
		      /* there's fd.s available */
		      strcpy(path, payload_dirnm);
		      sprintf(&path[pathlen], "%u.%c", TCP_CONN_ID, way == SERVER ? 's' : 'c');
		      rc = open(path, O_WRONLY | O_CREAT | O_TRUNC, 00666 );
		      if (rc < 0)
			{
			  if (errno == EMFILE || errno == ENFILE)
			    {
			      /* shouldn't happen */
			      BUMP_CTR(tcp_payload_dropped);
			      tconnp->payload_dump_fd[way-1] = -1;
			    }
			  else
			    {
			      char errbuf[256];
			      sprintf(errbuf, "write_object %s", path);
			      error(errbuf,  "creat:");
			    }
			}
		      else
			{
			  tconnp->payload_dump_fd[way-1]  = rc;
			  ndumpfiles -= 1;
			}
		    }
		  else
		    {
		      /* no fd available */
		      tconnp->payload_dump_fd[way-1] = -1;
		    }
		  
		}
	      
	      /* dump payload if a file for it */
	      if (tconnp->payload_dump_fd[way-1] > 0)
		{
		  if (write(tconnp->payload_dump_fd[way-1], pp->buf, pp->len) != pp->len )
		    {
		      error("write_object", "write:");
		    }	  
		}
	    }
	  
	  if (!(tsp->state & TSP_RST))
	    TIME_NP_CALL("TCP: tcp_pkt", &call_times.tcp_serv, 
			 TCP_SERV_PKT(pp, tconnp, way));
	}
      else
	{
	  tsp->tot_e_pkts++;
	}
      if (lastpkt)
	{
	  TCP_SERV_CLOSE(pp, tconnp, way, flags);
	}
      
      FREE_INBUF(pp);
    }
  else
    {
      tsp->tot_e_pkts++;
      if (lastpkt)
	{
	  TCP_SERV_CLOSE(pp, tconnp, way, flags);
	}
    }

  tsp->solidseq = newhigh;

  TCP_TEST_PKT_DO_END(tconnp, tsp);
  
  return;
}

/*
 * In case of infeasable packet of a connection and no live and feasable 
 * alternative shelve existing connection and start over
 */

tcp_conn_t *
force_alt_conn(tcp_conn_t *tconnp, tcp_simplex_flow_t **tspp, 
	       struct tcphdr *tcpp, int way, unsigned int len, us_clock_t us_time)
{
  
  tcp_conn_t *tconnp_tmp, *walk, *walkstart;
  host_flow_t *hfp = (host_flow_t *)tconnp->flow_common.hconnp;
  list_t *connlist = &hfp->flow_conns[tconnp->flow_common.port_indx];
  tcp_simplex_flow_t *tsp = *tspp, *tsp_tmp;
  
  tconnp_tmp = new_tcp_conn(tconnp->flow_common.hconnp, 
			    tconnp->flow_common.port_indx, 
			    tcpp, way);
  tconnp_tmp->serv_control = tconnp->serv_control; /* struct assignment */
  tconnp_tmp->flow_common.inner.serv_type = tconnp->flow_common.inner.serv_type;
  L_INS_TAIL(&tcp_conn_timeo_q, tconnp_tmp, flow_common.conn_timeo_q, tcp_conn_t);
  TCP_STATE |= TCP_FORCED_ALT;
  tsp->state |= TSP_FORCED_ALT;
  BUMP_CTR(tcp_forced_alt);

  if (tconnp->serv_control->cflags & DUMP_OPEN)
    dump_new_conn(tconnp_tmp, us_time);

  /* find which of orig or its alts is on conn list and remove */
  walkstart = tconnp;
  for (walk = walkstart; walk->alt_next != walkstart; walk = walk->alt_next)
    if (walk->flow_common.inner.state & TCP_IS_ON_CONNLIST)
      break;
  assert(walk->flow_common.inner.state & TCP_IS_ON_CONNLIST);
  L_REM(connlist, walk, flow_common.hlist, tcp_conn_t);
  walk->flow_common.inner.state &= ~TCP_IS_ON_CONNLIST;
 
  tconnp = tconnp_tmp;

  /* attach new at head of alts list */
  tconnp->alt_prev = walk->alt_prev;
  tconnp->alt_prev->alt_next = tconnp;
  walk->alt_prev = tconnp;
  tconnp->alt_next = walk;

  tsp_tmp = way == SERVER ? &tconnp->tcp.server : &tconnp->tcp.client;
  tsp_tmp->state |= TSP_FORCED_OPEN;
  TCP_STATE |= TCP_FORCED_OPEN;

  *tspp = tsp_tmp;
  return tconnp;
}

/*
 * Is an arriving packet reasonable for an existing flow?
 * - return 0 if OK, 1 if unreasonable, -1 if ignore 
 */
int 
pkt_infeasable(tcp_simplex_flow_t *tsp, unsigned int seq, unsigned int ack, unsigned char flags, 
	      unsigned int mwindow)
{
  int infeasable = 0;
  if (tsp->state & TSP_SEQUENCING)
    {
      if (flags & TH_SYN)
	{
	  if (seq == tsp->firstseq 
	      && seq + 1 == tsp->solidseq 
	      && tsp->state & TSP_SYN)
	    {
	      tsp->state |= TSP_DUP_SYN;
	      BUMP_CTR(tcp_duplicate_SYN);
	      infeasable = -1;
	    }
	  else
	    {
	      infeasable = 1;
	    }
	}
      else if (labs((int)(seq - tsp->solidseq)) > mwindow*MWINDOW_FACT)
	{
	  infeasable = 1;
	}
    }
  if (flags & TH_ACK && tsp->reverse->state & TSP_SEQUENCING)
    if (labs((int)(ack - tsp->reverse->solidseq)) 
	> tsp->reverse->mwindow*MWINDOW_FACT)
      infeasable = 1;

  return infeasable;
}

void 
ins_before(list_t *lp, tcp_conn_t *befp, tcp_conn_t *itemp)
{                                   
  list_t *back_link;
  if ((list_t *)befp == lp)
    {
      back_link = ((list_t *)befp)->lprev;                             
      ((list_t *)befp)->lprev = (list_t *)itemp;
    }
  else
    {
      back_link = befp->flow_common.conn_timeo_q.lprev;
      befp->flow_common.conn_timeo_q.lprev =  (list_t *)itemp;
    }       
  itemp->flow_common.conn_timeo_q.lnext = (list_t *)befp;       
  itemp->flow_common.conn_timeo_q.lprev = back_link; 
             
  if (back_link == lp)                           
    back_link->lnext = (list_t *)itemp;        
  else                                           
    ((tcp_conn_t *)back_link)->flow_common.conn_timeo_q.lnext = (list_t *)itemp;     
}
      
/*
 * Main TCP processing function 
 */ 
int 
do_tcp(prec_t *pp, struct ip *ipp, us_clock_t us_time)
{
  unsigned int off_time;
  struct tcphdr tcph, *tcpp;
  char *tcp_optp = NULL;
  unsigned short sport, dport;
  unsigned short window;
  unsigned int seq, ack;
  unsigned short len;
  int diff;
#ifdef PRINT_OUT
  unsigned int print_seq, print_ack;
#endif
  int hlen;
  unsigned char flags;
  int way = 0;

  int port_indx;
  tcp_conn_t *tconnp, *tconnp_hold = NULL;
  host_flow_t *hostflowp;
  tcp_simplex_flow_t *tsp;
  listhdr_t *lp;

  unsigned int mwindow;
  int infeasable;
  int err;
  int pkt_dumped = 0;


  BUMP_GLOBAL(TRAFF_TCP);

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


  if (pp->len < sizeof(struct tcphdr))
    {
#ifdef PRINT_OUT
      fprintf(repfile, "[truncated TCP hdr]\n");
#endif
      BUMP_DROPS(TCPHdr);
      goto drop;
    }

  /* may not be aligned - if so get into aligned header */
  if (((unsigned int)pp->buf & 0xffffffff) & (sizeof(int) - 1))
    {
      memcpy((void *)&tcph, (void *)pp->buf, sizeof(struct tcphdr));
      tcpp = &tcph;
    }
  else
    {
      tcpp = (struct tcphdr *)pp->buf;
    }

#ifdef CKSUM
  if(!tcp_csum(ipp, tcpp, pp))
    {
      pkt_dump(pp, TCP_ERR_CHKSUM, NULL, ATM_DATA);
      BUMP_DROPS(TCPSum);
      goto drop;
    }
#endif /* ifdef CKSUM */
      
  sport = ntohs(tcpp->th_sport);
  dport = ntohs(tcpp->th_dport);
  seq = ntohl(tcpp->th_seq);
  ack = ntohl(tcpp->th_ack);
  flags = tcpp->th_flags;
  window = ntohs(tcpp->th_win);

  if (fport && sport != fport && dport != fport)
    {
      BUMP_DROPS(port_filtered_out);
      goto drop;
    }

#ifdef PRINT_OUT
  if (print_packets)
    {

      print_ts(&pp->brecp->ts, repfile);
      fprintf(repfile, "%s:%s > %s:%s: ",
	     get_hname((char *)&ipp->ip_src), 
	      tcpport_string(sport),
	      get_hname((char *)&ipp->ip_dst) , 
	      tcpport_string(dport));
    }
#endif
  
  hlen = tcpp->th_off*4;	  

  /* Any options? */
  if (hlen > sizeof(struct tcphdr))
    {
      if (pp->len < hlen)
	{
#ifdef PRINT_OUT
	  fprintf(repfile, "[truncated TCP options]\n");
#endif
	  BUMP_DROPS(TCPHdr_opts);
	  goto drop;
	}
      else
	{ 
	  tcp_optp = pp->buf + sizeof(struct tcphdr);
	}
    }
     

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

#ifdef STRIP_DEBUG
  fprintf(repfile, "TCP ports %d/%d payload len %d ", sport, dport, pp->len);
#endif
  
  port_indx = (tcpp->th_dport + tcpp->th_sport) % N_CONN_BUCKETS;
  
  hostflowp = get_host_flow(pp->flowlist, ipp);
  
  if (!hostflowp)
    {
      serv_control_t *scp;

      if (!((flags & TH_SYN) || tcp_accept_nosyn))
	{
	  BUMP_DROPS(startup_nosyn);
	  goto drop;
	}

      /* Payload type? - inferred from port */
      way = get_serv(sport, dport, TRANS_TCP, &scp);
     
      hostflowp = new_hostflow(pp->flowlist, ipp, way);
      assert(hostflowp->flow_conns[port_indx].lnext == hostflowp->flow_conns[port_indx].lprev && hostflowp->flow_conns[port_indx].lnext == &hostflowp->flow_conns[port_indx]);
      tconnp = new_tcp_conn(hostflowp, port_indx, tcpp, way);
      tconnp->serv_control = scp;
      tconnp->flow_common.inner.serv_type = scp->rectypes.serv_type;
      if (tconnp->serv_control->cflags & DUMP_OPEN)
	dump_new_conn(tconnp, us_time);
      L_INS_TAIL(&tcp_conn_timeo_q, tconnp, flow_common.conn_timeo_q, tcp_conn_t);
    }
  else
    {
      tconnp = (tcp_conn_t *)get_flow_conn(&hostflowp->flow_conns[port_indx], tcpp->th_sport, tcpp->th_dport, FLOW_TCP);
      if (!tconnp)
	{
	  serv_control_t *scp;

	  if (!((flags & TH_SYN) || tcp_accept_nosyn))
	    {
	      BUMP_DROPS(startup_nosyn);
	      goto drop;
	    }

	  /* Payload type? - inferred from port */
	  way = get_serv(sport, dport, TRANS_TCP, &scp);
	  tconnp = new_tcp_conn(hostflowp, port_indx, tcpp, way);
	  tconnp->serv_control = scp; 
	  tconnp->flow_common.inner.serv_type = scp->rectypes.serv_type;
	  if (tconnp->serv_control->cflags & DUMP_OPEN)
	    dump_new_conn(tconnp, us_time); 
	  L_INS_TAIL(&tcp_conn_timeo_q, tconnp, flow_common.conn_timeo_q, tcp_conn_t);
	}
      else
	{
	  tconnp_hold = (tcp_conn_t *)tconnp->flow_common.conn_timeo_q.lnext;
	  L_MOVE_TO_TAIL(&tcp_conn_timeo_q, tconnp, flow_common.conn_timeo_q, tcp_conn_t);
	  if (tcpp->th_sport == tconnp->flow_common.inner.srcport)
	    way = CLIENT;
	  else if (tcpp->th_sport == tconnp->flow_common.inner.dstport)
	    way = SERVER;
	  else
	    way = 0;
	}
    }

  assert(way == SERVER || way == CLIENT); /* TMP */

  if ((tconnp->serv_control->cflags & DUMP_PKTS) && !pkt_dumped)
    pkt_dump(pp, HTTP_ERR_NONE, NULL, ATM_DATA);

  TCP_TEST_PKT_ARRIVE(tconnp, way, seq, ack, len, flags);

  /* record arrival here to keep counters consistent */
  tconnp->serv_control->serv_ctr->pkts += 1;
  tconnp->serv_control->serv_ctr->octs += len;

  tsp = way == SERVER ? &tconnp->tcp.server : &tconnp->tcp.client;
  mwindow = MAX(tsp->mwindow, MIN_TEST_WDW_SIZE);

  infeasable = (pkt_infeasable(tsp, seq, ack, flags, mwindow));
  if (infeasable == 1)
    {
      /* it really is infeasable */
      int got_one = 0;
      tcp_simplex_flow_t *tsp_tmp;
      tcp_conn_t *walk, *walkstart = tconnp;
      assert(tconnp_hold);

      /* Replace in old position in timeout queue */
      L_REM(&tcp_conn_timeo_q, tconnp, flow_common.conn_timeo_q, tcp_conn_t);
      L_INS_BEFORE(&tcp_conn_timeo_q, tconnp_hold, tconnp, 
		   flow_common.conn_timeo_q, tcp_conn_t);
      
      for (walk = walkstart->alt_next; 
	   walk != walkstart; 
	   walk = walk->alt_next)
	{ 
	  tsp_tmp = way == SERVER ? &walk->tcp.server : &walk->tcp.client;
	  mwindow = 
	    MAX(tsp_tmp->mwindow, MIN_TEST_WDW_SIZE);
	  if (!pkt_infeasable(tsp_tmp, seq, ack, flags, mwindow))
	    {
	      got_one = 1;
	      break;
	    }
	}
      if (got_one)
	{
	  tconnp = walk;
	  tsp = tsp_tmp;
	  /* Move to end of timeout queue */
	  L_MOVE_TO_TAIL(&tcp_conn_timeo_q, tconnp, flow_common.conn_timeo_q, 
			 tcp_conn_t);
	}
      else
	{
	  tconnp = force_alt_conn(tconnp, &tsp, tcpp, way, len, us_time);
	}
    }
  else if (infeasable == -1)
    {
      if (tconnp->serv_control->cflags & DUMP_HDRS)
	rec_tcp_hdr(tconnp, tcpp, seq, ack, window, way, us_time, len);
      /* just ignore it */
      goto drop;
    }

  /*
   * Now that we know which connection we're dealing with it's time
   * to process any options and adjust the us offset from connection open time 
   */
  if (tcp_optp)
    {
      err = do_tcp_options(tconnp, tsp, tcp_optp, hlen, flags);
      if (err)
	goto dump_and_drop;
    }

  off_time = (unsigned long)(us_time - TCP_OPEN_TM);
  
  if (tconnp->serv_control->cflags & DUMP_HDRS)
    rec_tcp_hdr(tconnp, tcpp, seq, ack, window, way, us_time, len);
  
  if (way & SERVER)
    TCP_STATE |= TCP_SERVER_SEEN;
  else 
    TCP_STATE |= TCP_CLIENT_SEEN;

  TCP_SERV_TM = off_time;
	    
  TCP_LAST_ARR_TM = us_time;

  if (!(tsp->state & TSP_SEEN))
    {
      tsp->state |= TSP_SEEN;
      tsp->syn_us = off_time;
    }

  tsp->rst_us = off_time;

  if (len)
    {
      if (tsp->firstdata_us == 0UL)
	tsp->firstdata_us = off_time;
      tsp->lastdata_us = off_time;
    }

  tsp->tot_pkts++;
  tsp->tot_bytes += len;
  tsp->mwindow = MAX(window, tsp->mwindow);
  lp = &tsp->held_list;

  if (flags & TH_ACK)
    {
      if (tsp->state & TSP_ACKING)
	{
	  tsp->lastack = MAX_SEQ(ack, tsp->lastack);
	}
      else
	{
	  tsp->state |= TSP_ACKING;
	  tsp->firstack = ack;
	  tsp->lastack = ack;
	}
      if ((tsp->reverse->state & TSP_SEQUENCING)
	  && ack == tsp->reverse->firstseq + 1 
	  && !(tsp->reverse->state & TSP_ACKSYN)
	  && (tsp->reverse->state & TSP_SYN))
	{
	  tsp->reverse->state |= TSP_ACKSYN;
	  tsp->reverse->acksyn_us = off_time;
	}
    }
  
  if (flags & TH_SYN)
    {
      
      /* SYN but not sequencing - normal open */
      if (way & SERVER)
	{
	  TCP_STATE |= TCP_SERV_SYN;
	  tconnp->flow_common.inner.dst_atmdata = ATM_DATA;
	}
      else
	{
	  TCP_STATE |= TCP_CLI_SYN;
	  tconnp->flow_common.inner.src_atmdata = ATM_DATA;
	}
      tsp->state |= (TSP_SEQUENCING | TSP_SYN);
      tsp->firstseq = tsp->solidseq = seq;
      tsp->mwindow = window;
      
      /* inform service */
      TCP_SERV_OPEN(pp, tconnp, way, flags);
      
      /* process it */
      tcp_pkt(pp, tconnp, tsp, way, seq, len, ack, flags, off_time);
      tsp->solidseq += 1; /* for the SYN */
      goto check_close;
    } /* end if flags & SYN */

  else if (!(tsp->state & TSP_SEQUENCING))
    {
      /* No SYN and not sequencing - must be new connection without SYN */

      if (way & SERVER)
	tconnp->flow_common.inner.dst_atmdata = ATM_DATA;
      else
	tconnp->flow_common.inner.src_atmdata = ATM_DATA;
      tsp->state |= TSP_SEQUENCING;
      tsp->firstseq = seq;
      tsp->solidseq = seq;
      
      /* inform service */
      TCP_SERV_OPEN(pp, tconnp, way, flags);
      
      /* process it */
      TIME_NP_CALL("TCP: initial", &call_times.tcp_inseq, 
		   tcp_pkt(pp, tconnp, tsp, way, seq, len, ack, flags, 
			   off_time));
      
      goto check_close;
    } /* end no SYN */
  

  /* now must have a non-SYN packet and be sequencing */
  diff = (int)(seq - tsp->solidseq);

  /* check if can do a seq gap time out now because:
   * a) we've seen an ACK past the current high seq
   * b) we're holding too many packets
   * - don't do it if this packet is in seq (or a possible retransmission) as 
   * may fill any gap without timing it out   
   */

  if (diff > 0 && !L_EMPTY(lp))
    {
      
      unsigned int held, cleared = 0;
      us_clock_t lh_tm;
      if (TCP_BOTH_SEEN && (tsp->reverse->state & TSP_ACKING))
	{
	  unsigned int highack = tsp->reverse->lastack;
	  held = counters.max_ctrs.buffers_held.current;
	  lh_tm = ((tcp_heldpkt_t *)lp->lnext)->arr_tm;
	  while (lp->lnext != lp)
	    {
	      tcp_heldpkt_t *hpp = (tcp_heldpkt_t *)lp->lnext;
	      if ((int)(highack - hpp->seq) > 0)
		/* case a) */
		{
		  BUMP_CTR(tcp_seq_timeo_ack);
		  sto_a++;
		  cleared++;
		  tsp->state |= TSP_SEQ_TIMEO_ACK;
		  tcp_seq_timeout(hpp, ACK_TIMEO);
		  /* recalculate diff */
		  diff = (int)(seq - tsp->solidseq);
		}
	      else
		{
		  break;
		}
	    }
#if 0
	  if (cleared)
	    {
	      fprintf(stderr, "STO ACK %llums (%u) ",  
		      (off_time - lh_tm)/1000, held);
	      print_flow(stderr, &tconnp->flow_common.inner, way);
	      fprintf(stderr, "cleared %u\n", 
		      held - counters.max_ctrs.buffers_held.current);
	    } 
#endif
	}
      if (tsp->nheld > MAX_HELD_PER_FLOW)
	{
	  tcp_heldpkt_t *hpp = (tcp_heldpkt_t *)lp->lnext;
	  held = counters.max_ctrs.buffers_held.current;
	  BUMP_CTR(tcp_seq_timeo_ql);
	  sto_q++;
	  tsp->state |= TSP_SEQ_TIMEO_QL;
#if 0
	  fprintf(stderr, "STO QL %llums (%u) ",  
		    (off_time - hpp->arr_tm)/1000, held);
	  print_flow(stderr, &tconnp->flow_common.inner, way);
#endif
	  tcp_seq_timeout(hpp, Q_TIMEO);
	  /* recalculate diff */
	  diff = (int)(seq - tsp->solidseq);
	}
    }
  
  /* now the current packet */
  if (diff == 0)
    {
      /* in sequence - no gap */
      TIME_NP_CALL("TCP: in seq", &call_times.tcp_inseq, 
		   tcp_pkt(pp, tconnp, tsp, way, seq, len, ack, flags, 
			   off_time));
      /* has this filled a sequence hole? */
      if (!L_EMPTY(lp))
	{
	  /* this must be out of order */
	  tsp->ooo_bytes += len;
	  tsp->ooo_pkts++;
	  TIME_NP_CALL("TCP: catchup", &call_times.tcp_catchup, 
		       tcp_catch_up(tsp, tconnp, way, RETRANSMISSION));
	}
    }
  else if (diff < 0)
    {
      /* earlier packet */
      unsigned int end = seq + len;
      int pastend = (int)(end - tsp->solidseq);
      
      if (pastend <= 0)
	{
	  /* totally seen already */
	  tsp->ooo_bytes += len;
	  tsp->ooo_pkts++;
	  /* XXX TODO - fix this - might be timed-out gap */
	  tsp->duplicate_pkts++;
	  tsp->duplicate_bytes += len;
	  goto drop;
	}
      else
	{
	  /* some not previously seen */
	  TIME_NP_CALL("TCP: in seq - o'lap", &call_times.tcp_inseq, 
		       tcp_pkt(pp, tconnp, tsp, way, seq, len, ack, flags, 
			       off_time));
	  /* has this filled a sequence hole? */
	  if (!L_EMPTY(lp))
	    {
	      /* this must be out of order */
	      tsp->ooo_bytes += len;
	      tsp->ooo_pkts++;
	      TIME_NP_CALL("TCP: catchup - o'lap", 
			   &call_times.tcp_catchup, 
			   tcp_catch_up(tsp, tconnp, way, RETRANSMISSION));
	    }
	} 
    }
  else
    {
      /* in sequence - but a gap */
      tsp->state |= TSP_SEQ_HELD;
      tcp_hold(tconnp, tsp, pp, seq, ack, len, flags);
    }
  
 check_close:
  /* closing ? */
  if (TCP_STATE & tcp_closing_state)
    {
#if defined REPORT
      if (report)
	report_tcp_close(tconnp);
#endif
      tconn_cleanup(tconnp, CONN_CLOSE); /* ? */
      free_tcp_conn(tconnp);
    }
  
  return 0;
  
 dump_and_drop:

  pkt_dump(pp, err, NULL, ATM_DATA);

  /* normally buffers and packet records are freed by tcp_pkt */
 drop:
  FREE_INBUF(pp);
  
  return 0;
}

  
/*
 * end tcp.c 
 */
