/**************************************************************************** 
** File: gre.c
**
** Author: Mike Borella
**
** Comments: 
**
** $Id: gre.c,v 1.12 2001/11/15 00:22:36 mborella 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 "gre.h"
#include "ip_protocols.h"
#include "ethertypes.h"

extern struct arg_t * my_args;
extern strmap_t ipproto_map[];
extern strmap_t ethertype_map[];

/*----------------------------------------------------------------------------
**
** dump_gre()
**
** Displays both versions of GRE headers
** 
**----------------------------------------------------------------------------
*/

void dump_gre(packet_t *pkt)
{
  gre_v0_rfc2784_t gre0;
  gre_v1_t gre1;
  ethertype_func_t et;

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

  /*
   * Stats accounting
   */

  stats_update(STATS_GRE);

  /*
   * Look at the first 2 bytes of the GRE header so that we can check the 
   * version
   */

  if (look_packet_bytes((u_int8_t *) &gre0, pkt, 2) == 0)
    return;
  
  /*
   * Check version number
   */

  switch(gre0.version)
    {
    case 0:
      {
	u_int16_t reserved;
	u_int16_t protocol;
	ethertype_func_t et;

	/* 
	 * Please note: If the reserved field is 0, we assume that 
	 * the GRE packet follows RFC2784 and act accordingly.  Otherwise
	 * we assume the RFC1701 interpretation.  If key and sequence
	 * number fields are used, ala RFC2890, then we parse according
	 * RFC1701.  Got all that?
	 */

	reserved = (gre0.reserved_high << 5) || gre0.reserved_low;
	protocol = ntohs(gre0.protocol);
	if (reserved == 0)
	  { 
	    gre_v0_rfc2784_t gre;
	    u_int8_t C;

	    /*
	     * Get the header 
	     */
	
	    if (get_packet_bytes((u_int8_t *) &gre, pkt, 
				 sizeof(gre_v0_rfc2784_t)) == 0)
	      return;
	    
	    /*
	     * Conversions
	     */
	    
	    gre.protocol = ntohs(gre.protocol);
	    protocol = gre.protocol;
	    C = gre.c;
	    
	    /* Dump the header */
	    if (my_args->m)
	      {
		display_minimal_string("| GRE v0 ");
		display_minimal((u_int8_t *) &gre.protocol, 2, DISP_HEX);
	      }
	    else
	      {
		display_header_banner("GREv0 Header");
		display("C", &C, 1, DISP_DEC);
		display("Reserved", (u_int8_t *) &reserved, 2, DISP_DEC);
		display_strmap_hex("Protocol", gre.protocol, 
				   ethertype_map);
	      }
	    
	    /*
	     * Get the checksum and reserved fields, if they exist
	     */
	    
	    if (C)
	      {
		u_int16_t checksum;
		u_int16_t reserved;
		
		/* Get the fields */
		if (get_packet_bytes((u_int8_t *) &checksum, pkt, 2) == 0)
		  return;
		if (get_packet_bytes((u_int8_t *) &reserved, pkt, 2) == 0)
		  return;
		
		/* conversions */
		checksum = ntohs(checksum);
		reserved = ntohs(reserved);
		
		if (!my_args->m)
		  {
		    display("Checksum", (u_int8_t *) &checksum, 2, DISP_DEC);
		    display("Reserved", (u_int8_t *) &reserved, 2, DISP_DEC);
		  }
	      }
	  }
	else
	  {
	    gre_v0_rfc1701_t gre;
	    u_int8_t C, R, K, S, s, recur, flags, version;

	    /*
	     * Get the header 
	     */
	
	    if (get_packet_bytes((u_int8_t *) &gre, pkt, 
				 sizeof(gre_v0_rfc1701_t)) == 0)
	      return;
	    
	    /*
	     * Conversions
	     */
	    
	    gre.protocol = ntohs(gre.protocol);
	    protocol = gre.protocol;
	    C = gre.c;
	    R = gre.r;
	    K = gre.k;
	    S = gre.S;
	    s = gre.s;
	    recur = gre.recur;
	    flags = gre.flags;
	    version = gre.version;
	    
	    /* Dump the header */
	    if (my_args->m)
	      {
		display_minimal_string("| GRE v0 ");
		display_minimal((u_int8_t *) &gre.protocol, 2, DISP_HEX);
		display_minimal_string(" ");
	      }
	    else
	      {
		display_header_banner("GREv0 Header");
		display("C", &C, 1, DISP_DEC);
		display("R", &R, 1, DISP_DEC);
		display("K", &K, 1, DISP_DEC);
		display("S", &S, 1, DISP_DEC);
		display("s", &s, 1, DISP_DEC);
		display("Recur", &recur, 1, DISP_DEC);
		display("Flags", &flags, 1, DISP_DEC);
		display("Version", &version, 1, DISP_DEC);
		display_strmap_hex("Protocol", gre.protocol, 
				   ethertype_map);
	      }

	    /* 
	     * Parse options fields. Note that if the C or the R bits are
	     * present then both the checksum and offset fields are present
	     */

	    if (C || R)
	      {
		u_int16_t checksum;
		u_int16_t offset;
		
		/* 
		 * Nothing to report for minimal mode here, so just get
		 * the checksum and offset and display them.
		 */
		
		if (get_packet_bytes((u_int8_t *) &checksum, pkt, 2)
		    == 0)
		  return;
		checksum = ntohs(checksum);
		if (!my_args->m)
		  display("Checksum", (u_int8_t *) &checksum, 2, DISP_DEC);
		
		if (get_packet_bytes((u_int8_t *) &offset, pkt, 2)
		    == 0)
		  return;
		offset = ntohs(offset);
		if (!my_args->m)
		  display("Offset", (u_int8_t *) &offset, 2, DISP_DEC);
	      }

	    /* Check for the K bit (key field) */
	    if (K)
	      {
		u_int32_t key;
		
		if (get_packet_bytes((u_int8_t *) &key, pkt, 4) == 0)
		  return;
		key = ntohl(key);
		if (!my_args->m)
		  display("Key", (u_int8_t *) &key, 4, DISP_DEC);
	      }
	    
	    /* Check for the sequence number field */
	    if (S)
	      {
		u_int32_t seqno;
		
		if (get_packet_bytes((u_int8_t *) &seqno, pkt, 4) == 0)
		  return;
		seqno = ntohl(seqno);
		if (!my_args->m)
		  display("Sequence number", (u_int8_t *) &seqno, 4, 
			  DISP_DEC);
	      }
	    
	    /* Check for routing field */
	    if (R)
	      {
		u_int32_t routing;
		
		if (get_packet_bytes((u_int8_t *) &routing, pkt, 4) == 0)
		  return;
		routing = ntohs(routing);
		if (!my_args->m)
		  display("Routing", (u_int8_t *) &routing, 4, DISP_DEC);
	      }
	  }
	
	/* flush the hexbuffer */
	hexbuffer_flush();

	/*
	 * Display the rest of the packet
	 */
	
	et = ethertype2func(protocol);
	if (et)
	  {
	    et(pkt);
	  }
	
      }
      break;

    case 1: /* PPTP version */
      {
	u_int8_t C, R, K, S, s, recur, A, flags, version;

	/*
	 * Get the header 
	 */
	
	if (get_packet_bytes((u_int8_t *) &gre1, pkt, sizeof(gre_v1_t)) == 0)
	  return;
	
	/*
	 * Conversions
	 */

	C = gre1.C_bit;
	R = gre1.R_bit;
	K = gre1.K_bit;
	S = gre1.S_bit;
	s = gre1.s_bit;
	recur = gre1.recur;
	A = gre1.A_bit;
	flags = gre1.flags;
	version = gre1.version;
	gre1.protocol = ntohs(gre1.protocol);
	gre1.payload_len = ntohs(gre1.payload_len);
	gre1.call_id = ntohs(gre1.call_id);

	/*
	 * Dump header 
	 */

	if (my_args->m)
	  {
	    display_minimal_string("| GREv1 ");
	  }
	else
	  {
	    /* announcement */
	    display_header_banner("GREv1 Header");
	    
	    /* fields */
	    display("Checksum (C) bit", &C, 1, DISP_BINNLZ);
	    display("Routing (R) bit", &R, 1, DISP_BINNLZ);
	    display("Key present (K) bit", &K, 1, DISP_BINNLZ);
	    display("Seq. # present (S) bit", &S, 1, DISP_BINNLZ);
	    display("Strict src rte (s) bit", &s, 1, DISP_BINNLZ);
	    display("Recursion control", &recur, 1, DISP_DEC);
	    display("Ack. # present (A) bit", &A, 1, DISP_BINNLZ);
	    display("Flags", &flags, 1, DISP_DEC);
	    display("Version", &version, 1, DISP_DEC);
	    display_strmap_hex("Protocol", gre1.protocol, ethertype_map);
	    display("Payload length", (u_int8_t *) &gre1.payload_len, 2, 
		    DISP_DEC);
	    display("Call ID", (u_int8_t *) &gre1.call_id, 2, DISP_DEC);
	  }

	/*
	 * Check for sequence and acknowledgement number fields
	 */

	if (S)
	  {
	    u_int32_t seqno;

	    if (get_packet_bytes((u_int8_t *) &seqno, pkt, 4) == 0)
	      return;
	    seqno = ntohl(seqno);
	    if (my_args->m)
	      {
		display_minimal_string("seq ");
		display_minimal((u_int8_t *) &seqno, 4, DISP_DEC);
		display_minimal_string(" ");
	      }
	    else 
	      display("Sequence number", (u_int8_t *) &seqno, 4, DISP_DEC);
	  }
	
	if (A)
	  {
	    u_int32_t ackno;

	    if (get_packet_bytes((u_int8_t *) &ackno, pkt, 4) == 0)
	      return;
	    ackno = ntohl(ackno);
	    if (my_args->m)
	      {
		display_minimal_string("ack ");
		display_minimal((u_int8_t *) &ackno, 4, DISP_DEC);
		display_minimal_string(" ");
	      }
	    else
	      display("Acknowledgement number", (u_int8_t *) &ackno, 4, 
		      DISP_DEC);
	  }

	/* dump the hex buffer */
	hexbuffer_flush();
	
	/*
	 * Display the rest of the packet
	 */
	
	et = ethertype2func(gre1.protocol);
	if (et)
	  {
	    et(pkt);
	  }
	else
	  {
	    /* 
	     * If we can't map the type, and we're in minimal mode, 
	     * dump the protocol type 
	     */
	    
	    if (my_args->m)
	      {
		display_minimal_string("(");
		display_minimal((u_int8_t *) &gre1.protocol, 2, DISP_HEX);
		display_minimal_string(") ");
	      }
	  }
	break;
	
      default:
	break;
      }
      
      
    }
  
}
