/*

Copyright (C) 2000 - 2006 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <libnd_udp.h>

#ifdef LINUX_HOST
struct ipovly
{
  u_char           ih_x1[9];
  u_char           ih_pr;
  u_int16_t        ih_len;
  struct in_addr   ih_src;
  struct in_addr   ih_dst;
};
#endif


static LND_Protocol *udp;

static LND_Protocol *
udp_get_ip(void)
{
  static LND_Protocol *ip = NULL;

  if (!ip)
    ip = libnd_proto_registry_find(LND_PROTO_LAYER_NET, 0x0800);

  return ip;
}

static gboolean
udp_header_complete(guchar *data, guchar *data_end)
{
  if (!data)
    return FALSE;

  /* D(("UDP header complete? %p %p --> %i\n", data, data_end, (data + 8 <= data_end))); */
  return (data + 8 <= data_end);
}

static gboolean
udp_get_first(const LND_Packet *packet, struct ip **iphdr, struct udphdr **udphdr)
{
  GList        *l;
  LND_Protocol  *ip;
  LND_ProtoData *pd, *pd_prev;

  if (!packet || !iphdr || !udphdr)
    return FALSE;

  if (! (ip = udp_get_ip()))
    return FALSE;

  /* Walk the proto data and find the first UDP
     (we don't support nesting UDP) with a preceding IP
  */
  
  for (l = packet->pd; l; l = g_list_next(l))
    {
      pd = (LND_ProtoData *) l->data;

      if (g_list_previous(l) && pd->inst.proto == udp)
	{
	  pd_prev = (LND_ProtoData *) g_list_previous(l)->data;

	  if (pd_prev->inst.proto == ip)
	    {
	      *iphdr  = (struct ip *) pd_prev->data;
	      *udphdr = (struct udphdr *) pd->data;

	      return TRUE;
	    }
	}
    }
  
  return FALSE;
}

/* Plugin hook implementations: ---------------------------------------- */

const char *
name(void)
{
  return ("UDP Plugin");
}


const char *
description(void)
{
  return ("A plugin providing support for the UDP protocol.\n");
}


const char *
author(void)
{
  return ("Christian Kreibich, <christian@whoop.org>");
}


const char *
version(void)
{
  return VERSION;
}


LND_Protocol *
init(void)
{
  udp = libnd_proto_new("UDP", LND_PROTO_LAYER_TRANS, IPPROTO_UDP);
			     		
  udp->init_packet     = libnd_udp_init_packet;
  udp->header_complete = libnd_udp_header_complete;
  udp->fix_packet      = libnd_udp_fix_packet;

  return udp;
}


/* Protocol method implementations: ------------------------------------ */

void       
libnd_udp_init_packet(LND_Packet *packet, guchar *data, guchar *data_end)
{
  struct udphdr *udphdr;
  LND_Protocol   *payload_proto;

  D_ENTER;

  udphdr = (struct udphdr *) data;
  
  if (!udp_header_complete(data, data_end))
    {
      libnd_raw_proto_get()->init_packet(packet, data, data_end);
      D_RETURN;
    }
  
  libnd_packet_add_proto_data(packet, udp, data, data_end);  
  
  /* If UDP contains any payload, check the appriopriate header
     field value and demultiplex packet initialization up to the
     next correct protocol: */

  if (! (payload_proto = libnd_proto_registry_find(LND_PROTO_LAYER_APP, ntohs(udphdr->uh_dport))))
    payload_proto = libnd_raw_proto_get();

  payload_proto->init_packet(packet, data + 8, data_end);

  D_RETURN;
}


gboolean
libnd_udp_header_complete(const LND_Packet *packet, guint nesting)
{
  LND_ProtoData *pd;

  if (!packet)
    return FALSE;

  if (! (pd = libnd_packet_get_proto_data(packet, udp, nesting)))
    return FALSE;

  return udp_header_complete(pd->data, pd->data_end);
}


gboolean   
libnd_udp_datagram_complete(const LND_Packet *packet, guint nesting)
{
  LND_ProtoData  *pd;
  struct udphdr *udphdr;

  if (!packet)
    return FALSE;

  if (! (pd = libnd_packet_get_proto_data(packet, udp, nesting)))
    return FALSE;

  udphdr = (struct udphdr *) pd->data;

  return (pd->data + ntohs(udphdr->uh_ulen) <= libnd_packet_get_end(packet));
}


gboolean
libnd_udp_fix_packet(LND_Packet *packet)
{
  struct udphdr *udphdr;
  guint16        correct_sum;
  
  if (!packet)
    return FALSE;
  
  if (!libnd_udp_csum_correct(packet, &correct_sum))
    {
      udphdr = (struct udphdr*) libnd_packet_get_data(packet, udp, 0);
      udphdr->uh_sum = correct_sum;
      libnd_packet_modified(packet);
      return TRUE;
    }

  return FALSE;
}


guint16
libnd_udp_checksum(const LND_Packet *packet)
{
  LND_Protocol    *ip;
  struct ip*      iphdr = NULL;
  struct udphdr  *udphdr = NULL;
  struct ipovly   ipovly;
  guint16        *w;
  guint           i, preadd;
  guint16         old, result = 0;

  if (!packet || !(ip = udp_get_ip()))
    return 0;

  if (!udp_get_first(packet, &iphdr, &udphdr))
    return 0;
  
  w  = (guint16 *) &ipovly;
  preadd = 0;
      
  memset(&ipovly, 0, sizeof(struct ipovly));
  ipovly.ih_pr  = iphdr->ip_p;
  ipovly.ih_len = udphdr->uh_ulen;
  ipovly.ih_src = iphdr->ip_src;
  ipovly.ih_dst = iphdr->ip_dst;
      
  for (i = 0; i < sizeof(struct ipovly)/sizeof(guint16) ; i++)
    preadd += *w++;

  old = udphdr->uh_sum;
  udphdr->uh_sum = 0;
  result = libnd_misc_in_cksum((u_short*)udphdr, ntohs(udphdr->uh_ulen), preadd);
  udphdr->uh_sum = old;

  return result;
}

  
LND_Protocol *
libnd_udp_get(void)
{
  return udp;
}


gboolean
libnd_udp_get_headers(const LND_Packet *packet, struct ip **iphdr, struct udphdr **udphdr)
{
  GList         *l;
  LND_Protocol  *ip;
  LND_ProtoData *pd, *pd_prev;

  if (!packet || !iphdr || !udphdr)
    return FALSE;

  if (! (ip = udp_get_ip()))
    return FALSE;
  
  /* Walk the proto data and find the first UDP
   * (we don't support nesting UDP) with a preceding IP
   */
  
  for (l = packet->pd; l; l = g_list_next(l))
    {
      pd = (LND_ProtoData *) l->data;

      if (g_list_previous(l) && pd->inst.proto == udp)
	{
	  pd_prev = (LND_ProtoData *) g_list_previous(l)->data;

	  if (pd_prev->inst.proto == ip)
	    {
	      *iphdr  = (struct ip *) pd_prev->data;
	      *udphdr = (struct udphdr *) pd->data;

	      return TRUE;
	    }
	}
    }
  
  return FALSE;
}


guint
libnd_udp_get_payload_length(struct ip *iphdr, struct udphdr *udphdr)
{
  if (!udphdr)
    return 0;

  return ntohs(udphdr->uh_ulen) - 8;
}


gboolean   
libnd_udp_csum_correct(const LND_Packet *packet, guint16 *correct_sum)
{
  struct udphdr *udphdr;
  guint16 old_sum, correct_sum_tmp;

  if (!packet)
    return FALSE;

  udphdr = (struct udphdr *) libnd_packet_get_data(packet, libnd_udp_get(), 0);

  old_sum = udphdr->uh_sum;
  correct_sum_tmp = libnd_udp_checksum(packet);

  if (correct_sum)
    *correct_sum = correct_sum_tmp;

  return (old_sum == correct_sum_tmp);
}


syntax highlighted by Code2HTML, v. 0.9.1