/* OSPFv3 SNMP support
 * Copyright (C) 2004 Yasuhiro Ohara
 *
 * This file is part of GNU Zebra.
 *
 * GNU Zebra 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, or (at your option) any
 * later version.
 *
 * GNU Zebra 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU Zebra; see the file COPYING.  If not, write to the 
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 
 * Boston, MA 02111-1307, USA.  
 */

#include <zebra.h>

#ifdef HAVE_SNMP

#ifdef HAVE_NETSNMP
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#else
#include <asn1.h>
#include <snmp.h>
#include <snmp_impl.h>
#endif

#include "log.h"
#include "vty.h"
#include "linklist.h"
#include "smux.h"

#include "ospf6_proto.h"
#include "ospf6_lsa.h"
#include "ospf6_lsdb.h"
#include "ospf6_route.h"
#include "ospf6_top.h"
#include "ospf6_area.h"
#include "ospf6_interface.h"
#include "ospf6_message.h"
#include "ospf6_neighbor.h"
#include "ospf6d.h"
#include "ospf6_snmp.h"

/* OSPFv3-MIB */
#define OSPFv3MIB 1,3,6,1,3,102

/* OSPFv3 MIB General Group values. */
#define OSPFv3ROUTERID                   1
#define OSPFv3ADMINSTAT                  2
#define OSPFv3VERSIONNUMBER              3
#define OSPFv3AREABDRRTRSTATUS           4
#define OSPFv3ASBDRRTRSTATUS             5
#define OSPFv3ASSCOPELSACOUNT            6
#define OSPFv3ASSCOPELSACHECKSUMSUM      7
#define OSPFv3ORIGINATENEWLSAS           8
#define OSPFv3RXNEWLSAS                  9
#define OSPFv3EXTLSACOUNT               10
#define OSPFv3EXTAREALSDBLIMIT          11
#define OSPFv3MULTICASTEXTENSIONS       12
#define OSPFv3EXITOVERFLOWINTERVAL      13
#define OSPFv3DEMANDEXTENSIONS          14
#define OSPFv3TRAFFICENGINEERINGSUPPORT 15
#define OSPFv3REFERENCEBANDWIDTH        16
#define OSPFv3RESTARTSUPPORT            17
#define OSPFv3RESTARTINTERVAL           18
#define OSPFv3RESTARTSTATUS             19
#define OSPFv3RESTARTAGE                20
#define OSPFv3RESTARTEXITREASON         21

/* OSPFv3 MIB Area Table values. */
#define OSPFv3AREAID                     1
#define OSPFv3IMPORTASEXTERN             2
#define OSPFv3AREASPFRUNS                3
#define OSPFv3AREABDRRTRCOUNT            4
#define OSPFv3AREAASBDRRTRCOUNT          5
#define OSPFv3AREASCOPELSACOUNT          6
#define OSPFv3AREASCOPELSACKSUMSUM       7
#define OSPFv3AREASUMMARY                8
#define OSPFv3AREASTATUS                 9
#define OSPFv3STUBMETRIC                10
#define OSPFv3AREANSSATRANSLATORROLE    11
#define OSPFv3AREANSSATRANSLATORSTATE   12
#define OSPFv3AREANSSATRANSLATORSTABILITYINTERVAL    13
#define OSPFv3AREANSSATRANSLATOREVENTS  14
#define OSPFv3AREASTUBMETRICTYPE        15

/* OSPFv3 MIB Area Lsdb Table values. */
#define OSPFv3AREALSDBAREAID             1
#define OSPFv3AREALSDBTYPE               2
#define OSPFv3AREALSDBROUTERID           3
#define OSPFv3AREALSDBLSID               4
#define OSPFv3AREALSDBSEQUENCE           5
#define OSPFv3AREALSDBAGE                6
#define OSPFv3AREALSDBCHECKSUM           7
#define OSPFv3AREALSDBADVERTISEMENT      8
#define OSPFv3AREALSDBTYPEKNOWN          9

/* SYNTAX Status from OSPF-MIB. */
#define OSPF_STATUS_ENABLED  1
#define OSPF_STATUS_DISABLED 2

/* SNMP value hack. */
#define COUNTER     ASN_COUNTER
#define INTEGER     ASN_INTEGER
#define GAUGE       ASN_GAUGE
#define TIMETICKS   ASN_TIMETICKS
#define IPADDRESS   ASN_IPADDRESS
#define STRING      ASN_OCTET_STR

/* For return values e.g. SNMP_INTEGER macro */
SNMP_LOCAL_VARIABLES

static struct in_addr tmp;
#define INT32_INADDR(x) \
  (tmp.s_addr = (x), tmp)

/* OSPFv3-MIB instances. */
oid ospfv3_oid [] = { OSPFv3MIB };

/* empty ID 0.0.0.0 e.g. empty router-id */
static struct in_addr ospf6_empty_id = {0};

/* Hook functions. */
static u_char *ospfv3GeneralGroup (struct variable *, oid *, size_t *,
				   int, size_t *, WriteMethod **);
static u_char *ospfv3AreaEntry (struct variable *, oid *, size_t *,
				int, size_t *, WriteMethod **);
static u_char *ospfv3AreaLsdbEntry (struct variable *, oid *, size_t *,
				    int, size_t *, WriteMethod **);

struct variable ospfv3_variables[] =
{
  /* OSPF general variables */
  {OSPFv3ROUTERID,              IPADDRESS, RWRITE, ospfv3GeneralGroup,
   3, {1, 1, 1}},
  {OSPFv3ADMINSTAT,             INTEGER,   RWRITE, ospfv3GeneralGroup,
   3, {1, 1, 2}},
  {OSPFv3VERSIONNUMBER,         INTEGER,   RONLY,  ospfv3GeneralGroup,
   3, {1, 1, 3}},
  {OSPFv3AREABDRRTRSTATUS,      INTEGER,   RONLY,  ospfv3GeneralGroup,
   3, {1, 1, 4}},
  {OSPFv3ASBDRRTRSTATUS,        INTEGER,   RWRITE, ospfv3GeneralGroup,
   3, {1, 1, 5}},
  {OSPFv3ASSCOPELSACOUNT,       GAUGE,     RONLY,  ospfv3GeneralGroup,
   3, {1, 1, 6}},
  {OSPFv3ASSCOPELSACHECKSUMSUM, INTEGER,   RONLY,  ospfv3GeneralGroup,
   3, {1, 1, 7}},
  {OSPFv3ORIGINATENEWLSAS,      COUNTER,   RONLY,  ospfv3GeneralGroup,
   3, {1, 1, 8}},
  {OSPFv3RXNEWLSAS,             COUNTER,   RONLY,  ospfv3GeneralGroup,
   3, {1, 1, 9}},
  {OSPFv3EXTLSACOUNT,           GAUGE,     RONLY,  ospfv3GeneralGroup,
   3, {1, 1, 10}},
  {OSPFv3EXTAREALSDBLIMIT,      INTEGER,   RWRITE, ospfv3GeneralGroup,
   3, {1, 1, 11}},
  {OSPFv3MULTICASTEXTENSIONS,   INTEGER,   RWRITE, ospfv3GeneralGroup,
   3, {1, 1, 12}},
  {OSPFv3EXITOVERFLOWINTERVAL,  INTEGER,   RWRITE, ospfv3GeneralGroup,
   3, {1, 1, 13}},
  {OSPFv3DEMANDEXTENSIONS,      INTEGER,   RWRITE, ospfv3GeneralGroup,
   3, {1, 1, 14}},
  {OSPFv3TRAFFICENGINEERINGSUPPORT, INTEGER, RWRITE, ospfv3GeneralGroup,
   3, {1, 1, 15}},
  {OSPFv3REFERENCEBANDWIDTH,    INTEGER, RWRITE, ospfv3GeneralGroup,
   3, {1, 1, 16}},
  {OSPFv3RESTARTSUPPORT,        INTEGER, RWRITE, ospfv3GeneralGroup,
   3, {1, 1, 17}},
  {OSPFv3RESTARTINTERVAL,       INTEGER, RWRITE, ospfv3GeneralGroup,
   3, {1, 1, 18}},
  {OSPFv3RESTARTSTATUS,         INTEGER, RONLY,  ospfv3GeneralGroup,
   3, {1, 1, 19}},
  {OSPFv3RESTARTAGE,            INTEGER, RONLY,  ospfv3GeneralGroup,
   3, {1, 1, 20}},
  {OSPFv3RESTARTEXITREASON,     INTEGER, RONLY,  ospfv3GeneralGroup,
   3, {1, 1, 21}},

  /* OSPFv3 Area Data Structure */
  {OSPFv3AREAID,                IPADDRESS, RONLY,  ospfv3AreaEntry,
   4, {1, 2, 1, 1}},
  {OSPFv3IMPORTASEXTERN,        INTEGER,   RWRITE, ospfv3AreaEntry,
   4, {1, 2, 1, 2}},
  {OSPFv3AREASPFRUNS,           COUNTER,   RONLY,  ospfv3AreaEntry,
   4, {1, 2, 1, 3}},
  {OSPFv3AREABDRRTRCOUNT,       GAUGE,     RONLY,  ospfv3AreaEntry,
   4, {1, 2, 1, 4}},
  {OSPFv3AREAASBDRRTRCOUNT,     GAUGE,     RONLY,  ospfv3AreaEntry,
   4, {1, 2, 1, 5}},
  {OSPFv3AREASCOPELSACOUNT,     GAUGE,     RONLY,  ospfv3AreaEntry,
   4, {1, 2, 1, 6}},
  {OSPFv3AREASCOPELSACKSUMSUM,  INTEGER,   RONLY,  ospfv3AreaEntry,
   4, {1, 2, 1, 7}},
  {OSPFv3AREASUMMARY,           INTEGER,   RWRITE, ospfv3AreaEntry,
   4, {1, 2, 1, 8}},
  {OSPFv3AREASTATUS,            INTEGER,   RWRITE, ospfv3AreaEntry,
   4, {1, 2, 1, 9}},
  {OSPFv3STUBMETRIC,            INTEGER,   RWRITE, ospfv3AreaEntry,
   4, {1, 2, 1, 10}},
  {OSPFv3AREANSSATRANSLATORROLE, INTEGER,  RWRITE, ospfv3AreaEntry,
   4, {1, 2, 1, 11}},
  {OSPFv3AREANSSATRANSLATORSTATE, INTEGER, RONLY,  ospfv3AreaEntry,
   4, {1, 2, 1, 12}},
  {OSPFv3AREANSSATRANSLATORSTABILITYINTERVAL, INTEGER, RWRITE, ospfv3AreaEntry,
   4, {1, 2, 1, 13}},
  {OSPFv3AREANSSATRANSLATOREVENTS, COUNTER, RONLY, ospfv3AreaEntry,
   4, {1, 2, 1, 14}},
  {OSPFv3AREASTUBMETRICTYPE,    INTEGER, RWRITE, ospfv3AreaEntry,
   4, {1, 2, 1, 15}},

  {OSPFv3AREALSDBAREAID,        IPADDRESS, RONLY,  ospfv3AreaLsdbEntry,
   4, {1, 4, 1, 1}},
  {OSPFv3AREALSDBTYPE,          GAUGE,     RONLY,  ospfv3AreaLsdbEntry,
   4, {1, 4, 1, 2}},
  {OSPFv3AREALSDBROUTERID,      IPADDRESS, RONLY,  ospfv3AreaLsdbEntry,
   4, {1, 4, 1, 3}},
  {OSPFv3AREALSDBLSID,          IPADDRESS, RONLY,  ospfv3AreaLsdbEntry,
   4, {1, 4, 1, 4}},
  {OSPFv3AREALSDBSEQUENCE,      INTEGER,   RONLY,  ospfv3AreaLsdbEntry,
   4, {1, 4, 1, 5}},
  {OSPFv3AREALSDBAGE,           INTEGER,   RONLY,  ospfv3AreaLsdbEntry,
   4, {1, 4, 1, 6}},
  {OSPFv3AREALSDBCHECKSUM,      INTEGER,   RONLY,  ospfv3AreaLsdbEntry,
   4, {1, 4, 1, 7}},
  {OSPFv3AREALSDBADVERTISEMENT, STRING,    RONLY,  ospfv3AreaLsdbEntry,
   4, {1, 4, 1, 8}},
  {OSPFv3AREALSDBTYPEKNOWN,     INTEGER,   RONLY,  ospfv3AreaLsdbEntry,
   4, {1, 4, 1, 9}},

};

static u_char *
ospfv3GeneralGroup (struct variable *v, oid *name, size_t *length,
                    int exact, size_t *var_len, WriteMethod **write_method)
{
  /* Check whether the instance identifier is valid */
  if (smux_header_generic (v, name, length, exact, var_len, write_method)
      == MATCH_FAILED)
    return NULL;

  /* Return the current value of the variable */
  switch (v->magic)
    {
    case OSPFv3ROUTERID:                  /* 1*/
      /* Router-ID of this OSPF instance. */
      if (ospf6)
	return SNMP_IPADDRESS (INT32_INADDR (ospf6->router_id));
      else
	return SNMP_IPADDRESS (ospf6_empty_id);
      break;
    case OSPFv3ADMINSTAT:                 /* 2*/
      break;
    case OSPFv3VERSIONNUMBER:             /* 3*/
      break;
    case OSPFv3AREABDRRTRSTATUS:          /* 4*/
      break;
    case OSPFv3ASBDRRTRSTATUS:            /* 5*/
      break;
    case OSPFv3ASSCOPELSACOUNT:           /* 6*/
      break;
    case OSPFv3ASSCOPELSACHECKSUMSUM:     /* 7*/
      break;
    case OSPFv3ORIGINATENEWLSAS:          /* 8*/
      break;
    case OSPFv3RXNEWLSAS:                 /* 9*/
      break;
    case OSPFv3EXTLSACOUNT:               /*10*/
      break;
    case OSPFv3EXTAREALSDBLIMIT:          /*11*/
      break;
    case OSPFv3MULTICASTEXTENSIONS:       /*12*/
      break;
    case OSPFv3EXITOVERFLOWINTERVAL:      /*13*/
      break;
    case OSPFv3DEMANDEXTENSIONS:          /*14*/
      break;
    case OSPFv3TRAFFICENGINEERINGSUPPORT: /*15*/
      break;
    case OSPFv3REFERENCEBANDWIDTH:        /*16*/
      break;
    case OSPFv3RESTARTSUPPORT:            /*17*/
      break;
    case OSPFv3RESTARTINTERVAL:           /*18*/
      break;
    case OSPFv3RESTARTSTATUS:             /*19*/
      break;
    case OSPFv3RESTARTAGE:                /*20*/
      break;
    case OSPFv3RESTARTEXITREASON:         /*21*/
      break;
    default:
      return NULL;
    }
  return NULL;
}

static u_char *
ospfv3AreaEntry (struct variable *v, oid *name, size_t *length,
                 int exact, size_t *var_len, WriteMethod **write_method)
{
  struct ospf6_area *oa, *area = NULL;
  u_int32_t area_id = 0;
  struct listnode *node;
  unsigned int len;

  if (ospf6 == NULL)
    return NULL;

  len = *length - v->namelen;
  len = (len >= sizeof (u_int32_t) ? sizeof (u_int32_t) : 0);
  if (exact && len != sizeof (u_int32_t))
    return NULL;
  if (len)
    oid2in_addr (name + v->namelen, len, (struct in_addr *) &area_id);

  zlog_debug ("SNMP access by area: %s, exact=%d len=%d length=%lu",
	      inet_ntoa (* (struct in_addr *) &area_id),
	      exact, len, (u_long)*length);

  for (ALL_LIST_ELEMENTS_RO (ospf6->area_list, node, oa))
    {
      if (area == NULL)
        {
          if (len == 0) /* return first area entry */
            area = oa;
          else if (exact && ntohl (oa->area_id) == ntohl (area_id))
            area = oa;
          else if (ntohl (oa->area_id) > ntohl (area_id))
            area = oa;
        }
    }

  if (area == NULL)
    return NULL;

  *length = v->namelen + sizeof (u_int32_t);
  oid_copy_addr (name + v->namelen, (struct in_addr *) &area->area_id,
                 sizeof (u_int32_t));

  zlog_debug ("SNMP found area: %s, exact=%d len=%d length=%lu",
	      inet_ntoa (* (struct in_addr *) &area->area_id),
	      exact, len, (u_long)*length);

  switch (v->magic)
    {
    case OSPFv3AREAID:                   /* 1*/
      return SNMP_IPADDRESS (INT32_INADDR (area->area_id));
      break;
    case OSPFv3IMPORTASEXTERN:           /* 2*/
      return SNMP_INTEGER (ospf6->external_table->count);
      break;
    default:
      return NULL;
      break;
    }
  return NULL;
}

static u_char *
ospfv3AreaLsdbEntry (struct variable *v, oid *name, size_t *length,
                     int exact, size_t *var_len, WriteMethod **write_method)
{
  struct ospf6_lsa *lsa = NULL;
  struct in_addr area_id;
  u_int16_t type;
  struct in_addr id;
  struct in_addr adv_router;
  int len;
  oid *offset;
  int offsetlen;
  char a[16], b[16], c[16];
  struct ospf6_area *oa;
  struct listnode *node;

  memset (&area_id, 0, sizeof (struct in_addr));
  type = 0;
  memset (&id, 0, sizeof (struct in_addr));
  memset (&adv_router, 0, sizeof (struct in_addr));

  /* Check OSPFv3 instance. */
  if (ospf6 == NULL)
    return NULL;

  /* Get variable length. */
  offset = name + v->namelen;
  offsetlen = *length - v->namelen;

#define OSPFV3_AREA_LSDB_ENTRY_EXACT_OFFSET \
  (IN_ADDR_SIZE + 1 + IN_ADDR_SIZE + IN_ADDR_SIZE)

  if (exact && offsetlen != OSPFV3_AREA_LSDB_ENTRY_EXACT_OFFSET)
    return NULL;

  /* Parse area-id */
  len = (offsetlen < IN_ADDR_SIZE ? offsetlen : IN_ADDR_SIZE);
  if (len)
    oid2in_addr (offset, len, &area_id);
  offset += len;
  offsetlen -= len;

  /* Parse type */
  len = (offsetlen < 1 ? offsetlen : 1);
  if (len)
    type = htons (*offset);
  offset += len;
  offsetlen -= len;

  /* Parse Router-ID */
  len = (offsetlen < IN_ADDR_SIZE ? offsetlen : IN_ADDR_SIZE);
  if (len)
    oid2in_addr (offset, len, &adv_router);
  offset += len;
  offsetlen -= len;

  /* Parse LS-ID */
  len = (offsetlen < IN_ADDR_SIZE ? offsetlen : IN_ADDR_SIZE);
  if (len)
    oid2in_addr (offset, len, &id);
  offset += len;
  offsetlen -= len;

  inet_ntop (AF_INET, &area_id, a, sizeof (a));
  inet_ntop (AF_INET, &adv_router, b, sizeof (b));
  inet_ntop (AF_INET, &id, c, sizeof (c));
  zlog_debug ("SNMP access by lsdb: area=%s exact=%d length=%lu magic=%d"
	      " type=%#x adv_router=%s id=%s",
	      a, exact, (u_long)*length, v->magic, ntohs (type), b, c);

  if (exact)
    {
      oa = ospf6_area_lookup (area_id.s_addr, ospf6);
      lsa = ospf6_lsdb_lookup (type, id.s_addr, adv_router.s_addr, oa->lsdb);
    }
  else
    {
      for (ALL_LIST_ELEMENTS_RO (ospf6->area_list, node, oa))
        {
          if (lsa)
            continue;
          if (ntohl (oa->area_id) < ntohl (area_id.s_addr))
            continue;

          lsa = ospf6_lsdb_lookup_next (type, id.s_addr, adv_router.s_addr,
                                        oa->lsdb);
          if (! lsa)
            {
              type = 0;
              memset (&id, 0, sizeof (struct in_addr));
              memset (&adv_router, 0, sizeof (struct in_addr));
            }
        }
    }

  if (! lsa)
    {
      zlog_debug ("SNMP respond: No LSA to return");
      return NULL;
    }
  oa = OSPF6_AREA (lsa->lsdb->data);

  zlog_debug ("SNMP respond: area: %s lsa: %s", oa->name, lsa->name);

  /* Add Index (AreaId, Type, RouterId, Lsid) */
  *length = v->namelen + OSPFV3_AREA_LSDB_ENTRY_EXACT_OFFSET;
  offset = name + v->namelen;
  oid_copy_addr (offset, (struct in_addr *) &oa->area_id, IN_ADDR_SIZE);
  offset += IN_ADDR_SIZE;
  *offset = ntohs (lsa->header->type);
  offset++;
  oid_copy_addr (offset, (struct in_addr *) &lsa->header->adv_router,
                 IN_ADDR_SIZE);
  offset += IN_ADDR_SIZE;
  oid_copy_addr (offset, (struct in_addr *) &lsa->header->id, IN_ADDR_SIZE);
  offset += IN_ADDR_SIZE;

  /* Return the current value of the variable */
  switch (v->magic)
    {
    case OSPFv3AREALSDBAREAID:        /* 1 */
      area_id.s_addr = OSPF6_AREA (lsa->lsdb->data)->area_id;
      return SNMP_IPADDRESS (area_id);
      break;
    case OSPFv3AREALSDBTYPE:          /* 2 */
      return SNMP_INTEGER (ntohs (lsa->header->type));
      break;
    case OSPFv3AREALSDBROUTERID:      /* 3 */
      adv_router.s_addr = lsa->header->adv_router;
      return SNMP_IPADDRESS (adv_router);
      break;
    case OSPFv3AREALSDBLSID:          /* 4 */
      id.s_addr = lsa->header->id;
      return SNMP_IPADDRESS (id);
      break;
    case OSPFv3AREALSDBSEQUENCE:      /* 5 */
      return SNMP_INTEGER (lsa->header->seqnum);
      break;
    case OSPFv3AREALSDBAGE:           /* 6 */
      ospf6_lsa_age_current (lsa);
      return SNMP_INTEGER (lsa->header->age);
      break;
    case OSPFv3AREALSDBCHECKSUM:      /* 7 */
      return SNMP_INTEGER (lsa->header->checksum);
      break;
    case OSPFv3AREALSDBADVERTISEMENT: /* 8 */
      *var_len = ntohs (lsa->header->length);
      return (u_char *) lsa->header;
      break;
    case OSPFv3AREALSDBTYPEKNOWN:     /* 9 */
      return SNMP_INTEGER (OSPF6_LSA_IS_KNOWN (lsa->header->type) ?
                           SNMP_TRUE : SNMP_FALSE);
      break;
    default:
      return NULL;
      break;
    }
  return NULL;
}


/* Register OSPFv3-MIB. */
void
ospf6_snmp_init (struct thread_master *master)
{
  smux_init (master);
  REGISTER_MIB ("OSPFv3MIB", ospfv3_variables, variable, ospfv3_oid);
}

#endif /* HAVE_SNMP */

