/**************************************************************************** 
** File: ip.c
**
** Author: Mike Borella
**
** Comments: Dump IP header information
**
** $Id: ip.c,v 1.27 2006/11/21 07:47:34 farooq-i-azam Exp $
**
** This program 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.
**
** This program 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 Library General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "global.h"
#include "ip_protocols.h"
#include "ip_services.h"

#define HOLDER_SIZE 64

/* 
 * Contains the descriptions of IP options
 */

strmap_t ip_option_map[] = 
{
  { IP_OPTION_EOL,                "end of options" },
  { IP_OPTION_NOP,                "no op" },
  { IP_OPTION_RECORDROUTE,        "record route" },
  { IP_OPTION_TIMESTAMP,          "time stamp" },
  { IP_OPTION_TRACEROUTE,         "traceroute" },
  { IP_OPTION_SECURITY,           "security" },
  { IP_OPTION_LSR,                "loose source route" },
  { IP_OPTION_EXTSECURITY,        "extra security" },
  { IP_OPTION_COMSECURITY,        "commercial security" },
  { IP_OPTION_STREAMID,           "stream ID" },
  { IP_OPTION_SSR,                "strict source route" },
  { IP_OPTION_ROUTERALERT,        "router alert" },
  { 0, "" }
};

extern struct arg_t *my_args;
extern void (*ip_proto_func[])(packet_t *);
extern strmap_t ipproto_map[];

void dump_ip_options(packet_t *, u_int8_t);

/*----------------------------------------------------------------------------
**
** dump_ip()
**
** Parse IP header and dump fields
**
**----------------------------------------------------------------------------
*/

void dump_ip(packet_t *pkt)
{
  ip_header_t ip;
  u_int16_t frag_off;
  u_int8_t ver, hlen;
  u_int8_t u_bit, df_bit, mf_bit;

#ifdef DEBUG
  printf("\nEntering IP\n");
#endif

  /* Set the layer */
  set_layer(LAYER_NETWORK);

  /*
   * Stats accounting
   */

  stats_update(STATS_IP);

  /*
   * Get the IP header
   */

  if (get_packet_bytes((u_int8_t *) &ip, pkt, sizeof(ip_header_t)) == 0)
    return;

  /*
   * Conversions
   */

  ver = ip.version;
  hlen = ip.header_length;
  ip.offset = ntohs(ip.offset);
  ip.length = ntohs(ip.length);
  ip.id = ntohs(ip.id);
  frag_off = ip.offset & 0x1fff;
  u_bit = (ip.offset & 0x8000) >> 15;
  df_bit = (ip.offset & 0x4000) >> 14;
  mf_bit = (ip.offset & 0x2000) >> 13;
  ip.checksum = ntohs(ip.checksum);

  /*
   * Set the effective end of the packet.  Take the total length as per the 
   * IP header, subtract the header we've read.  This is the number of bytes
   * into the packet where the end should be.  This helps us get rid of 
   * link layer padding on tinygrams.
   */

  set_packet_apparentend(pkt, ip.length - sizeof(ip_header_t));

  /*
   * Dump header
   */

  if (my_args->m)
    {
      u_int16_t new_fo = frag_off * 8;

      display_minimal_string("| IP ");
      display_minimal_ipv4((u_int8_t *) &ip.src); 
      display_minimal_string("->");
      display_minimal_ipv4((u_int8_t *) &ip.dst); 

      display_minimal_string(" (len:");
      display_minimal((u_int8_t *) &ip.length, 2, DISP_DEC);
      display_minimal_string(",id:");
      display_minimal((u_int8_t *) &ip.id, 2, DISP_DEC);
      if (df_bit)
	display_minimal_string(",DF");
      if (mf_bit)
	display_minimal_string(",MF");
      display_minimal_string(",frag:");
      display_minimal((u_int8_t *) &new_fo, 2, DISP_DEC);
      display_minimal_string(") ");
    }
  else
    {
      u_int8_t holder[64];
      
      /* announcement */
      display_header_banner("IP Header");
      
      /* print fields */
      display("Version", (u_int8_t *) &ver, 1, DISP_DEC);
      sprintf(holder, "%d (%d bytes)", hlen, hlen*4);	
      display_string("Header length", holder);
      display("TOS", (u_int8_t *) &ip.tos, 1, DISP_HEX);
      display("Total length", (u_int8_t *) &ip.length, 2, DISP_DEC);
      display("Identification", (u_int8_t *) &ip.id, 2, DISP_DEC);
      
      if (frag_off)
	{
	  sprintf(holder, "%d (%d bytes)", frag_off, frag_off*8);
	  display_string("Fragmentation offset", holder);
	}
      else
	display("Fragmentation offset", (u_int8_t *) &frag_off, 2, DISP_DEC);
      
      display("Unused bit", (u_int8_t *) &u_bit, 1, DISP_BINNLZ);
      display("Don't fragment bit", (u_int8_t *) &df_bit, 1, DISP_BINNLZ);
      display("More fragments bit", (u_int8_t *) &mf_bit, 1, DISP_BINNLZ);
      display("Time to live", (u_int8_t *) &ip.ttl, 1, DISP_DEC);
      display_strmap("Protocol", ip.protocol, ipproto_map);
      display("Header checksum", (u_int8_t *) &ip.checksum, 2, 
	      DISP_DEC);
      
      /* IP's are not in network byte order?? */
      display_ipv4("Source address", (u_int8_t *) &ip.src); 
      display_ipv4("Destination address", (u_int8_t *) &ip.dst); 
    }

  /* set IP address state */
  state_set_srcaddr(ntohl(ip.src));
  state_set_dstaddr(ntohl(ip.dst));
  
  /* check for IP options */
  if (hlen > 5)
    dump_ip_options(pkt, hlen * 4 - 20);

  /* dump the hex buffer */
  hexbuffer_flush();
  
  /*
   * If this is fragment zero, hand it to the next higher
   * level protocol.
   */
  
  if ((ip.offset & 0x1fff) == 0 && ip_proto_func[ip.protocol]) 
      ip_proto_func[ip.protocol](pkt);

#ifdef DEBUG
  printf("\nLeaving IP\n");
#endif

}

/*----------------------------------------------------------------------------
**
** dump_ip_recordroute()
**
** Dump a record route IP option
**
**----------------------------------------------------------------------------
*/

void dump_ip_recordroute(u_int8_t * rr, u_int8_t len)
{
  u_int8_t   pointer;
  u_int32_t  addr;
  int        i;

  /*
   * The first byte should be a pointer and the next len-1 bytes should be 
   * a series of IP addresses.  If len-1 is not a multiple of 4 then something
   * is very wrong and we bail out.
   */

  pointer = *rr;
  if (!my_args->m)
    {
      display("  Pointer", (u_int8_t *) &pointer, 1, DISP_HEX);
    }

  i = 1;
  while (i+3 <= len)
    {
      /* Get and address then increment i to the next address */
      memcpy((u_int8_t *) &addr, rr+i, 4);
      display_ipv4("  Address", (u_int8_t *) &addr);
      i = i + 4;
    }
}

/*----------------------------------------------------------------------------
**
** dump_ip_options()
**
** Dump any IP options
**
**----------------------------------------------------------------------------
*/

void dump_ip_options(packet_t *pkt, u_int8_t length)
{
  u_int8_t bytes_read = 0;
  ip_option_t opt;
  char holder[HOLDER_SIZE];

  while(bytes_read < length)
    {
      /*
       * Get the first byte to make sure that we don't have a 1-byte option
       */

      if (get_packet_bytes((u_int8_t *) &opt.code, pkt, 1) == 0)
	return;
      bytes_read += 1;

      /*
       * Display it
       */
      
      snprintf(holder, HOLDER_SIZE, "%d (%s)", opt.code, 
	      map2str(ip_option_map, opt.code));
      
      if (!my_args->m)
	display_string("IP option code", holder);
      else
	{
	  display_minimal_string(holder);
	  display_minimal_string(" ");
	}
      
      /*
       * If it is 0, then its just a 1-byte EOL
       */

      if (opt.code == 0)
	continue;

      /*
       * It isn't, so lets read the rest...
       */

      if (get_packet_bytes((u_int8_t *) &opt.length, pkt, 1) == 0)
	return;
      bytes_read += 1;
      if (!my_args->m)
	display("  Length", (u_int8_t *) &opt.length, 1, DISP_DEC);

      /*
       * Get the rest of the option, adjusting the length by 2 to count 
       * option type and length fields...
       */
      
      if (get_packet_bytes((u_int8_t *) &holder, pkt, opt.length-2) == 0)
	return;
      bytes_read = bytes_read + opt.length - 2;

      /*
       * Do something intelligent with the options that we understand
       */

      switch(opt.code)
	{
	case IP_OPTION_RECORDROUTE:
	  dump_ip_recordroute(holder, opt.length-2);
	  break;

	default:
	  break;
	}
    }
}
