/**************************************************************************** 
**
** File: l2tp.c
**
** Author: Mike Borella
**
** Comments: Dump L2TP header information
**
** $Id: l2tp.c,v 1.11 2007/06/25 13:08:18 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 "l2tp.h"
#include "ppp.h"

#define L2TP_AVP_SIZE  6

/*
 * Control message types 
 */

#define L2TP_CTRL_RESERVED1    0
#define L2TP_CTRL_SCCRQ        1
#define L2TP_CTRL_SCCRP        2
#define L2TP_CTRL_SCCCN        3
#define L2TP_CTRL_STOPCCN      4
#define L2TP_CTRL_RESERVED2    5
#define L2TP_CTRL_HELLO        6
#define L2TP_CTRL_OCRQ         7
#define L2TP_CTRL_OCRP         8
#define L2TP_CTRL_OCCN         9
#define L2TP_CTRL_ICRQ         10
#define L2TP_CTRL_ICRP         11
#define L2TP_CTRL_ICCN         12
#define L2TP_CTRL_RESERVED3    13
#define L2TP_CTRL_CDN          14
#define L2TP_CTRL_WEN          15
#define L2TP_CTRL_SLI          16


/*
 * L2tp control message type map
 */

strmap_t l2tp_ctrl_map[] =
{
  { L2TP_CTRL_RESERVED1,    "Reserved" },
  { L2TP_CTRL_SCCRQ,        "Start-Control-Connection-Request" },
  { L2TP_CTRL_SCCRP,        "Start-Control-Connection-Reply" },
  { L2TP_CTRL_SCCCN,        "Stop-Control-Connection-Connected" },
  { L2TP_CTRL_STOPCCN,      "Stop-Control-Connection-Notification" },
  { L2TP_CTRL_RESERVED2,    "Reserved" },
  { L2TP_CTRL_HELLO,        "Hello" },
  { L2TP_CTRL_OCRQ,         "Outgoing-Call-Request" },
  { L2TP_CTRL_OCRP,         "Outgoing-Call-Reply" },
  { L2TP_CTRL_OCCN,         "Outgoing-Call-Connected" },
  { L2TP_CTRL_ICRQ,         "Incoming-Call-Request" },
  { L2TP_CTRL_ICRP,         "Incoming-Call-Reply" },
  { L2TP_CTRL_ICCN,         "Incoming-Call-Connected" },
  { L2TP_CTRL_RESERVED3,    "Reserved" },
  { L2TP_CTRL_CDN,          "Call-Disconnect-Notify" },
  { L2TP_CTRL_WEN,          "WAN-Error-Notify" },
  { L2TP_CTRL_SLI,          "Set-Link-Info" },
  { 0, "" }
};

/*
 * L2TP AVPs
 */

#define L2TP_AVP_MESSAGETYPE        0
#define L2TP_AVP_RESULTCODE         1
#define L2TP_AVP_PROTOCOLVERSION    2
#define L2TP_AVP_FRAMINGCAP         3
#define L2TP_AVP_BEARERCAP          4
#define L2TP_AVP_TIEBREAKER         5
#define L2TP_AVP_FIRMWAREREV        6
#define L2TP_AVP_HOSTNAME           7
#define L2TP_AVP_VENDORNAME         8
#define L2TP_AVP_ASSIGNEDTUNNELID   9
#define L2TP_AVP_RECEIVEWINDOWSIZE  10
#define L2TP_AVP_CHALLENGE          11
#define L2TP_AVP_Q931CAUSECODE      12
#define L2TP_AVP_RESPONSE           13
#define L2TP_AVP_ASSIGNEDSESSIONID  14
#define L2TP_AVP_CALLSERIALNUMBER   15
#define L2TP_AVP_MINIMUMBPS         16
#define L2TP_AVP_MAXIMUMBPS         17
#define L2TP_AVP_BEARERTYPE         18
#define L2TP_AVP_FRAMINGTYPE        19
#define L2TP_AVP_RESERVED1          20
#define L2TP_AVP_CALLEDNUMBER       21
#define L2TP_AVP_CALLINGNUMBER      22
#define L2TP_AVP_SUBADDRESS         23
#define L2TP_AVP_TXCONNECTSPEED     24
#define L2TP_AVP_PHYSICALCHANNELID  25
#define L2TP_AVP_INITIALRECVLCP     26
#define L2TP_AVP_LASTSENTLCP        27
#define L2TP_AVP_LASTRECVLCP        28
#define L2TP_AVP_PROXYAUTHTYPE      29
#define L2TP_AVP_PROXYAUTHNAME      30
#define L2TP_AVP_PROXYAUTHCHALLENGE 31
#define L2TP_AVP_PROXYAUTHID        32
#define L2TP_AVP_PROXYAUTHRESPONSE  33
#define L2TP_AVP_CALLERRORS         34
#define L2TP_AVP_ACCM               35
#define L2TP_AVP_RANDOMVECTOR       36
#define L2TP_AVP_PRIVATEGROUP       37
#define L2TP_AVP_RXCONNECTSPEED     38
#define L2TP_AVP_SEQUENCINGREQUIRED 39

/*
 * L2TP AVP map
 */

strmap_t l2tp_avp_map[] =
{
  { L2TP_AVP_MESSAGETYPE,       "message type" },
  { L2TP_AVP_RESULTCODE,        "result code" },
  { L2TP_AVP_PROTOCOLVERSION,   "protocol version" },
  { L2TP_AVP_FRAMINGCAP,        "framing capabilities" },
  { L2TP_AVP_BEARERCAP,         "bearer capabilities" },
  { L2TP_AVP_TIEBREAKER,        "tie breaker" },
  { L2TP_AVP_FIRMWAREREV,       "firmware revision" },
  { L2TP_AVP_HOSTNAME,          "host name" },
  { L2TP_AVP_VENDORNAME,        "vendor name" },
  { L2TP_AVP_ASSIGNEDTUNNELID,  "assigned tunnel ID" },
  { L2TP_AVP_RECEIVEWINDOWSIZE, "receive window size" },
  { L2TP_AVP_CHALLENGE,         "challenge" },
  { L2TP_AVP_Q931CAUSECODE,     "Q.931 cause code" },
  { L2TP_AVP_RESPONSE,          "response" },
  { L2TP_AVP_ASSIGNEDSESSIONID, "assigned session ID" },
  { L2TP_AVP_CALLSERIALNUMBER,  "call serial number" },
  { L2TP_AVP_MINIMUMBPS,        "minimum BPS" },
  { L2TP_AVP_MAXIMUMBPS,        "maximum BPS" },
  { L2TP_AVP_BEARERTYPE,        "bearer type" },
  { L2TP_AVP_FRAMINGTYPE,       "framing type" },
  { L2TP_AVP_RESERVED1,         "reserved" },
  { L2TP_AVP_CALLEDNUMBER,      "called number" },
  { L2TP_AVP_CALLINGNUMBER,     "calling number" },
  { L2TP_AVP_SUBADDRESS,        "subaddress" },
  { L2TP_AVP_TXCONNECTSPEED,    "transmit connect speed" },
  { L2TP_AVP_PHYSICALCHANNELID, "physical channel ID" },
  { L2TP_AVP_INITIALRECVLCP,    "initial receive LCP" },
  { L2TP_AVP_LASTSENTLCP,       "last sent LCP" },
  { L2TP_AVP_LASTRECVLCP,       "last receive LCP" },
  { L2TP_AVP_PROXYAUTHTYPE,     "proxy authentication type" },
  { L2TP_AVP_PROXYAUTHNAME,     "proxy authentication name" },
  { L2TP_AVP_PROXYAUTHCHALLENGE,"proxy authentication challenge" },
  { L2TP_AVP_PROXYAUTHID,       "proxy authentication ID" },
  { L2TP_AVP_PROXYAUTHRESPONSE, "proxy authentication response" },
  { L2TP_AVP_CALLERRORS,        "call errors" },
  { L2TP_AVP_ACCM,              "ACCM" },
  { L2TP_AVP_RANDOMVECTOR,      "random vector" },
  { L2TP_AVP_PRIVATEGROUP,      "private group" },
  { L2TP_AVP_RXCONNECTSPEED,    "receive connect speed" },
  { L2TP_AVP_SEQUENCINGREQUIRED,"sequencing required" },
  { 0, "" }
};

/*
 * Message type AVP (generic part)
 *
 *  0                   1                   2                   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |M|H| rsvd  |      Length       |           Vendor ID           |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |         Attribute Type        |        Attribute Value...
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *                     [until Length is reached]...                |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 */

typedef struct l2tpavp
{
#if defined(WORDS_BIGENDIAN)
  u_int8_t  m:1,
            h:1,
            zeros:4,
            length_high:2;
#else
  u_int16_t length_high:2,
            zeros:4,
            h:1,
            m:1;
#endif
  u_int8_t  length_low;
  u_int16_t vendor_id;
  u_int16_t attribute_type;
} l2tpavp_t;


/*
 * First two bytes of l2tp header
 *  0                   1                   2                   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |T|L|x|x|S|x|O|P|x|x|x|x|  Ver  |          Length (opt)         |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |           Tunnel ID           |           Session ID          |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |             Ns (opt)          |             Nr (opt)          |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |      Offset Size (opt)        |    Offset pad... (opt)
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 */

typedef struct l2tphdr
{
#if defined(WORDS_BIGENDIAN)
  u_int8_t t_bit:1,
           l_bit:1,
           reserved1:2,
           s_bit:1,
           reserved2:1,
           o_bit:1,
           p_bit:1;
#else
  u_int8_t p_bit:1,
           o_bit:1,
           reserved2:1,
           s_bit:1,
           reserved1:2,
           l_bit:1,
           t_bit:1;
#endif
#if defined(WORD_BIGENDIAN)
  u_int8_t reserved3:4,
           version:4;
#else
  u_int8_t version:4,
           reserved3:4;
#endif
} l2tphdr_t;

extern struct arg_t * my_args;

/*----------------------------------------------------------------------------
**
** dump_l2tp_avp()
**
** Parse L2TP AVP subheader
**
** Returns a -1 on error so that we can fall out of the calling loop.
**
**----------------------------------------------------------------------------
*/

int dump_l2tp_avp(packet_t * pkt, u_int16_t *n)
{
  l2tpavp_t avp;
  int8_t m, h, zeros;
  int16_t length;

  /*
   * Get the avp
   */
  
  if (get_packet_bytes((u_int8_t *) &avp, pkt, L2TP_AVP_SIZE) == 0)
    return -1;
  *n = *n + L2TP_AVP_SIZE;

  /*
   * conversions 
   */

  m = avp.m;
  h = avp.h;
  zeros = avp.zeros;
  length = avp.length_high * 256 + avp.length_low;
  avp.vendor_id = ntohs(avp.vendor_id);
  avp.attribute_type = ntohs(avp.attribute_type);

  /*
   * display
   */

  if (my_args->m)
    {

    }
  else
    {
      display_strmap("AVP type", avp.attribute_type, l2tp_avp_map);
      display("  M (mandatory)", (u_int8_t *) &m, 1, DISP_DEC);
      display("  H (hidden)", (u_int8_t *) &h, 1, DISP_DEC);
      display("  Reserved", (u_int8_t *) &zeros, 1, DISP_DEC);
      display("  Length", (u_int8_t *) &length, 2, DISP_DEC);
      display("  Vendor ID", (u_int8_t *) &avp.vendor_id, 2, DISP_DEC);
    }

  /*
   * Now we can deal with the attribute, the length of which is the 
   * given length - 6
   */

  if (length > 6)
    {
      u_int8_t * attribute;
      int real_length = length - 6;

      attribute = (u_int8_t *) my_malloc(real_length);
      if (get_packet_bytes((u_int8_t *) attribute, pkt, real_length) == 0)
	{
	  my_free(attribute);
	  return -1;
	}
      *n = *n + real_length;

      if (my_args->m)
	{
	  switch(avp.attribute_type)
	    {
	    case L2TP_AVP_MESSAGETYPE:
	      {
		u_int16_t attr;

		memcpy((void *) &attr, (void *) attribute, 2);
		attr = ntohs(attr);
		display_minimal_string(map2str(l2tp_ctrl_map, attr));
		display_minimal_string(" ");
	      }
	      break;

	    default:
	      break;
	    }
	}
      else
	{
	  switch(avp.attribute_type)
	    {
	    case L2TP_AVP_MESSAGETYPE:
	      {
		u_int16_t attr;

		memcpy((void *) &attr, (void *) attribute, 2);
		attr = ntohs(attr);
		display_strmap("  Message type", attr, l2tp_ctrl_map);
	      }
	      break;

	    case L2TP_AVP_HOSTNAME:
	    case L2TP_AVP_VENDORNAME:
	      /* all of these attributes are text strings */
	      attribute[real_length] = '\0';
	      display_string("  Name", attribute);
	      break;

	    case L2TP_AVP_ASSIGNEDTUNNELID:
	    case L2TP_AVP_ASSIGNEDSESSIONID:
	    case L2TP_AVP_RECEIVEWINDOWSIZE:
	    case L2TP_AVP_FIRMWAREREV:
	    case L2TP_AVP_PROXYAUTHTYPE:
	      {
		/* all of these attributes are 2 bytes */
		u_int16_t value;

		memcpy((void *) &value, (void *) attribute, 2);
		value = ntohs(value);
		display("  Value", (u_int8_t *) &value, 2, DISP_DEC);
	      }
	      break;

	    case L2TP_AVP_CALLSERIALNUMBER:
	    case L2TP_AVP_MINIMUMBPS:
	    case L2TP_AVP_MAXIMUMBPS:
	    case L2TP_AVP_TXCONNECTSPEED:
	    case L2TP_AVP_PHYSICALCHANNELID:
	      {
		/* all of these attributes are 4 bytes */
		u_int32_t value;
		
		memcpy((void *) &value, (void *) attribute, 4);
		value = ntohl(value);
		display("  Value", (u_int8_t *) &value, 4, DISP_DEC);
		
	      }
	      break;

	    case L2TP_AVP_PROTOCOLVERSION:
	      {
		u_int8_t ver;
		u_int8_t rev;

		ver = attribute[0];
		rev = attribute[1];
		display("  Version", (u_int8_t *) &ver, 1, DISP_DEC);
		display("  Revision", (u_int8_t *) &rev, 1, DISP_DEC);
	      }
	      break;

	    default:
	      /* display as hex for now */
	      display("  Attribute", (u_int8_t *) attribute, real_length, 
		      DISP_HEX_MULTILINE);
	      break;
	    }
	}

      /* free memory */
      my_free(attribute);
    }

  return 0;
}


/*----------------------------------------------------------------------------
**
** dump_l2tp()
**
** Parse L2TP packet and dump fields
**
**----------------------------------------------------------------------------
*/

void dump_l2tp(packet_t *pkt)
{

  l2tphdr_t hdr;
  u_int8_t  t, l, r1, s, r2, o, p, r3, v;
  u_int16_t tunnel_id;
  u_int16_t session_id;
  u_int16_t bytes_read = 0;
  u_int16_t length;

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

  /*
   * Read the first byte, determine what to do next
   */

  if (get_packet_bytes((u_int8_t *) &hdr, pkt, 2) == 0)
    return;
  bytes_read += 2;

  /*
   * conversions
   */

  t =  hdr.t_bit;
  l =  hdr.l_bit;
  r1 = hdr.reserved1;
  s =  hdr.s_bit;
  r2 = hdr.reserved2;
  o =  hdr.o_bit;
  p =  hdr.p_bit;
  r3 = hdr.reserved3;
  v =  hdr.version;

  /*
   * Display the first byte
   */

  if (my_args->m)
    {
      if (t)
	display_minimal_string("| L2TP control ");
      else
	display_minimal_string("| L2TP data ");
    }
  else
    {
      /* announcement */
      if (t)
	display_header_banner("L2TP control");
      else
	display_header_banner("L2TP data");
      display("T (type)", (u_int8_t *) &t, 1, DISP_DEC);
      display("L (length present)", (u_int8_t *) &l, 1, DISP_DEC);
      display("Reserved", (u_int8_t *) &r1, 1, DISP_DEC);
      display("S (sequence present)", (u_int8_t *) &s, 1, DISP_DEC);
      display("Reserved", (u_int8_t *) &r2, 1, DISP_DEC);
      display("O (offset present)", (u_int8_t *) &o, 1, DISP_DEC);
      display("P (priority)", (u_int8_t *) &p, 1, DISP_DEC);
      display("Reserved", (u_int8_t *) &r3, 1, DISP_DEC);
      display("Version", (u_int8_t *) &v, 1, DISP_DEC);
    }

  /*
   * If the length bit is set, get the length field and display it
   */

  if (l)
    {
      /* get it */
      if (get_packet_bytes((u_int8_t *) &length, pkt, 2) == 0)
	return;
      bytes_read += 2;

      /* convert */
      length = ntohs(length);

      if (!my_args->m)
	display("Length", (u_int8_t *) &length, 2, DISP_DEC);
    }

  /*
   * Get the tunnel ID and session ID fields
   */
  
  if (get_packet_bytes((u_int8_t *) &tunnel_id, pkt, 2) == 0)
    return;
  if (get_packet_bytes((u_int8_t *) &session_id, pkt, 2) == 0)
    return;
  bytes_read += 4;

  
  /* convert */
  tunnel_id = ntohs(tunnel_id);
  session_id = ntohs(session_id);

  /* display */
  if (my_args->m)
    {
      display_minimal_string("tunnel ");
      display_minimal((u_int8_t *) &tunnel_id, 2, DISP_DEC);
      display_minimal_string(" ");
      display_minimal_string("session ");
      display_minimal((u_int8_t *) &session_id, 2, DISP_DEC);
      display_minimal_string(" ");
    }
  else
    {
      display("Tunnel ID", (u_int8_t *) &tunnel_id, 2, DISP_DEC);
      display("Session ID", (u_int8_t *) &session_id, 2, DISP_DEC);
    }
  
  /*
   * If the sequence bit is set, get the seqno fields and display them
   */

  if (s)
    {
      u_int16_t ns;
      u_int16_t nr;

      /* get them */
      if (get_packet_bytes((u_int8_t *) &ns, pkt, 2) == 0)
	return;
      if (get_packet_bytes((u_int8_t *) &nr, pkt, 2) == 0)
	return;
      bytes_read += 4;

      /* convert */
      ns = ntohs(ns);
      nr = ntohs(nr);

      /* display */
      if (!my_args->m)
	{
	  display("Seqno (Ns)", (u_int8_t *) &ns, 2, DISP_DEC);
	  display("Received seqno (Nr)", (u_int8_t *) &nr, 2, DISP_DEC);
	}
    }

  /*
   * If the offset bit is set, get the offset and pad
   */

  if (o)
    {
      u_int16_t  offset;
      u_int8_t * pad;

      /* get the offset */
      if (get_packet_bytes((u_int8_t *) &offset, pkt, 2) == 0)
	return;
      bytes_read += 2;

      /* convert */
      offset = ntohs(offset);

      /* display */
      if (!my_args->m)
	display("Offset", (u_int8_t *) &offset, 2, DISP_DEC);

      /* allocate memory for pad */
      pad = (u_int8_t *) my_malloc(offset+1);
      
      /* get the pad */
      if (get_packet_bytes((u_int8_t *) &pad, pkt, offset) == 0)
	{ 
	  my_free(pad);
	  return;
	}
      bytes_read += offset;
	 
      /* display the offset in hex */
      if (!my_args->m)
	display("Offset pad", pad, offset, DISP_HEX_MULTILINE);

      /* Free memory for the offset */
      my_free(pad);
    }

  /* 
   * For control messages, get the avp's.  For data messages, parse the 
   * PPP header that should be on top of the L2TP header...
   */

  if (t)
    {
      /* grab all of the avp's, one by one */
      while(bytes_read < length)
	if (dump_l2tp_avp(pkt, &bytes_read) == -1)
	  break;
      
      /* dump the hex buffer */
      hexbuffer_flush();
    }
  else
    {
      dump_ppp(pkt);
      /* dump the hex buffer */
      hexbuffer_flush();
    }

  
  
}

