/**************************************************************************** 
** File: bgp.c
**
** Author: Mike Borella
**
** Comments: Dumps BGP packets 
**
** $Id: bgp.c,v 1.5 2002/03/08 13:57:47 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 "bgp.h"

#define BGP_COMMON_HDR_SIZE    19
#define HOLDER_SIZE            64
#define PREFIX_SIZE            16

/*
 * BGP message types
 */

#define BGP_MSGTYPE_OPEN         1
#define BGP_MSGTYPE_UPDATE       2
#define BGP_MSGTYPE_NOTIFICATION 3
#define BGP_MSGTYPE_KEEPALIVE    4

/* 
 * BGP message type map 
 */

strmap_t bgp_msgtype_map[] = 
  {
    { BGP_MSGTYPE_OPEN,         "open" },
    { BGP_MSGTYPE_UPDATE,       "update" },
    { BGP_MSGTYPE_NOTIFICATION, "notification" },
    { BGP_MSGTYPE_KEEPALIVE,    "keepalive" },
    { 0, "" }
  }; 

/*
 * BGP path attributes 
 */

#define BGP_PATHATTR_ORIGIN            1
#define BGP_PATHATTR_ASPATH            2
#define BGP_PATHATTR_NEXTHOP           3
#define BGP_PATHATTR_MULTIEXITDISC     4
#define BGP_PATHATTR_LOCALPREF         5
#define BGP_PATHATTR_ATOMICAGGREGATE   6
#define BGP_PATHATTR_AGGREGATOR        7
#define BGP_PATHATTR_COMMUNITY         8
#define BGP_PATHATTR_ORIGINATORID      9
#define BGP_PATHATTR_CLUSTERLIST       10
#define BGP_PATHATTR_DPA               11
#define BGP_PATHATTR_ADVERTISER        12
#define BGP_PATHATTR_CLUSTERID         13
#define BGP_PATHATTR_MPREACHNLRI       14
#define BGP_PATHATTR_MPUNREACHNLRI     15
#define BGP_PATHATTR_EXTENDCOMMUNITIES 16
#define BGP_PATHATTR_NEWASPATH         17
#define BGP_PATHATTR_NEWAGGREGATOR     18

/* 
 * BGP path attribute map 
 */

strmap_t bgp_pathattr_map[] = 
  {
    { BGP_PATHATTR_ORIGIN,             "origin" },
    { BGP_PATHATTR_ASPATH,             "AS path" },
    { BGP_PATHATTR_NEXTHOP,            "next hop" },
    { BGP_PATHATTR_MULTIEXITDISC,      "multiexit discrimination" },
    { BGP_PATHATTR_LOCALPREF,          "local preferences" },
    { BGP_PATHATTR_ATOMICAGGREGATE,    "atomic aggregate" },
    { BGP_PATHATTR_AGGREGATOR,         "aggregator" },
    { BGP_PATHATTR_COMMUNITY,          "community" },
    { BGP_PATHATTR_ORIGINATORID,       "originator ID" },
    { BGP_PATHATTR_CLUSTERLIST,        "cluster list" },
    { BGP_PATHATTR_DPA,                "DPA" },
    { BGP_PATHATTR_ADVERTISER,         "advertiser" },
    { BGP_PATHATTR_CLUSTERID,          "cluster ID" },
    { BGP_PATHATTR_MPREACHNLRI,        "multiprotocol reachable NLRI" },
    { BGP_PATHATTR_MPUNREACHNLRI,      "multiprotocol unreachable NLRI" },
    { BGP_PATHATTR_EXTENDCOMMUNITIES,  "extended communities" },
    { BGP_PATHATTR_NEWASPATH,          "new AS path" },
    { BGP_PATHATTR_NEWAGGREGATOR,      "new aggregator" },
    { 0, "" }
  }; 

/*
 * BGP origin types
 */

#define BGP_ORIGIN_IGP        1
#define BGP_ORIGIN_EGP        2
#define BGP_ORIGIN_INCOMPLETE 3

/*
 * BGP origin map
 */

strmap_t bgp_origin_map[] = 
  {
    { BGP_ORIGIN_IGP,          "IGP" },
    { BGP_ORIGIN_EGP,          "EGP" },
    { BGP_ORIGIN_INCOMPLETE,   "incomplete" },
    { 0, "" }
  }; 

/*
 * BGP AS Path types
 */

#define BGP_ASPATH_SET       1
#define BGP_ASPATH_SEQUENCE  2

/*
 * BGP AS Path type map
 */

strmap_t bgp_aspath_map[] = 
  {
    { BGP_ASPATH_SET,          "set" },
    { BGP_ASPATH_SEQUENCE,     "sequence" },
    { 0, "" }
  }; 

/*
 * BGP common header format 
 */

typedef struct bgp_common
{
  u_int8_t  marker[16];
  u_int16_t length;
  u_int8_t  type;
} bgp_common_t;

/*
 * BGP open message format without the version number 
*/

typedef struct bgp_open
{
  u_int16_t    my_as;
  u_int16_t    hold_time;
  u_int32_t    bgp_id;
} bgp_open_t;

extern struct arg_t * my_args;

/*----------------------------------------------------------------------------
**
** dump_bgp_prefix()
**
** Dumps and displays a BGP prefix
**
**----------------------------------------------------------------------------
*/

void dump_bgp_prefix(char * s, packet_t * pkt, int total_len)
{
  u_int8_t   bytes_read = 0;
  u_int8_t   length;
  u_int8_t   prefix[PREFIX_SIZE]; /* Made this big enough for v6 just in case */
  u_int8_t   prefix_length;
  int        i;

  while(1)
    {
      /* Get the length of the prefix */
      if (get_packet_bytes((u_int8_t *) &length, pkt, 1) == 0)
	break;	    
      
      /* Display the length */
      if (!my_args->m)
	display(s, (u_int8_t *) &length, 1, DISP_DEC);

      /* Determine the real length */
      if (length % 8 == 0)
	prefix_length = length / 8;
      else
	prefix_length = (length + 8) / 8;

      /* Get the prefix and pad it with 0's*/
      if (get_packet_bytes((u_int8_t *) prefix, pkt, prefix_length) == 0)
	break;
      for (i=prefix_length; i<PREFIX_SIZE; i++)
	prefix[i] = 0;

      /* Figure out how to display it */
      if (prefix_length > 4)
	{
	  
	}
      else
	{
	  /* IPv4 */
	  if (my_args->m)
	    {
	      display_minimal((u_int8_t *) prefix, prefix_length, 
			      DISP_DOTTEDDEC);
	      display_minimal_string("/");
	      display_minimal((u_int8_t *) &prefix_length, 1, DISP_DEC);
	      display_minimal_string(" ");
	    }
	  else
	    display("    Prefix", (u_int8_t *) prefix, prefix_length, 
		    DISP_DOTTEDDEC);
	}

      /* Check for the end of the list */
      bytes_read = bytes_read + prefix_length + 1;
      if (bytes_read >= total_len)
	break;
    } 
}

/*----------------------------------------------------------------------------
**
** dump_bgp_attr()
**
** Dumps a single BGP attribute
**
**----------------------------------------------------------------------------
*/

void dump_bgp_attr(int attr, packet_t * pkt, int len)
{
  switch(attr)
    {
    case BGP_PATHATTR_ORIGIN:
      {
	u_int8_t value;
	
	/* Get the value */
	if (get_packet_bytes((u_int8_t *) &value, pkt, 1) == 0)
	  return;
	
	/* Display */
	if (my_args->m)
	  {
	    display_minimal_attribute(map2str(bgp_origin_map, value), 
				      (u_int8_t *) &value, 1, DISP_DEC, 1);
	  }
	else
	  {
	    display_strmap("    Attribute", value, bgp_origin_map);
	  }
      }
      break;
      
    case BGP_PATHATTR_ASPATH:
      {
	int       bytes_read = 0;
	u_int8_t  type;
	u_int8_t  length;
	u_int16_t value;
	int       i = 0;

	while(bytes_read < len)
	  {
	    /* Get the type and length */
	    if (get_packet_bytes((u_int8_t *) &type, pkt, 1) == 0)
	      return;
	    if (get_packet_bytes((u_int8_t *) &length, pkt, 1) == 0)
	      return;
	    
	    /* Display */
	    if (my_args->m)
	      {

	      }
	    else
	      {
		display_strmap("    Type", type, bgp_aspath_map);
		display("      Length", (u_int8_t *) &length, 1, DISP_DEC);
	      }
	    
	    /* Grab all of the values */
	    for (i=0; i<length; i++)
	      {
		/* Get it */
		if (get_packet_bytes((u_int8_t *) &value, pkt, 2) == 0)
		  return;

		/* Conversion */
		value = ntohs(value);

		/* Display */
		if (my_args->m)
		  {

		  }
		else
		  display("      Value", (u_int8_t *) &value, 2, DISP_DEC);
	      } /* for */

	    bytes_read = bytes_read + 2*length + 2;
	  }
      }
      break;

    case BGP_PATHATTR_NEXTHOP:
    case BGP_PATHATTR_ORIGINATORID:
      {
	u_int32_t addr;

	/* Get the address */
	if (get_packet_bytes((u_int8_t *) &addr, pkt, 4) == 0)
	  return;
	
	/* Display */
	if (my_args->m)
	  {
	    display_minimal_attribute("address", (u_int8_t *) &addr, 4, 
				      DISP_DOTTEDDEC, 1);
	  }
	else
	  {
	    display_ipv4("    Address", (u_int8_t *) &addr);
	  }
	
      }
      break; 

    case BGP_PATHATTR_AGGREGATOR:
      {
	u_int16_t   as;
	u_int32_t   addr;

	/* Get the AS and address */
	if (get_packet_bytes((u_int8_t *) &as, pkt, 2) == 0)
	  return;
	if (get_packet_bytes((u_int8_t *) &addr, pkt, 4) == 0)
	  return;

	/* Conversion */
	as = ntohs(as);

	/* Display */
	if (my_args->m)
	  {

	  }
	else
	  {
	    display("    AS", (u_int8_t *) &as, 2, DISP_DEC);
	    display_ipv4("    Address", (u_int8_t *) &addr);
	  }
	
      }
      break;

    case BGP_PATHATTR_COMMUNITY:
      {
	u_int8_t bytes_read = 0;

	while (bytes_read < len)
	  {
	    u_int16_t  as;
	    u_int16_t  value;

	    /* Get the AS and address */
	    if (get_packet_bytes((u_int8_t *) &as, pkt, 2) == 0)
	      return;
	    if (get_packet_bytes((u_int8_t *) &value, pkt, 2) == 0)
	      return;

	    /* Conversion */
	    as = ntohs(as);
	    value = ntohs(value);

	    /* Display */
	    if (my_args->m)
	      {
		
	      }
	    else
	      {
		display("    AS", (u_int8_t *) &as, 2, DISP_DEC);
		display("    Value", (u_int8_t *) &value, 2, DISP_DEC);
	      }	    

	    bytes_read = bytes_read + 4;
	  }
      }
      break;

    case BGP_PATHATTR_CLUSTERLIST:
      {
	u_int8_t bytes_read = 0;

	while (bytes_read < len)
	  {
	    u_int32_t  addr;

	    /* Get the AS and address */
	    if (get_packet_bytes((u_int8_t *) &addr, pkt, 4) == 0)
	      return;

	    /* Display */
	    if (my_args->m)
	      {
		
	      }
	    else
	      {
		display("    Address", (u_int8_t *) &addr, 4, DISP_DOTTEDDEC);
	      }	    

	    bytes_read = bytes_read + 4;
	  }	
      }
      break;

    default:
      {
	dump_hexbytes("    Attribute", pkt, len, 0);
      }
      break;
    }
}

/*----------------------------------------------------------------------------
**
** dump_bgp_pathattr()
**
** Dumps and displays BGP path attributes
**
**----------------------------------------------------------------------------
*/

void dump_bgp_pathattr(packet_t *pkt, int total_len)
{
  u_int8_t bytes_read = 0;

  while(bytes_read < total_len)
    {
      u_int8_t  attr_flags;
      u_int8_t  attr_code;
      u_int8_t  attr_len1;
      u_int16_t attr_len2;
      u_int8_t  optional_bit, transitive_bit, partial_bit, extlen_bit;
      u_int16_t real_len;

      /* Get the flags and code */
      if (get_packet_bytes((u_int8_t *) &attr_flags, pkt, 1) == 0)
	return;
      if (get_packet_bytes((u_int8_t *) &attr_code, pkt, 1) == 0)
	return;
      bytes_read = bytes_read + 2;
      
      /* Conversions */
      optional_bit =   attr_flags >> 7;
      transitive_bit = (attr_flags >> 6) & 0x01;
      partial_bit =    (attr_flags >> 5) & 0x01;
      extlen_bit =     (attr_flags >> 4) & 0x01;

      /* Display the attribute flags and code */
      if (my_args->m)
	{
	  display_minimal_strmap(attr_code, bgp_pathattr_map);
	  display_minimal_string(" ");
	}
      else
	{
	  display_strmap("  Attribute code", attr_code, bgp_pathattr_map);
	  display("    Optional bit", (u_int8_t *) &optional_bit, 1, DISP_DEC);
	  display("    Transitive bit", (u_int8_t *) &transitive_bit, 1, 
		  DISP_DEC);
	  display("    Partial bit", (u_int8_t *) &partial_bit, 1, DISP_DEC);
	  display("    Extended len bit", (u_int8_t *) &extlen_bit, 1, 
		  DISP_DEC);
	}

      /* Get the length, making sure we read the size correctly */
      if (extlen_bit)
	{
	  if (get_packet_bytes((u_int8_t *) &attr_len2, pkt, 2) == 0)
	    return;
	  attr_len2 = ntohs(attr_len2);
	  real_len = attr_len2;
	  bytes_read = bytes_read + 2;
	}
      else
	{
	  if (get_packet_bytes((u_int8_t *) &attr_len1, pkt, 1) == 0)
	    return;
	  real_len = attr_len1;
	  bytes_read = bytes_read + 1;
	}
      /* Display the length */
      if (!my_args->m)
	display("    Length", (u_int8_t *) &real_len, 2, DISP_DEC);

      /* Get and display the attribute.  We'll fix this later... */
      if (real_len > 0)
	{
	  dump_bgp_attr(attr_code, pkt, real_len);
	  bytes_read = bytes_read + real_len;
	}
    } /* while */
}

/*----------------------------------------------------------------------------
**
** dump_bgp()
**
** Dumps and displays BGP packets
**
**----------------------------------------------------------------------------
*/

void dump_bgp(packet_t *pkt)
{
  bgp_common_t bgp_common_hdr;
  char         holder[HOLDER_SIZE];

  /* Display the banner info */
  if (my_args->m)
    display_minimal_string("| BGP ");
  else
    display_header_banner("BGP Header");

  /* Loop over all individual messages */
  while(1)
    {
      /* Get the common header */
      if (get_packet_bytes((u_int8_t *) &bgp_common_hdr, pkt, 
			   BGP_COMMON_HDR_SIZE) == 0)
	break;
      
      /* Conversions */
      bgp_common_hdr.length = ntohs(bgp_common_hdr.length);
      
      /* Display the common header */
      if (my_args->m)
	{
	  display_minimal_string("[");
	  display_minimal_strmap(bgp_common_hdr.type, bgp_msgtype_map);
	  display_minimal_string(" ");
	}
      else
	{
	  snprintf(holder, HOLDER_SIZE, "%d (%s)", bgp_common_hdr.type, 
		   map2str(bgp_msgtype_map, bgp_common_hdr.type));
	  display_string("Message type", holder); 
	  display("  Marker", (u_int8_t *) &bgp_common_hdr.marker, 16, 
		  DISP_HEX);
	  display("  Length", (u_int8_t *) &bgp_common_hdr.length, 2, DISP_DEC);
	}
      
      /* Decide how to continue based on message type */
      switch(bgp_common_hdr.type)
	{
	case BGP_MSGTYPE_OPEN:
	  {
	    u_int8_t   version;
	    bgp_open_t open;
	    u_int8_t   option_len;

	    /* Get the version then get the rest of the packet open header */
	    if (get_packet_bytes((u_int8_t *) &version, pkt, 1) == 0)
	      break;
	    if (get_packet_bytes((u_int8_t *) &open, pkt, sizeof(bgp_open_t))
		== 0)
	      break;
	    
	    /* Conversions */
	    open.my_as = ntohs(open.my_as);
	    open.hold_time = ntohs(open.hold_time);
	    
	    /* Display */
	    if (my_args->m)
	      {
		
	      }
	    else
	      {
		display("  Version", (u_int8_t *) &version, 1, DISP_DEC);
		display("  My AS", (u_int8_t *) &open.my_as, 2, DISP_DEC);
		display("  Hold time", (u_int8_t *) &open.hold_time, 2, 
			DISP_DEC);
		display_ipv4("  BGP ID", (u_int8_t *) &open.bgp_id);
	      }

	    /* Get options, if we need to */
	    if (get_packet_bytes((u_int8_t *) &option_len, pkt, 1) == 0)
	      break;	    

	    /* Display */
	    if (!my_args->m)
	      display("  Options length", (u_int8_t *) &option_len, 1, 
		      DISP_DEC);
	    
	    if (option_len > 0)
	      dump_hexbytes("  Options", pkt, option_len, 0);
	  }
	  break;
	  
	case BGP_MSGTYPE_UPDATE:
	  {
	    u_int16_t  withdrawn_routes_len;
	    u_int16_t  total_path_attr_len;

	    /* Get the withdrawn routes len */
	    if (get_packet_bytes((u_int8_t *) &withdrawn_routes_len, pkt, 2) 
		== 0)
	      break;

	    /* Conversion */
	    withdrawn_routes_len = ntohs(withdrawn_routes_len);

	    /* Display */
	    if (!my_args->m)
	      display("  Withdrawn routes len", 
		      (u_int8_t *) &withdrawn_routes_len, 2, DISP_DEC);
	    
	    /* Parse and display the withdrawn routes */
	    if (withdrawn_routes_len > 0)
	      dump_bgp_prefix("  Withdrawn route len", pkt, 
			      withdrawn_routes_len);
	    
	    /* Get the total path attr len */
	    if (get_packet_bytes((u_int8_t *) &total_path_attr_len, pkt, 2) 
		== 0)
	      break;
	    
	    /* Conversion */
	    total_path_attr_len = ntohs(total_path_attr_len);

	    /* Display */
	    if (!my_args->m)
	      display("  Total path attr len", 
		      (u_int8_t *) &total_path_attr_len, 2, DISP_DEC);
	    
	    /* Dump all of the path attributes */
	    dump_bgp_pathattr(pkt, total_path_attr_len);

	    /* Dump the NLRI's */
	    dump_bgp_prefix("  NLRI length", pkt, 
			    bgp_common_hdr.length - BGP_COMMON_HDR_SIZE -
			    withdrawn_routes_len - total_path_attr_len - 4);
	  }
	  break;
	  
	case BGP_MSGTYPE_NOTIFICATION:
	  {
	    u_int8_t  error_code;
	    u_int8_t  error_subcode;

	    /* Get the code and subcode */
	    if (get_packet_bytes((u_int8_t *) &error_code, pkt, 1) == 0)
	      break;
	    if (get_packet_bytes((u_int8_t *) &error_subcode, pkt, 1) == 0)
	      break;

	    /* Display */
	    if (my_args->m)
	      {
	      }
	    else
	      {
		display("  Error code", (u_int8_t *) &error_code, 1, DISP_DEC);
		display("  Error subcode", (u_int8_t *) &error_subcode, 1, 
			DISP_DEC);
	      }

	    /* Figure out the length of the data and get it */
	    dump_hexbytes("  Data", pkt, 
			  bgp_common_hdr.length - BGP_COMMON_HDR_SIZE - 2, 0);
	  }
	  break;
	  
	case BGP_MSGTYPE_KEEPALIVE:
	  {
	    /* Do nothing because keepalives have no data */
	  }
	  break;
	  
	default:
	  {
	    /* If we don't understand the message, dump the rest of it */
	    dump_hexbytes("  Value", pkt, 
			  bgp_common_hdr.length - BGP_COMMON_HDR_SIZE, 0);
	  }
	  break;
	}

      /* Close off this message in minimal mode */
      if (my_args->m)
	display_minimal_string("] ");

    } /* while (1) */
  
  /* dump the hex buffer */
  hexbuffer_flush();

}
