/**************************************************************************** 
** File: icmp.c
**
** Author: Mike Borella
**
** Comments: Dump ICMP information
**
** $Id: icmp.c,v 1.18 2007/06/25 12:23:16 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 "icmp.h"
#include "ip.h"

#define HOLDER_SIZE 64

/*
 * Format of ICMP echo request and reply
 */

typedef struct icmp_echo
{
  u_int16_t id;
  u_int16_t seqno;
} icmp_echo_t;

/*
 * Format of ICMP mask request and reply
 */

typedef struct icmp_mask
{
  u_int16_t id;
  u_int16_t seqno;
  u_int32_t mask;
} icmp_mask_t;

/*
 * Format of ICMP router advertisement
 */

typedef struct icmp_routeradvert
{
  u_int8_t  addresses;
  u_int8_t  address_size;
  u_int16_t lifetime;
} icmp_routeradvert_t;

/* 
 * Contains the descriptions of ICMP types 
 */

strmap_t icmp_type_map[] = 
{
  { ICMP_TYPE_ECHOREPLY ,      "echo reply" },
  { ICMP_TYPE_DESTUNREACHABLE, "destination unreachable" },
  { ICMP_TYPE_SOURCEQUENCH,    "source quench" },
  { ICMP_TYPE_REDIRECT,        "redirect" },
  { ICMP_TYPE_ECHOREQUEST,     "echo request" },
  { ICMP_TYPE_ROUTERADVERT,    "router advertisement" },
  { ICMP_TYPE_ROUTERSOLICIT,   "router solicitation" },
  { ICMP_TYPE_TIMEEXCEEDED,    "time exceeded" },
  { ICMP_TYPE_PARAMPROBLEM,    "parameter problem" },
  { ICMP_TYPE_TIMESTAMP,       "timestamp" },
  { ICMP_TYPE_TIMESTAMPREPLY,  "timestamp reply" },
  { ICMP_TYPE_INFOREQUEST,     "information request" },
  { ICMP_TYPE_INFOREPLY,       "information reply" },
  { ICMP_TYPE_MASKREQUEST,     "mask request" },
  { ICMP_TYPE_MASKREPLY,       "mask reply" },
  { ICMP_TYPE_TRACEROUTE,      "traceroute" },
  { ICMP_TYPE_CONVERSIONERROR, "datagram conversion error" },
  { 0, "" }
};

/* 
 * Contains the descriptions of ICMP destination unreachable messages
 */

strmap_t icmp_du_map[] = 
{
  { ICMP_DU_NET,               "network unreachable" },
  { ICMP_DU_HOST,              "host unreachable" },
  { ICMP_DU_PROTOCOL,          "protocol unreachable" },
  { ICMP_DU_PORT,              "port unreachable"},
  { ICMP_DU_FRAG,              "fragmentation needed"},
  { ICMP_DU_SRCRTEFAIL,        "source route failed"},
  { ICMP_DU_NETUNKNOWN,        "network unknown"},
  { ICMP_DU_HOSTUNKNOWN,       "host unknown"},
  { ICMP_DU_SRCISOLATED,       "source isolated"},
  { ICMP_DU_NETADMIN,          "network admin prohibited"},
  { ICMP_DU_HOSTADMIN,         "host admin prohibited"},
  { ICMP_DU_NETTOS,            "network unreachable for TOS"},
  { ICMP_DU_HOSTTOS,           "host unreachable for TOS"},
  { ICMP_DU_ADMIN,             "admin prohibited"},
  { ICMP_DU_HOSTPRECVIOL,      "host precedence violation"},
  { ICMP_DU_PRECCUTOFF,        "precendence cutoff"},
  { 0, "" }
};

/* 
 * Contains the descriptions of ICMP destination unreachable messages
 */

strmap_t icmp_timeexceeded_map[] = 
{
  { ICMP_TIMEEXCEEDED_TTL,               "TTL equals 0" },
  { ICMP_TIMEEXCEEDED_REASSEMBLY,        "TTL expired in reassembly"}
};

extern struct arg_t * my_args;

/*----------------------------------------------------------------------------
**
** dump_icmp_echo()
**
** Parse ICMP echo request and echo reply fields.  Note that we don't have to 
** check for the -n option because if it is set, we shouldn't ever get here.
**
**----------------------------------------------------------------------------
*/

void dump_icmp_echo(packet_t *pkt)
{
  icmp_echo_t echo;

  /*
   * Get the Echo Request/Reply fields
   */

  if (get_packet_bytes((u_int8_t *) &echo, pkt, 4) == 0)
    return;

   /*
    * Conversions 
    */

   echo.id = ntohs ( echo.id );
   echo.seqno = ntohs ( echo.seqno );

  /*
   * Dump the ID and sequence #
   */

  if (my_args->m)
    {
      display_minimal((u_int8_t *) &echo.seqno, 2, DISP_DEC);
      display_minimal_string(" ");
    }
  else
    {
      display("Identifier", (u_int8_t *) &echo.id, 2, DISP_DEC);
      display("Sequence number", (u_int8_t *) &echo.seqno, 2, DISP_DEC);
    }
}

/*----------------------------------------------------------------------------
**
** dump_icmp_mask()
**
** Parse ICMP mask request and reply fields
**
**----------------------------------------------------------------------------
*/

void dump_icmp_mask(packet_t *pkt)
{
  icmp_mask_t mask;

  /*
   * Get the Mask Request/Reply fields
   */

  if (get_packet_bytes((u_int8_t *) &mask, pkt, 8) == 0)
    return;

  /*
   * Conversions
   */

   mask.id = ntohs ( mask.id );
   mask.seqno = ntohs ( mask.seqno );   

  /*
   * Dump the mask
   */

  if (my_args->m)
    {
      display_minimal_ipv4((u_int8_t *) &mask.mask);
      display_minimal_string ( " " );
    }
  else
    {
      display ( "Identifier", (u_int8_t *) &mask.id, 2, DISP_DEC );
      display ( "Sequence Number", (u_int8_t *) &mask.seqno, 2, DISP_DEC );
      display_ipv4("Mask", (u_int8_t *) &mask.mask);
    }

}

/*----------------------------------------------------------------------------
**
** dump_icmp_routeradvert()
**
** Parse ICMP router advertisement
**
**----------------------------------------------------------------------------
*/

void dump_icmp_routeradvert(packet_t *pkt)
{
  icmp_routeradvert_t ra;
  u_int32_t addr;
  u_int32_t preference;
  int i;

  /*
   * Get the basic fields
   */

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

  /* Conversions */
  ra.lifetime = ntohs(ra.lifetime);

  /*
   * Dump the basic fields
   */

  if (my_args->m)
    {

    }
  else
    {
      display("Addresses", (u_int8_t *) &ra.addresses, 1, DISP_DEC);
      display("Address size", (u_int8_t *) &ra.address_size, 1, DISP_DEC);
      display("Lifetime", (u_int8_t *) &ra.lifetime, 2, DISP_DEC);
    }

  /* 
   * Loop through addresses
   */

  for (i=0; i < ra.addresses; i++)
    {
      if (get_packet_bytes((u_int8_t *) &addr, pkt, 4) == 0)
	return;
      if (get_packet_bytes((u_int8_t *) &preference, pkt, 4) == 0)
	return;

      /* Conversions */
      preference = ntohl(preference);

      if (my_args->m)
	{
	  display_minimal_ipv4((u_int8_t *) &addr);
	  display_minimal_string("(");
	  display_minimal((u_int8_t *) &preference, 4, DISP_DEC);
	  display_minimal_string(") ");
	}
      else
	{
	  display_ipv4("Address", (u_int8_t *) &addr);
	  display("Preference", (u_int8_t *) &preference, 4, DISP_DEC);
	}
    }
}

/*----------------------------------------------------------------------------
**
** dump_icmp()
**
** Parse ICMP header and dump fields
**
**----------------------------------------------------------------------------
*/

void dump_icmp(packet_t *pkt)
{
  icmp_header_t icmp;
  char holder[HOLDER_SIZE];

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

  /*
   * Stats accounting
   */

  stats_update(STATS_ICMP);

  /*
   * Get the header
   */

  if (get_packet_bytes((u_int8_t *) &icmp, pkt, 4) == 0)
    return;

  if (my_args->m)
    {
      /* In minimal mode lets just dump the type and code */
      display_minimal_string("| ICMP ");
      display_minimal_string(map2str(icmp_type_map, icmp.type));
      display_minimal_string(" ");
      switch(icmp.type)
	{
	case ICMP_TYPE_DESTUNREACHABLE:
	  display_minimal_string(map2str(icmp_du_map, icmp.code));
	  break;
	  
	case ICMP_TYPE_TIMEEXCEEDED:
	  display_minimal_string(map2str(icmp_timeexceeded_map, icmp.code));
	  break;
	    
	case ICMP_TYPE_ECHOREPLY:
	case ICMP_TYPE_ECHOREQUEST:
	  dump_icmp_echo(pkt);
	  break;

	case ICMP_TYPE_MASKREQUEST:
	case ICMP_TYPE_MASKREPLY:
	  dump_icmp_mask(pkt);
	  break; 

	case ICMP_TYPE_ROUTERADVERT:
	  dump_icmp_routeradvert(pkt);
	  break;

	case ICMP_TYPE_ROUTERSOLICIT:
	  return;

	case ICMP_TYPE_SOURCEQUENCH:
	case ICMP_TYPE_REDIRECT:
	case ICMP_TYPE_PARAMPROBLEM:
	case ICMP_TYPE_TIMESTAMP:
	case ICMP_TYPE_TIMESTAMPREPLY:
	case ICMP_TYPE_INFOREQUEST:
	case ICMP_TYPE_INFOREPLY:
	case ICMP_TYPE_TRACEROUTE:
	case ICMP_TYPE_CONVERSIONERROR:
	  break;

	default:
	  break;
	}
    }
  else
    {
      /* announcement */
      display_header_banner("ICMP Header");
      
      /* Dump the type */
      snprintf(holder, HOLDER_SIZE, "%d (%s)", icmp.type, 
	      map2str(icmp_type_map, icmp.type));
      display("Type", (u_int8_t *) holder, strlen(holder), DISP_STRING);
      
      /* 
       * Based on the type, dump the code, if the type has codes
       */
      
      switch(icmp.type)
	{
	case ICMP_TYPE_DESTUNREACHABLE:
	  snprintf(holder, HOLDER_SIZE, "%d (%s)", icmp.code, 
		  map2str(icmp_du_map, icmp.code));
	  display("Code", (u_int8_t *) &holder, strlen(holder), DISP_STRING);
	  break;
	  
	case ICMP_TYPE_TIMEEXCEEDED:
	  snprintf(holder, HOLDER_SIZE, "%d (%s)", icmp.code, 
		  map2str(icmp_timeexceeded_map, icmp.code));
	  display("Code", (u_int8_t *) &holder, strlen(holder), DISP_STRING);
	  break;
	  
	default:
	  display("Code", (u_int8_t *) &icmp.code, 1, DISP_DEC);
	  break;
	}
      
      icmp.checksum = ntohs(icmp.checksum);
      display("Checksum", (u_int8_t *) &icmp.checksum, 2, DISP_DEC);
      
      /* 
       * Call an ICMP-type-specific function 
       */
      
      switch (icmp.type) 
	{
	case ICMP_TYPE_ECHOREPLY:
	case ICMP_TYPE_ECHOREQUEST:
	  dump_icmp_echo(pkt);
	  
	  /* dump the hex buffer */
	  hexbuffer_flush();
	  
	  return; /* bail so that we don't continue reading */
	  
	case ICMP_TYPE_DESTUNREACHABLE:
	  /* Get the unused bytes */
	  if (get_packet_bytes((u_int8_t *) &holder, pkt, 4) == 0)
	    return;
	  break;
	  
	case ICMP_TYPE_ROUTERSOLICIT:
	  /* Get the unused bytes */
	  if (get_packet_bytes((u_int8_t *) &holder, pkt, 4) == 0)
	    return;
	  /* dump the hex buffer */
	  hexbuffer_flush();
	  return;
	  
	case ICMP_TYPE_ROUTERADVERT:
	  dump_icmp_routeradvert(pkt);
	  /* dump the hex buffer */
	  hexbuffer_flush();
	  return;
	  
	case ICMP_TYPE_SOURCEQUENCH:
	case ICMP_TYPE_REDIRECT:
	case ICMP_TYPE_PARAMPROBLEM:
	case ICMP_TYPE_TIMESTAMP:
	case ICMP_TYPE_TIMESTAMPREPLY:
	case ICMP_TYPE_INFOREQUEST:
	case ICMP_TYPE_INFOREPLY:
	case ICMP_TYPE_TRACEROUTE:
	case ICMP_TYPE_CONVERSIONERROR:
	  break;
	  
	case ICMP_TYPE_TIMEEXCEEDED:
	  /* Get the unused bytes */
	  if (get_packet_bytes((u_int8_t *) &holder, pkt, 4) == 0)
	    return;
	  break;
	  
	case ICMP_TYPE_MASKREQUEST:
	case ICMP_TYPE_MASKREPLY:
	  dump_icmp_mask(pkt);
	  break;
	  
	default:
	  break;
	  
	}
      
      /* dump the hex buffer */
      hexbuffer_flush();
      
      /*
       * In most ICMP packets there will be an IP header after the ICMP hdr.
       * If it seems to be there, we can parse it out
       */
      
      if (get_packet_apparentbytesleft(pkt) >= 20)
	dump_ip(pkt);
    }
  
}

