/*  -*- 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/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_rtsp.h"
#include "tcp.h"
#include "udp.h"
#include "service.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 "tcp_test.h"

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


/****************************************************************************/


void 
udp_dump(struct udp_conn *uconnp, int client_seen, int server_seen)
{
  DUMP_STRUCT(outp, &uconnp->flow_common.inner, flow_inner_t);

  /* data on udp connection */
  if (client_seen)
    DUMP_STRUCT(outp, &uconnp->udp.client, udp_simplex_flow_t);
  if (server_seen)
    DUMP_STRUCT(outp, &uconnp->udp.server, udp_simplex_flow_t);
  
  return;
}


/****************************************************************************/

void 
free_udp_conn(udp_conn_t *uconnp)
{
  host_flow_t *hfp = (host_flow_t *)uconnp->flow_common.hconnp;

  assert (UDP_STATE & (UDP_TIMEO | UDP_RUNEND | UDP_SERV_DONE));

  //UDP_CLOSE_TM = uconnp->flow_common.last_arr_tm;
  
  if (!UDP_BOTH_SEEN)
    BUMP_CTR(udp_one_way_only);

  /* Dump routine MUST process and free any service state */

  UDP_SERV_DUMP(uconnp);

  L_REM(&hfp->flow_conns[uconnp->flow_common.port_indx], uconnp, flow_common.hlist, udp_conn_t);
  L_REM(&udp_conn_timeo_q, uconnp, flow_common.conn_timeo_q, udp_conn_t);

  recycle_udp_conn_t(uconnp);

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

  return;
}


/****************************************************************************/


udp_conn_t *
new_udp_conn(host_flow_t *hflow, int port_indx, struct udphdr *udpp, int way)
{
  udp_conn_t *connp;
  listhdr_t *conns;

  connp = get_udp_conn_t();
  
  if (way == CLIENT)
    {
      connp->flow_common.inner.srcport = udpp->uh_sport;
      connp->flow_common.inner.dstport = udpp->uh_dport;
      connp->flow_common.inner.srcaddr = hflow->srcaddr;
      connp->flow_common.inner.dstaddr = hflow->dstaddr;
    }
  else
    {
      connp->flow_common.inner.srcport = udpp->uh_dport;
      connp->flow_common.inner.dstport = udpp->uh_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;

  conns = &hflow->flow_conns[port_indx];

  L_INS_HEAD(conns, connp, flow_common.hlist, udp_conn_t);
  hflow->n_conns++;

  return connp;
}


/****************************************************************************/

      
int 
do_udp(prec_t *pp, struct ip *ipp, us_clock_t us_time)
{
  struct udphdr *udpp;
  unsigned short sport, dport;
  unsigned int len;
  unsigned int tm;

  int way = 0;

  int port_indx;
  udp_conn_t *uconnp;
  host_flow_t *hostflowp;
  udp_simplex_flow_t *usp;

  int pkt_dumped = 0;

  BUMP_GLOBAL(TRAFF_UDP);

  if (dump_pkt_types & DUMP_UDP_PKTS)
    {
      //pkt_dump(buf_start(pp), pp->wirelen, &pp->tstamp, HTTP_ERR_NONE, NULL, ATM_DATA);
      pkt_dump(pp, HTTP_ERR_NONE, NULL, ATM_DATA);
      pkt_dumped = 1;
    }

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

  udpp = (struct udphdr *)pp->buf;

#if 0
#ifdef CKSUM
  if(udpp->uh_sum != 0 && !udp_csum(ipp, udpp, pp))
    {
      //pkt_dump(buf_start(pp),  pp->wirelen, &pp->tstamp, 
      //	   TCP_ERR_CHKSUM, NULL, ATM_DATA);
      pkt_dump(pp, TCP_ERR_CHKSUM, NULL, ATM_DATA);
      BUMP_DROPS(UDPSum);
      goto drop;
    }
#endif /* ifdef CKSUM */
#endif
      
  sport = ntohs(udpp->uh_sport);
  dport = ntohs(udpp->uh_dport);
     

  pp->buf += sizeof(struct udphdr);
  pp->len -= sizeof(struct udphdr);
  len = pp->len;
  
  port_indx = (udpp->uh_dport + udpp->uh_sport) % N_CONN_BUCKETS;
  
  hostflowp = get_host_flow(pp->flowlist, ipp);
  
  if (!hostflowp)
    {
      serv_control_t *scp;
      /* Payload type? - inferred from port */
      way = get_serv(sport, dport, TRANS_UDP, &scp);
      hostflowp = new_hostflow(pp->flowlist, ipp, way);
      uconnp = new_udp_conn(hostflowp, port_indx, udpp, way);
      uconnp->serv_control = scp; 
      uconnp->flow_common.inner.serv_type = scp->rectypes.serv_type;
      UDP_OPEN_TM = us_time;
      SET_ATM_DATA(uconnp, pp, way);  /* ATM data */
      UDP_SERV_OPEN(pp, uconnp, way);
      L_INS_TAIL(&udp_conn_timeo_q, uconnp, flow_common.conn_timeo_q, udp_conn_t);
    }
  else
    {
      uconnp = (udp_conn_t *)get_flow_conn(&hostflowp->flow_conns[port_indx], udpp->uh_sport, udpp->uh_dport, FLOW_UDP);
      if (!uconnp)
	{
	  serv_control_t *scp;
	  /* Payload type? - inferred from port */
	  way = get_serv(sport, dport, TRANS_UDP, &scp);
	  uconnp = new_udp_conn(hostflowp, port_indx, udpp, way);
	  uconnp->serv_control = scp; /* struct assignment */
	  uconnp->flow_common.inner.serv_type = scp->rectypes.serv_type;
	  UDP_OPEN_TM = us_time;
	  SET_ATM_DATA(uconnp, pp, way);  /* ATM data */
	  UDP_SERV_OPEN(pp, uconnp, way);
	  L_INS_TAIL(&udp_conn_timeo_q, uconnp, flow_common.conn_timeo_q, udp_conn_t);
	}
      else
	{
	  L_MOVE_TO_TAIL(&udp_conn_timeo_q, uconnp, flow_common.conn_timeo_q, udp_conn_t);
	  if (udpp->uh_sport == uconnp->flow_common.inner.srcport)
	    way = CLIENT;
	  else if (udpp->uh_sport == uconnp->flow_common.inner.dstport)
	    way = SERVER;
	  else
	    way = 0;
	}
    }

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

  if ((uconnp->serv_control->cflags & DUMP_PKTS) && !pkt_dumped)
    //pkt_dump(buf_start(pp), pp->wirelen, &pp->tstamp, HTTP_ERR_NONE, NULL, ATM_DATA);
    pkt_dump(pp, HTTP_ERR_NONE, NULL, ATM_DATA);

  UDP_CLOSE_TM = us_time;
  tm = us_time - UDP_OPEN_TM;

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

  if (way & SERVER)
    {
      usp = &uconnp->udp.server;
      UDP_STATE |= UDP_SERVER_SEEN;
      UDP_SEND_TM = tm;
      if (UDP_SSTART_TM == 0L)
	UDP_SSTART_TM = tm;
    }
  else 
    {
      usp = &uconnp->udp.client;
      UDP_STATE |= UDP_CLIENT_SEEN;
      UDP_CEND_TM = tm;
      if (UDP_CSTART_TM == 0L)
	UDP_CSTART_TM = tm;
    }

  usp->tot_pkts++;
  usp->tot_bytes += len;

  /* do whatever we have to with the packet */
  UDP_SERV_PKT(pp, uconnp, way, tm);

 drop:
  FREE_INBUF(pp);

  return 0;
}


/*
 * End udp.c 
 */
