/**************************************************************************** 
** File: icmpv6.c
**
** Author: Mike Borella
**
** Comments: Dump ICMPv6 information
**
** $Id: icmpv6.c,v 1.18 2007/06/25 12:25:36 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 "ipv6.h"
#include "icmpv6.h"

#define HOLDER_SIZE 64

/*
 * ICMPv6 type map
 */

strmap_t icmpv6_type_map [] =
{
  { ICMPV6_TYPE_DESTUNREACHABLE,    "destination unreachable" },
  { ICMPV6_TYPE_PACKETTOOBIG,       "packet too big" },
  { ICMPV6_TYPE_TIMEEXCEEDED,       "time exceeded" },
  { ICMPV6_TYPE_PARAMETERPROBLEM,   "parameter problem" },
  { ICMPV6_TYPE_ECHOREQUEST,        "echo request" },
  { ICMPV6_TYPE_ECHOREPLY,          "echo reply" },
  { ICMPV6_TYPE_GROUPMEMQUERY,      "group membership query" },
  { ICMPV6_TYPE_GROUPMEMREPORT,     "group membership report" },
  { ICMPV6_TYPE_GROUPMEMREDUCTION,  "group membership reduction" },
  { ICMPV6_TYPE_ROUTERSOLICIT,      "router solicitation" },
  { ICMPV6_TYPE_ROUTERADVERT,       "router advertisement" },
  { ICMPV6_TYPE_NEIGHBORSOLICIT,    "neighbor solicitation" },
  { ICMPV6_TYPE_NEIGHBORADVERT,     "neighbor advertisement" },
  { ICMPV6_TYPE_REDIRECT,           "redirect" },
  { 0, "" }
};

/*
 * ICMPv6 destination unreachable code map
 */

strmap_t icmpv6_ducode_map [] = 
{
  { ICMPV6_DUCODE_NOROUTE,          "no route" },
  { ICMPV6_DUCODE_ADMIN,            "administratively prohibited" },
  { ICMPV6_DUCODE_ADDRUNREACHABLE,  "address unreachable" },
  { ICMPV6_DUCODE_PORTUNREACHABLE,  "port unreachable" },
  { 0, "" }
};

extern struct arg_t *my_args;

/*----------------------------------------------------------------------------
**
** dump_icmpv6()
**
** Parse ICMPv6 header and dump fields
**
**----------------------------------------------------------------------------
*/

void dump_icmpv6(packet_t *pkt)
{
  icmpv6_header_t icmpv6;
  char            holder[HOLDER_SIZE];
  u_int32_t       parameter;
  u_int16_t       ping6_id, ping6_seqno;
  u_int8_t        v6addr[16];

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

  /*
   * Stats accounting
   */

  stats_update(STATS_ICMPV6);

  /*
   * Get the ICMPv6 header
   */

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

  /*
   * Conversions
   */

  icmpv6.checksum = ntohs(icmpv6.checksum);

  /*
   * Dump header
   */

  if (my_args->m)
    {
      display_minimal_string("| ICMPv6 ");
      display_minimal_string(map2str(icmpv6_type_map, icmpv6.type));
      display_minimal_string(" ");
    }
  else
    {
      /* announcement */
      display_header_banner("ICMPv6 Header");
      
      /* print type */
      snprintf(holder, HOLDER_SIZE, "%d (%s)", icmpv6.type, 
	      map2str(icmpv6_type_map, icmpv6.type));
      display_string("Type", holder);
      
      /* based on the type, decide what to do with the code */
      switch(icmpv6.type)
	{
	case ICMPV6_TYPE_DESTUNREACHABLE:
	  snprintf(holder, HOLDER_SIZE, "%d (%s)", icmpv6.code, 
		  map2str(icmpv6_ducode_map, icmpv6.code));
	  display_string("Code", holder);
	  break;
	  
	default:
	  display("Code", (u_int8_t *) &icmpv6.code, 1, DISP_DEC);
	}
      
      display("Checksum", (u_int8_t *) &icmpv6.checksum, 2, DISP_DEC);
    }
  
  /*
   * Special processing of the rest of the ICMP part
   */

  switch(icmpv6.type)
    {
    case ICMPV6_TYPE_DESTUNREACHABLE:
    case ICMPV6_TYPE_TIMEEXCEEDED:
      /* skip unused 4 bytes */
      if (skip_packet_bytes(pkt, 4) == 0)
	return;
      /* dump the contained IPv6 header */
      if (get_packet_apparentbytesleft(pkt))
	dump_ipv6(pkt);
      break;

    case ICMPV6_TYPE_PACKETTOOBIG:
      /* next 4 bytes should be the mtu, get them, convert, display */
      if (get_packet_bytes((u_int8_t *) &parameter, pkt, 4) == 0)
	return;
      parameter = ntohl(parameter);
      display("MTU", (u_int8_t *) &parameter, 4, DISP_DEC);
      
      /* dump the contained IPv6 header */
      if (get_packet_apparentbytesleft(pkt))
	dump_ipv6(pkt);
      break;

    case ICMPV6_TYPE_PARAMETERPROBLEM:
      /* next 4 bytes should be a pointer, get them, convert, display */
      if (get_packet_bytes((u_int8_t *) &parameter, pkt, 4) == 0)
	return;
      parameter = ntohl(parameter);
      display("Pointer", (u_int8_t *) &parameter, 4, DISP_DEC);
      
      /* dump the contained IPv6 header */
      if (get_packet_apparentbytesleft(pkt))
	dump_ipv6(pkt);
      break;

    case ICMPV6_TYPE_ECHOREQUEST:
    case ICMPV6_TYPE_ECHOREPLY:
      /* Next 4 bytes are an id followed by a seqno: get, convert, display */
      if (get_packet_bytes((u_int8_t *) &ping6_id, pkt, 2) == 0)
	return;
      if (get_packet_bytes((u_int8_t *) &ping6_seqno, pkt, 2) == 0)
	return;
      ping6_id = ntohs(ping6_id);
      ping6_seqno = ntohs(ping6_seqno);
      if (my_args->m)
	{
          display_minimal((u_int8_t *) &ping6_seqno, 2, DISP_DEC);
	}
      else
	{
	  display("Identifier", (u_int8_t *) &ping6_id, 2, DISP_DEC);
      	  display("Sequence number", (u_int8_t *) &ping6_seqno, 2, 
		   DISP_DEC);
	}
      break;

    case ICMPV6_TYPE_NEIGHBORSOLICIT:
      /* Skip 4 unused bytes */
      if ( skip_packet_bytes (pkt,4) == 0 )
         return 0;

      /* Next 16 bytes are an IPv6 address */
      if (get_packet_bytes((u_int8_t *) &v6addr, pkt, 16) == 0)
	return;
      if (my_args->m)
	{
	  display_minimal_ipv6((u_int8_t *) &v6addr);
	  display_minimal_string(" ");
	}
      else
	display_ipv6("Address", (u_int8_t *) &v6addr);
      break;
      
    default:
      break;
    }
  
}




