/*  -*- 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 <ctype.h>
#include <stdlib.h>
#include <sys/param.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>

#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 <endian.h>
#include <arpa/nameser.h>

#include <assert.h>

#include "list.h"
#include "pkt.h"
#include "interface.h"
#include "flows.h"
#include "service.h"
#include "http.h"
//#include "tcp_ftp.h"
#include "tcp_rtsp.h"
//#include "tcp_other.h"
#include "tcp.h"
#include "udp.h"
#include "timeouts.h"
#include "udp_ns.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 "http_errdefs.h"
#include "np_file.h"


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

#ifndef OWAN
#define FIND_NOPRINTS
#endif

#ifdef FIND_NOPRINTS
int noprints = 0;
#endif

void 
udp_ns_open(prec_t *pp, struct udp_conn *uconnp, int way)
{
  /* get an ns record for the connection */
  uconnp->service_data = get_ns_rec();
  ((ns_rec_t *)uconnp->service_data)->state = 0U;
  
  return;
}

void 
udp_ns_close(prec_t *pp, struct udp_conn *uconnp, int way)
{
  return;
}

/* used only if response not seen */
void 
udp_ns_dump(struct udp_conn *uconnp)
{
  /* dump */
  int client_seen = UDP_STATE & UDP_CLIENT_SEEN;
  int server_seen = UDP_STATE & UDP_SERVER_SEEN;
  
  rec_dump_start();
  udp_dump(uconnp, client_seen, server_seen);
  DUMP_STRUCT(outp, uconnp->service_data, ns_rec_t);
  rec_dump_end(REC_UDP_DNS);

  /* dispose of service (ns_rtec) state */
  recycle_ns_rec((ns_rec_t *)uconnp->service_data);
  
  return;
}

void 
udp_ns_start_dump(struct udp_conn *uconnp)
{
  /* dump */
  int client_seen = UDP_STATE & UDP_CLIENT_SEEN;
  int server_seen = UDP_STATE & UDP_SERVER_SEEN;
  
  rec_dump_start();
  udp_dump(uconnp, client_seen, server_seen);
  
  return;
}

/* used if server seen - response processing takes care of dump */
void 
udp_ns_nodump(struct udp_conn *uconnp)
{
  return;
}

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

udp_serv_methods_t udp_ns_serv_methods = 
{
  udp_ns_open,
  udp_ns_pkt,
  udp_ns_close,
  udp_ns_dump
};

udp_serv_methods_t udp_ns_serv_methods_nodump = 
{
  udp_ns_open,
  udp_ns_pkt,
  udp_ns_close,
  udp_ns_nodump
};

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

serv_control_t udp_ns_serv_control = 
  {
    {
      (void *)&udp_ns_serv_methods
    },
    {
      REC_UDP_DNS, REC_UDP_DNS, REC_UDP_DNS, UDP_SERV_DNS
    },
    &counters.TRAFF_UDP_DNS,
    NO_DUMP
  };

serv_control_t udp_ns_serv_control_nodump = 
  {
    {
      (void *)&udp_ns_serv_methods_nodump
    },
    {
      REC_UDP_DNS, REC_UDP_DNS, REC_UDP_DNS, UDP_SERV_DNS
    },
    &counters.TRAFF_UDP_DNS,
    NO_DUMP
  };

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

char *
ns_nskip(char *cp, char *start, char *end)
{
  unsigned char i = *cp++;

  while (i && cp < end)
    {
      if ((i & INDIR_MASK) == INDIR_MASK)
	return cp + 1;
      cp += i;
      i = *cp++;
    }
  return cp;
}


int 
copy_label(char *cp, unsigned int i, char *end, char *limit)
{
  while (i-- && cp < end && outp < limit)
    {
      //putchar(*cp);
      if (!isprint(*cp))
	{
#ifdef FIND_NOPRINTS
	  fprintf(stderr, "XXX DNS non-print XXX\n");
	  noprints++;
#endif
	  *outp++ = '/';
	  outp += sprintf(outp, "%2x", *cp++) -1;
	}
      else
	{
	  *outp++ = *cp++;
	}
    }
  if (cp == end)
    return 1;
  else if (outp == limit)
    return -1;
  else
    return 0;
}

char *
ns_ncopy(char *cp, char *start, char *end, char *limit, ns_rec_t *np)
{
  unsigned int i;
  int ret;
  unsigned char *rp;
  char *cp_last = cp;
  int compress = 0;
  
  i = *cp++;
  rp = cp + i;
  
  while ((i & INDIR_MASK) == INDIR_MASK) 
    {
      if (!compress)
	rp = cp + 1;
      cp = start + (((i << 8) | *cp) & 0x3fff);
      i = *cp++;
      compress = 1;
      if (cp == cp_last)
	return NULL;
      else
	cp_last = cp;
    }
  
  if (i != 0)
    {
      cp_last = cp;
      while (i && cp < end) 
	{
	  if ((ret = copy_label(cp, i, end, limit)) != 0)
	    {
	      if (ret == -1)
		np->state |= NS_RR_TRUNC;
	      break;
	    }
	  cp += i;
	  i = *cp++;
	  if ((i & INDIR_MASK) == INDIR_MASK) 
	    {
	      if (!compress)
		rp = cp + 1;
	      cp = start + (((i << 8) | *cp) & 0x3fff);
	      i = *cp++;
	      compress = 1;
	      continue;
	    }
	  if (!compress)
	    rp = cp + i;
	  if (i)
	    {
	      *outp++ = '.';
	      //putchar( '.');
	    }
	  if (cp == cp_last)
	    return NULL;
	  else
	    cp_last = cp;
	}
      
    }
  else
    {
      *outp++ = '.';
      //putchar('.');
    }
  /* NULL terminate */
  *outp++ = '\0';
  //putchar('\n');
  
  return (rp);
}

int 
ns_get_data(HEADER *np, int len,  ns_rec_t *rp)
{
  char *cp;
  char *start = (char *)np; 
  char *end = start + len;
  char *outp_hold;
  char *buf_start = outp;
  char *limit = (outp +  NS_MAX_RRBUFSZ) - 6;
  unsigned short qdcount = ntohs(np->qdcount);
  unsigned short ancount = ntohs(np->ancount);

  rp->ns_parms = *((ns_parms_t *)  (((char *)np) + 2));

  cp = (char *)(np + 1) - 4;

  if (rp->ns_parms.rcode)
    {
      /* Not succes - save the requests */
      if (qdcount)
	{
	  rp->state |= NS_RRS_FOLLOW;

	  while (qdcount-- && cp < end)
	    {
	      *outp++ = RR_REQ;
	      if ((cp = ns_ncopy(cp + 4, start, end, limit, rp)) == NULL)
		goto error;
	      if (rp->state &  NS_RR_TRUNC)
		goto overlimit;
	    }
	}
    }
  else
    {
      /* jump over requests */
      while (qdcount-- && cp < end)
	cp = ns_nskip(cp + 4, start, end);
    }
  
  cp += 4;
  
  if (ancount)
    {
      if (!(rp->state & NS_RRS_FOLLOW))
	{
	  rp->state |= NS_RRS_FOLLOW;
	}
      
      /* run through answers */
      while (ancount-- && cp < end)
	{
	  unsigned short type, class, len;
	  /* remember start of output buffer area */
	  outp_hold = outp;
	  /* step over preamble byte */
	  *outp++ = RR_NONE;
	  /* copy domain name */
	  if ((cp = ns_ncopy(cp, start, end, limit, rp)) == NULL)
	    goto error;
	  type = *cp++ << 8;
	  type |= *cp++;
	  class = *cp++ << 8;
	  class |= *cp++;
	  /* step over ttl */
	  cp += 4;
	  len = *cp++ << 8;
	  len |= *cp++;

	  //printf ("class %hu type %hu\n", class, type);
	  
	  if (class != C_IN)
	    {
	      /* forget it */
	      outp = outp_hold;
	      *outp++ = RR_NOT_INET;
	      DUMP_INT(outp, class, unsigned short);
	    }
	  else if (type == T_A)
	    {
	      *outp_hold =  RR_IPADDR;
	      DUMP_INT(outp, *((unsigned int *)cp), unsigned int);
	      //printf("%s\n", get_hname(cp));
	    }
	  else if (type == T_CNAME)
	    {
	      *outp_hold = RR_CNAME;
	      if (ns_ncopy(cp, start, end, limit, rp) == NULL)
		goto error;
	    }
	  else 
	    {
	      outp = outp_hold;
	      *outp++ = RR_OTHER_TYPE;
	      DUMP_INT(outp, type, unsigned short);
	    }
	  
	  cp += len;
	  if (rp->state & NS_RR_TRUNC)
	    goto overlimit;
	}
    }

 overlimit:

  if (rp->state & NS_RRS_FOLLOW)
    {
      *outp++ = RR_NONE;
      rp->buflen = outp - buf_start;
    }
  
  return 0;

 error:
  return HTTP_ERR_NS_DECODE;
}
  

int 
udp_ns_pkt(prec_t *pp, struct udp_conn *uconnp, int way, unsigned int tm)
{
  int ret = 0;
  HEADER *hp = (HEADER *)pp->buf;
  int len = pp->len;
  ns_rec_t *np = (ns_rec_t *)(uconnp->service_data);

  if (way == CLIENT)
    /* request */
    {
      np->id = hp->id;
      np->req_us = tm;
      np->state |= NS_CLIENT_SEEN;
      if (!hp->qr)
	np->state |= NS_REQ_REQ;
    }
  else
    /* response */
    {
      UDP_STATE |= UDP_SERV_DONE;
      //UDP_CLOSE_TM = uconnp->flow_common.last_arr_tm;
      udp_ns_start_dump(uconnp);
      np = (ns_rec_t *)outp;
      uconnp->serv_control = &udp_ns_serv_control_nodump;
      
      DUMP_STRUCT(outp, uconnp->service_data, ns_rec_t); 
      if ((np->state & NS_CLIENT_SEEN) && np->id != hp->id)
	/* wrong response - record it anyway */
	  np->state |= NS_UNMATCHED_RESPONSE;

      /* record what we want from response */
      np->rep_us = tm;
      np->state |= NS_SERVER_SEEN;
      if (hp->qr)
	{
	  np->state |= NS_RESP_RESP;
	  /* only interested in queries */
	  if (hp->opcode == QUERY)
	    if ((ret = ns_get_data(hp, len, np)) != 0)
	      np->state |= NS_DECODE_ERR;
	}

      /* 
       * Better dump connection now whether match or not 
       * - assume request already seen, if not will just have to time out 
       */
 
      rec_dump_end(REC_UDP_DNS);
      /* dispose of service (ns_rtec) state */
      recycle_ns_rec((ns_rec_t *)uconnp->service_data);     
      free_udp_conn(uconnp);
    }

#ifdef FIND_NOPRINTS
  if (noprints)
    {
      //pkt_dump(buf_start(pp),  pp->wirelen, &pp->tstamp,
      pkt_dump(pp, 
	       INTERESTING_DNS_PKT, NULL, 
	       way == CLIENT ? uconnp->flow_common.inner.src_atmdata 
	       : uconnp->flow_common.inner.dst_atmdata);
      noprints = 0;
    }
#endif

  if (ret)
    udp_error(uconnp, pp, ret, way);
  
  return 0;
}
  
