// SPDX-License-Identifier: GPL-2.0-or-later
/* MPLS/BGP L3VPN MIB
 * Copyright (C) 2020 Volta Networks Inc
 */

#include <zebra.h>

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>

#include "if.h"
#include "log.h"
#include "prefix.h"
#include "command.h"
#include "frrevent.h"
#include "smux.h"
#include "filter.h"
#include "hook.h"
#include "libfrr.h"
#include "lib/version.h"

#include "bgpd/bgpd.h"
#include "bgpd/bgp_route.h"
#include "bgpd/bgp_attr.h"
#include "bgpd/bgp_ecommunity.h"
#include "bgpd/bgp_mplsvpn.h"
#include "bgpd/bgp_mplsvpn_snmp.h"

#define BGP_mplsvpn_notif_enable_true 1
#define BGP_mplsvpn_notif_enable_false 2

/* MPLSL3VPN MIB described in RFC4382 */
#define MPLSL3VPNMIB 1, 3, 6, 1, 2, 1, 10, 166, 11

/* MPLSL3VPN Scalars */
#define MPLSL3VPNCONFIGUREDVRFS 1
#define MPLSL3VPNACTIVEVRFS 2
#define MPLSL3VPNCONNECTEDINTERFACES 3
#define MPLSL3VPNNOTIFICATIONENABLE 4
#define MPLSL3VPNCONFMAXPOSSRTS 5
#define MPLSL3VPNVRFCONFRTEMXTHRSHTIME 6
#define MPLSL3VPNILLLBLRCVTHRSH 7

/* MPLSL3VPN IFConf Table */
#define MPLSL3VPNIFVPNCLASSIFICATION 1
#define MPLSL3VPNIFCONFSTORAGETYPE 2
#define MPLSL3VPNIFCONFROWSTATUS 3

/* MPLSL3VPN VRF Table */
#define MPLSL3VPNVRFVPNID 1
#define MPLSL3VPNVRFDESC 2
#define MPLSL3VPNVRFRD 3
#define MPLSL3VPNVRFCREATIONTIME 4
#define MPLSL3VPNVRFOPERSTATUS 5
#define MPLSL3VPNVRFACTIVEINTERFACES 6
#define MPLSL3VPNVRFASSOCIATEDINTERFACES 7
#define MPLSL3VPNVRFCONFMIDRTETHRESH 8
#define MPLSL3VPNVRFCONFHIGHRTETHRSH 9
#define MPLSL3VPNVRFCONFMAXROUTES 10
#define MPLSL3VPNVRFCONFLASTCHANGED 11
#define MPLSL3VPNVRFCONFROWSTATUS 12
#define MPLSL3VPNVRFCONFADMINSTATUS 13
#define MPLSL3VPNVRFCONFSTORAGETYPE 14

/* MPLSL3VPN RT Table */
#define MPLSL3VPNVRFRT 1
#define MPLSL3VPNVRFRTDESCR 2
#define MPLSL3VPNVRFRTROWSTATUS 3
#define MPLSL3VPNVRFRTSTORAGETYPE 4

/* MPLSL3VPN PERF Table */
#define MPLSL3VPNVRFPERFROUTESADDED 1
#define MPLSL3VPNVRFPERFROUTESDELETED 2
#define MPLSL3VPNVRFPERFCURRNUMROUTES 3

/* MPLSL3VPN RTE Table */
#define MPLSL3VPNVRFRTEINETCIDRDESTTYPE 1
#define MPLSL3VPNVRFRTEINETCIDRDEST 2
#define MPLSL3VPNVRFRTEINETCIDRPFXLEN 3
#define MPLSL3VPNVRFRTEINETCIDRPOLICY 4
#define MPLSL3VPNVRFRTEINETCIDRNHOPTYPE 5
#define MPLSL3VPNVRFRTEINETCIDRNEXTHOP 6
#define MPLSL3VPNVRFRTEINETCIDRIFINDEX 7
#define MPLSL3VPNVRFRTEINETCIDRTYPE 8
#define MPLSL3VPNVRFRTEINETCIDRPROTO 9
#define MPLSL3VPNVRFRTEINETCIDRAGE 10
#define MPLSL3VPNVRFRTEINETCIDRNEXTHOPAS 11
#define MPLSL3VPNVRFRTEINETCIDRMETRIC1 12
#define MPLSL3VPNVRFRTEINETCIDRMETRIC2 13
#define MPLSL3VPNVRFRTEINETCIDRMETRIC3 14
#define MPLSL3VPNVRFRTEINETCIDRMETRIC4 15
#define MPLSL3VPNVRFRTEINETCIDRMETRIC5 16
#define MPLSL3VPNVRFRTEINETCIDRXCPOINTER 17
#define MPLSL3VPNVRFRTEINETCIDRSTATUS 18

/* BGP Trap */
#define MPLSL3VPNVRFUP 1
#define MPLSL3VPNDOWN 2

/* SNMP value hack. */
#define INTEGER ASN_INTEGER
#define INTEGER32 ASN_INTEGER
#define COUNTER32 ASN_COUNTER
#define OCTET_STRING ASN_OCTET_STR
#define IPADDRESS ASN_IPADDRESS
#define GAUGE32 ASN_UNSIGNED
#define TIMETICKS ASN_TIMETICKS
#define OID ASN_OBJECT_ID

/* Declare static local variables for convenience. */
SNMP_LOCAL_VARIABLES

#define RT_PREAMBLE_SIZE 20

/* BGP-MPLS-MIB instances */
static oid mpls_l3vpn_oid[] = {MPLSL3VPNMIB};
static oid mpls_l3vpn_trap_oid[] = {MPLSL3VPNMIB, 0};
static char rd_buf[RD_ADDRSTRLEN];
/* Notifications enabled by default */
static uint8_t bgp_mplsvpn_notif_enable = SNMP_TRUE;
static oid mpls_l3vpn_policy_oid[2] = {0, 0};
static const char *empty_nhop = "";
char rt_description[VRF_NAMSIZ + RT_PREAMBLE_SIZE];

static uint8_t *mplsL3vpnConfiguredVrfs(struct variable *, oid[], size_t *, int,
					size_t *, WriteMethod **);

static uint8_t *mplsL3vpnActiveVrfs(struct variable *, oid[], size_t *, int,
				    size_t *, WriteMethod **);

static uint8_t *mplsL3vpnConnectedInterfaces(struct variable *, oid[], size_t *,
					     int, size_t *, WriteMethod **);

static uint8_t *mplsL3vpnNotificationEnable(struct variable *, oid[], size_t *,
					    int, size_t *, WriteMethod **);

static uint8_t *mplsL3vpnVrfConfMaxPossRts(struct variable *, oid[], size_t *,
					   int, size_t *, WriteMethod **);

static uint8_t *mplsL3vpnVrfConfRteMxThrshTime(struct variable *, oid[],
					       size_t *, int, size_t *,
					       WriteMethod **);

static uint8_t *mplsL3vpnIllLblRcvThrsh(struct variable *, oid[], size_t *, int,
					size_t *, WriteMethod **);

static uint8_t *mplsL3vpnVrfTable(struct variable *, oid[], size_t *, int,
				  size_t *, WriteMethod **);

static uint8_t *mplsL3vpnVrfRtTable(struct variable *, oid[], size_t *, int,
				    size_t *, WriteMethod **);

static uint8_t *mplsL3vpnIfConfTable(struct variable *, oid[], size_t *, int,
				     size_t *, WriteMethod **);

static uint8_t *mplsL3vpnPerfTable(struct variable *, oid[], size_t *, int,
				   size_t *, WriteMethod **);

static uint8_t *mplsL3vpnRteTable(struct variable *, oid[], size_t *, int,
				  size_t *, WriteMethod **);


static struct variable mpls_l3vpn_variables[] = {
	/* BGP version. */
	{MPLSL3VPNCONFIGUREDVRFS,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnConfiguredVrfs,
	 3,
	 {1, 1, 1} },
	{MPLSL3VPNACTIVEVRFS,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnActiveVrfs,
	 3,
	 {1, 1, 2} },
	{MPLSL3VPNCONNECTEDINTERFACES,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnConnectedInterfaces,
	 3,
	 {1, 1, 3} },
	{MPLSL3VPNNOTIFICATIONENABLE,
	 INTEGER,
	 RWRITE,
	 mplsL3vpnNotificationEnable,
	 3,
	 {1, 1, 4} },
	{MPLSL3VPNCONFMAXPOSSRTS,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnVrfConfMaxPossRts,
	 3,
	 {1, 1, 5} },
	{MPLSL3VPNVRFCONFRTEMXTHRSHTIME,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnVrfConfRteMxThrshTime,
	 3,
	 {1, 1, 6} },
	{MPLSL3VPNILLLBLRCVTHRSH,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnIllLblRcvThrsh,
	 3,
	 {1, 1, 7} },

	/* Ifconf Table */
	{MPLSL3VPNIFVPNCLASSIFICATION,
	 INTEGER,
	 RONLY,
	 mplsL3vpnIfConfTable,
	 5,
	 {1, 2, 1, 1, 2} },
	{MPLSL3VPNIFCONFSTORAGETYPE,
	 INTEGER,
	 RONLY,
	 mplsL3vpnIfConfTable,
	 5,
	 {1, 2, 1, 1, 4} },
	{MPLSL3VPNIFCONFROWSTATUS,
	 INTEGER,
	 RONLY,
	 mplsL3vpnIfConfTable,
	 5,
	 {1, 2, 1, 1, 5} },

	/* mplsL3VpnVrf Table */
	{MPLSL3VPNVRFVPNID,
	 OCTET_STRING,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 2} },
	{MPLSL3VPNVRFDESC,
	 OCTET_STRING,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 3} },
	{MPLSL3VPNVRFRD,
	 OCTET_STRING,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 4} },
	{MPLSL3VPNVRFCREATIONTIME,
	 TIMETICKS,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 5} },
	{MPLSL3VPNVRFOPERSTATUS,
	 INTEGER,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 6} },
	{MPLSL3VPNVRFACTIVEINTERFACES,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 7} },
	{MPLSL3VPNVRFASSOCIATEDINTERFACES,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 8} },
	{MPLSL3VPNVRFCONFMIDRTETHRESH,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 9} },
	{MPLSL3VPNVRFCONFHIGHRTETHRSH,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 10} },
	{MPLSL3VPNVRFCONFMAXROUTES,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 11} },
	{MPLSL3VPNVRFCONFLASTCHANGED,
	 TIMETICKS,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 12} },
	{MPLSL3VPNVRFCONFROWSTATUS,
	 INTEGER,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 13} },
	{MPLSL3VPNVRFCONFADMINSTATUS,
	 INTEGER,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 14} },
	{MPLSL3VPNVRFCONFSTORAGETYPE,
	 INTEGER,
	 RONLY,
	 mplsL3vpnVrfTable,
	 5,
	 {1, 2, 2, 1, 15} },

	/* mplsL3vpnVrfRt Table */
	{MPLSL3VPNVRFRT,
	 OCTET_STRING,
	 RONLY,
	 mplsL3vpnVrfRtTable,
	 5,
	 {1, 2, 3, 1, 4} },
	{MPLSL3VPNVRFRTDESCR,
	 OCTET_STRING,
	 RONLY,
	 mplsL3vpnVrfRtTable,
	 5,
	 {1, 2, 3, 1, 5} },
	{MPLSL3VPNVRFRTROWSTATUS,
	 INTEGER,
	 RONLY,
	 mplsL3vpnVrfRtTable,
	 5,
	 {1, 2, 3, 1, 6} },
	{MPLSL3VPNVRFRTSTORAGETYPE,
	 INTEGER,
	 RONLY,
	 mplsL3vpnVrfRtTable,
	 5,
	 {1, 2, 3, 1, 7} },

	/* mplsL3VpnPerfTable */
	{MPLSL3VPNVRFPERFROUTESADDED,
	 COUNTER32,
	 RONLY,
	 mplsL3vpnPerfTable,
	 5,
	 {1, 3, 1, 1, 1} },
	{MPLSL3VPNVRFPERFROUTESDELETED,
	 COUNTER32,
	 RONLY,
	 mplsL3vpnPerfTable,
	 5,
	 {1, 3, 1, 1, 2} },
	{MPLSL3VPNVRFPERFCURRNUMROUTES,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnPerfTable,
	 5,
	 {1, 3, 1, 1, 3} },

	/* mplsVpnRteTable */
	{MPLSL3VPNVRFRTEINETCIDRDESTTYPE,
	 INTEGER,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 1} },
	{MPLSL3VPNVRFRTEINETCIDRDEST,
	 OCTET_STRING,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 2} },
	{MPLSL3VPNVRFRTEINETCIDRPFXLEN,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 3} },
	{MPLSL3VPNVRFRTEINETCIDRPOLICY,
	 OID,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 4} },
	{MPLSL3VPNVRFRTEINETCIDRNHOPTYPE,
	 INTEGER,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 5} },
	{MPLSL3VPNVRFRTEINETCIDRNEXTHOP,
	 OCTET_STRING,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 6} },
	{MPLSL3VPNVRFRTEINETCIDRIFINDEX,
	 INTEGER,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 7} },
	{MPLSL3VPNVRFRTEINETCIDRTYPE,
	 INTEGER,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 8} },
	{MPLSL3VPNVRFRTEINETCIDRPROTO,
	 INTEGER,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 9} },
	{MPLSL3VPNVRFRTEINETCIDRAGE,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 10} },
	{MPLSL3VPNVRFRTEINETCIDRNEXTHOPAS,
	 GAUGE32,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 11} },
	{MPLSL3VPNVRFRTEINETCIDRMETRIC1,
	 INTEGER,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 12} },
	{MPLSL3VPNVRFRTEINETCIDRMETRIC2,
	 INTEGER,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 13} },
	{MPLSL3VPNVRFRTEINETCIDRMETRIC3,
	 INTEGER,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 14} },
	{MPLSL3VPNVRFRTEINETCIDRMETRIC4,
	 INTEGER,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 15} },
	{MPLSL3VPNVRFRTEINETCIDRMETRIC5,
	 INTEGER,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 16} },
	{MPLSL3VPNVRFRTEINETCIDRXCPOINTER,
	 OCTET_STRING,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 17} },
	{MPLSL3VPNVRFRTEINETCIDRSTATUS,
	 INTEGER,
	 RONLY,
	 mplsL3vpnRteTable,
	 5,
	 {1, 4, 1, 1, 18} },
};

/* timeticks are in hundredths of a second */
static void bgp_mpls_l3vpn_update_timeticks(time_t *counter)
{
	struct timeval tv;

	monotime(&tv);
	*counter = (tv.tv_sec * 100) + (tv.tv_usec / 10000);
}

static int bgp_mpls_l3vpn_update_last_changed(struct bgp *bgp)
{
	if (bgp->snmp_stats)
		bgp_mpls_l3vpn_update_timeticks(
			&(bgp->snmp_stats->modify_time));
	return 0;
}

static uint32_t bgp_mpls_l3vpn_current_routes(struct bgp *l3vpn_bgp)
{
	uint32_t count = 0;
	struct bgp_table *table;
	struct bgp_dest *dest;
	struct bgp_path_info *pi;

	table = l3vpn_bgp->rib[AFI_IP][SAFI_UNICAST];
	for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) {
		pi = bgp_dest_get_bgp_path_info(dest);
		for (; pi; pi = pi->next)
			count++;
	}
	table = l3vpn_bgp->rib[AFI_IP6][SAFI_UNICAST];
	for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) {
		pi = bgp_dest_get_bgp_path_info(dest);
		for (; pi; pi = pi->next)
			count++;
	}
	return count;
}

static int bgp_init_snmp_stats(struct bgp *bgp)
{
	if (is_bgp_vrf_mplsvpn(bgp)) {
		if (bgp->snmp_stats == NULL) {
			bgp->snmp_stats = XCALLOC(MTYPE_BGP_NAME,
						  sizeof(struct bgp_snmp_stats));
			/* fix up added routes */
			if (bgp->snmp_stats) {
				bgp->snmp_stats->routes_added =
					bgp_mpls_l3vpn_current_routes(bgp);
				bgp_mpls_l3vpn_update_timeticks(
					&(bgp->snmp_stats->creation_time));
			}
		}
	} else {
		if (bgp->snmp_stats) {
			XFREE(MTYPE_BGP_NAME, bgp->snmp_stats);
			bgp->snmp_stats = NULL;
		}
	}
	/* Something changed - update the timestamp */
	bgp_mpls_l3vpn_update_last_changed(bgp);
	return 0;
}

static int bgp_snmp_update_route_stats(struct bgp_dest *dest,
				       struct bgp_path_info *pi, bool added)
{
	struct bgp_table *table;

	if (dest) {
		table = bgp_dest_table(dest);
		/* only update if we have a stats block - MPLSVPN vrfs for now*/
		if (table && table->bgp && table->bgp->snmp_stats) {
			if (added)
				table->bgp->snmp_stats->routes_added++;
			else
				table->bgp->snmp_stats->routes_deleted++;
		}
	}
	return 0;
}

static bool is_bgp_vrf_active(struct bgp *bgp)
{
	struct vrf *vrf;
	struct interface *ifp;

	/* if there is one interface in the vrf which is up then it is deemed
	 *  active
	 */
	vrf = vrf_lookup_by_id(bgp->vrf_id);
	if (vrf == NULL)
		return false;
	RB_FOREACH (ifp, if_name_head, &vrf->ifaces_by_name) {
		/* if we are in a vrf skip the l3mdev */
		if (bgp->name && strncmp(ifp->name, bgp->name, VRF_NAMSIZ) == 0)
			continue;

		if (if_is_up(ifp))
			return true;
	}
	return false;
}

/* BGP Traps. */
static struct trap_object l3vpn_trap_list[] = {{5, {1, 2, 1, 1, 5} },
					       {5, {1, 2, 2, 1, 6} } };

static int bgp_vrf_check_update_active(struct bgp *bgp, struct interface *ifp)
{
	bool new_active = false;
	oid trap;
	struct index_oid trap_index[2];

	if (!is_bgp_vrf_mplsvpn(bgp) || bgp->snmp_stats == NULL
	    || !bgp_mplsvpn_notif_enable)
		return 0;
	new_active = is_bgp_vrf_active(bgp);
	if (bgp->snmp_stats->active != new_active) {
		/* add trap in here */
		bgp->snmp_stats->active = new_active;

		if (!CHECK_FLAG(bm->options, BGP_OPT_TRAPS_RFC4382)) {
			bgp_mpls_l3vpn_update_last_changed(bgp);
			return 0;
		}

		/* send relevent trap */
		if (bgp->snmp_stats->active)
			trap = MPLSL3VPNVRFUP;
		else
			trap = MPLSL3VPNDOWN;

		/*
		 * first index vrf_name + ifindex
		 * second index vrf_name
		 */
		trap_index[1].indexlen = strnlen(bgp->name, VRF_NAMSIZ);
		oid_copy_str(trap_index[0].indexname, bgp->name,
			     trap_index[1].indexlen);
		oid_copy_str(trap_index[1].indexname, bgp->name,
			     trap_index[1].indexlen);
		trap_index[0].indexlen =
			trap_index[1].indexlen + sizeof(ifindex_t);
		oid_copy_int(trap_index[0].indexname + trap_index[1].indexlen,
			     (int *)&(ifp->ifindex));

		smux_trap_multi_index(
			mpls_l3vpn_variables, array_size(mpls_l3vpn_variables),
			mpls_l3vpn_trap_oid, array_size(mpls_l3vpn_trap_oid),
			mpls_l3vpn_oid, sizeof(mpls_l3vpn_oid) / sizeof(oid),
			trap_index, array_size(trap_index), l3vpn_trap_list,
			array_size(l3vpn_trap_list), trap);
	}
	bgp_mpls_l3vpn_update_last_changed(bgp);
	return 0;
}

static uint8_t *mplsL3vpnConfiguredVrfs(struct variable *v, oid name[],
					size_t *length, int exact,
					size_t *var_len,
					WriteMethod **write_method)
{
	struct listnode *node, *nnode;
	struct bgp *bgp;
	uint32_t count = 0;

	if (smux_header_generic(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) {
		if (is_bgp_vrf_mplsvpn(bgp))
			count++;
	}
	return SNMP_INTEGER(count);
}

static uint8_t *mplsL3vpnActiveVrfs(struct variable *v, oid name[],
				    size_t *length, int exact, size_t *var_len,
				    WriteMethod **write_method)
{
	struct listnode *node, *nnode;
	struct bgp *bgp;
	uint32_t count = 0;

	if (smux_header_generic(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) {
		if (is_bgp_vrf_mplsvpn(bgp) && is_bgp_vrf_active(bgp))
			count++;
	}
	return SNMP_INTEGER(count);
}

static uint8_t *mplsL3vpnConnectedInterfaces(struct variable *v, oid name[],
					     size_t *length, int exact,
					     size_t *var_len,
					     WriteMethod **write_method)
{
	struct listnode *node, *nnode;
	struct bgp *bgp;
	uint32_t count = 0;
	struct vrf *vrf;

	if (smux_header_generic(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) {
		if (is_bgp_vrf_mplsvpn(bgp)) {
			vrf = vrf_lookup_by_name(bgp->name);
			if (vrf == NULL)
				continue;

			count += vrf_interface_count(vrf);
		}
	}

	return SNMP_INTEGER(count);
}

static int write_mplsL3vpnNotificationEnable(int action, uint8_t *var_val,
					     uint8_t var_val_type,
					     size_t var_val_len, uint8_t *statP,
					     oid *name, size_t length)
{
	uint32_t intval;

	if (var_val_type != ASN_INTEGER)
		return SNMP_ERR_WRONGTYPE;

	if (var_val_len != sizeof(long))
		return SNMP_ERR_WRONGLENGTH;

	intval = *(long *)var_val;
	bgp_mplsvpn_notif_enable = intval;
	return SNMP_ERR_NOERROR;
}

static uint8_t *mplsL3vpnNotificationEnable(struct variable *v, oid name[],
					    size_t *length, int exact,
					    size_t *var_len,
					    WriteMethod **write_method)
{
	if (smux_header_generic(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	*write_method = write_mplsL3vpnNotificationEnable;
	return SNMP_INTEGER(bgp_mplsvpn_notif_enable);
}

static uint8_t *mplsL3vpnVrfConfMaxPossRts(struct variable *v, oid name[],
					   size_t *length, int exact,
					   size_t *var_len,
					   WriteMethod **write_method)
{
	if (smux_header_generic(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	return SNMP_INTEGER(0);
}

static uint8_t *mplsL3vpnVrfConfRteMxThrshTime(struct variable *v, oid name[],
					       size_t *length, int exact,
					       size_t *var_len,
					       WriteMethod **write_method)
{
	if (smux_header_generic(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	return SNMP_INTEGER(0);
}

static uint8_t *mplsL3vpnIllLblRcvThrsh(struct variable *v, oid name[],
					size_t *length, int exact,
					size_t *var_len,
					WriteMethod **write_method)
{
	if (smux_header_generic(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	return SNMP_INTEGER(0);
}


static struct bgp *bgp_lookup_by_name_next(char *vrf_name)
{
	struct bgp *bgp, *bgp_next = NULL;
	struct listnode *node, *nnode;
	bool first = false;

	/*
	 * the vrfs are not stored alphabetically but since we are using the
	 * vrf name as an index we need the getnext function to return them
	 * in a atrict order. Thus run through and find the best next one.
	 */
	for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) {
		if (!is_bgp_vrf_mplsvpn(bgp))
			continue;
		if (strnlen(vrf_name, VRF_NAMSIZ) == 0 && bgp_next == NULL) {
			first = true;
			bgp_next = bgp;
			continue;
		}
		if (first || strncmp(bgp->name, vrf_name, VRF_NAMSIZ) > 0) {
			if (bgp_next == NULL)
				bgp_next = bgp;
			else if (strncmp(bgp->name, bgp_next->name, VRF_NAMSIZ)
				 < 0)
				bgp_next = bgp;
		}
	}
	return bgp_next;
}

/* 1.3.6.1.2.1.10.166.11.1.2.1.1.x = 14*/
#define IFCONFTAB_NAMELEN 14
static struct bgp *bgpL3vpnIfConf_lookup(struct variable *v, oid name[],
					 size_t *length, char *vrf_name,
					 ifindex_t *ifindex, int exact)
{
	struct bgp *bgp = NULL;
	size_t namelen = v ? v->namelen : IFCONFTAB_NAMELEN;
	struct interface *ifp;
	int vrf_name_len, len;

	/* too long ? */
	if (*length - namelen > (VRF_NAMSIZ + sizeof(uint32_t)))
		return NULL;
	/* do we have index info in the oid ? */
	if (*length - namelen != 0 && *length - namelen >= sizeof(uint32_t)) {
		/* copy the info from the oid */
		vrf_name_len = *length - (namelen + sizeof(ifindex_t));
		oid2string(name + namelen, vrf_name_len, vrf_name);
		oid2int(name + namelen + vrf_name_len, ifindex);
	}

	if (exact) {
		/* Check the length. */
		bgp = bgp_lookup_by_name(vrf_name);
		if (bgp && !is_bgp_vrf_mplsvpn(bgp))
			return NULL;
		if (!bgp)
			return NULL;
		ifp = if_lookup_by_index(*ifindex, bgp->vrf_id);
		if (!ifp)
			return NULL;
	} else {
		if (strnlen(vrf_name, VRF_NAMSIZ) == 0)
			bgp = bgp_lookup_by_name_next(vrf_name);
		else
			bgp = bgp_lookup_by_name(vrf_name);

		while (bgp) {
			ifp = if_vrf_lookup_by_index_next(*ifindex,
							  bgp->vrf_id);
			if (ifp) {
				vrf_name_len = strnlen(bgp->name, VRF_NAMSIZ);
				*ifindex = ifp->ifindex;
				len = vrf_name_len + sizeof(ifindex_t);
				oid_copy_str(name + namelen, bgp->name,
					     vrf_name_len);
				oid_copy_int(name + namelen + vrf_name_len,
					     ifindex);
				*length = len + namelen;

				return bgp;
			}
			*ifindex = 0;
			bgp = bgp_lookup_by_name_next(bgp->name);
		}

		return NULL;
	}
	return bgp;
}

static uint8_t *mplsL3vpnIfConfTable(struct variable *v, oid name[],
				     size_t *length, int exact, size_t *var_len,
				     WriteMethod **write_method)
{
	char vrf_name[VRF_NAMSIZ];
	ifindex_t ifindex = 0;
	struct bgp *l3vpn_bgp;

	if (smux_header_table(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	memset(vrf_name, 0, VRF_NAMSIZ);
	l3vpn_bgp = bgpL3vpnIfConf_lookup(v, name, length, vrf_name, &ifindex,
					  exact);
	if (!l3vpn_bgp)
		return NULL;

	switch (v->magic) {
	case MPLSL3VPNIFVPNCLASSIFICATION:
		return SNMP_INTEGER(2);
	case MPLSL3VPNIFCONFSTORAGETYPE:
		return SNMP_INTEGER(2);
	case MPLSL3VPNIFCONFROWSTATUS:
		return SNMP_INTEGER(1);
	}
	return NULL;
}

/* 1.3.6.1.2.1.10.166.11.1.2.2.1.x = 14*/
#define VRFTAB_NAMELEN 14

static struct bgp *bgpL3vpnVrf_lookup(struct variable *v, oid name[],
				      size_t *length, char *vrf_name, int exact)
{
	struct bgp *bgp = NULL;
	size_t namelen = v ? v->namelen : VRFTAB_NAMELEN;
	int len;

	if (*length - namelen > VRF_NAMSIZ)
		return NULL;
	oid2string(name + namelen, *length - namelen, vrf_name);
	if (exact) {
		/* Check the length. */
		bgp = bgp_lookup_by_name(vrf_name);
		if (bgp && !is_bgp_vrf_mplsvpn(bgp))
			return NULL;
	} else {
		bgp = bgp_lookup_by_name_next(vrf_name);

		if (bgp == NULL)
			return NULL;

		len = strnlen(bgp->name, VRF_NAMSIZ);
		oid_copy_str(name + namelen, bgp->name, len);
		*length = len + namelen;
	}
	return bgp;
}

static uint8_t *mplsL3vpnVrfTable(struct variable *v, oid name[],
				  size_t *length, int exact, size_t *var_len,
				  WriteMethod **write_method)
{
	char vrf_name[VRF_NAMSIZ];
	struct bgp *l3vpn_bgp;

	if (smux_header_table(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	memset(vrf_name, 0, VRF_NAMSIZ);
	l3vpn_bgp = bgpL3vpnVrf_lookup(v, name, length, vrf_name, exact);

	if (!l3vpn_bgp)
		return NULL;

	switch (v->magic) {
	case MPLSL3VPNVRFVPNID:
		*var_len = 0;
		return NULL;
	case MPLSL3VPNVRFDESC:
		*var_len = strnlen(l3vpn_bgp->name, VRF_NAMSIZ);
		return (uint8_t *)l3vpn_bgp->name;
	case MPLSL3VPNVRFRD:
		/*
		 * this is a horror show but the MIB dicates one RD per vrf
		 * and not one RD per AFI as we (FRR) have. So this little gem
		 * returns the V4 one if it's set OR the v6 one if it's set or
		 * zero-length string id neither are set
		 */
		memset(rd_buf, 0, RD_ADDRSTRLEN);
		if (CHECK_FLAG(l3vpn_bgp->vpn_policy[AFI_IP].flags,
			       BGP_VPN_POLICY_TOVPN_RD_SET))
			prefix_rd2str(&l3vpn_bgp->vpn_policy[AFI_IP].tovpn_rd,
				      rd_buf, sizeof(rd_buf),
				      bgp_get_asnotation(l3vpn_bgp));
		else if (CHECK_FLAG(l3vpn_bgp->vpn_policy[AFI_IP6].flags,
				    BGP_VPN_POLICY_TOVPN_RD_SET))
			prefix_rd2str(&l3vpn_bgp->vpn_policy[AFI_IP6].tovpn_rd,
				      rd_buf, sizeof(rd_buf),
				      bgp_get_asnotation(l3vpn_bgp));

		*var_len = strnlen(rd_buf, RD_ADDRSTRLEN);
		return (uint8_t *)rd_buf;
	case MPLSL3VPNVRFCREATIONTIME:
		return SNMP_INTEGER(
			(uint32_t)l3vpn_bgp->snmp_stats->creation_time);
	case MPLSL3VPNVRFOPERSTATUS:
		if (l3vpn_bgp->snmp_stats->active)
			return SNMP_INTEGER(1);
		else
			return SNMP_INTEGER(2);
	case MPLSL3VPNVRFACTIVEINTERFACES:
		return SNMP_INTEGER(bgp_vrf_interfaces(l3vpn_bgp, true));
	case MPLSL3VPNVRFASSOCIATEDINTERFACES:
		return SNMP_INTEGER(bgp_vrf_interfaces(l3vpn_bgp, false));
	case MPLSL3VPNVRFCONFMIDRTETHRESH:
		return SNMP_INTEGER(0);
	case MPLSL3VPNVRFCONFHIGHRTETHRSH:
		return SNMP_INTEGER(0);
	case MPLSL3VPNVRFCONFMAXROUTES:
		return SNMP_INTEGER(0);
	case MPLSL3VPNVRFCONFLASTCHANGED:
		return SNMP_INTEGER(
			(uint32_t)l3vpn_bgp->snmp_stats->modify_time);
	case MPLSL3VPNVRFCONFROWSTATUS:
		return SNMP_INTEGER(1);
	case MPLSL3VPNVRFCONFADMINSTATUS:
		return SNMP_INTEGER(1);
	case MPLSL3VPNVRFCONFSTORAGETYPE:
		return SNMP_INTEGER(2);
	}
	return NULL;
}

/* 1.3.6.1.2.1.10.166.11.1.2.3.1.x = 14*/
#define VRFRTTAB_NAMELEN 14
static struct bgp *bgpL3vpnVrfRt_lookup(struct variable *v, oid name[],
					size_t *length, char *vrf_name,
					uint32_t *rt_index, uint8_t *rt_type,
					int exact)
{
	uint32_t type_index_size;
	struct bgp *l3vpn_bgp;
	size_t namelen = v ? v->namelen : VRFRTTAB_NAMELEN;
	int vrf_name_len, len;

	/* too long ? */
	if (*length - namelen
	    > (VRF_NAMSIZ + sizeof(uint32_t)) + sizeof(uint8_t))
		return NULL;

	type_index_size = sizeof(uint32_t) + sizeof(uint8_t);
	/* do we have index info in the oid ? */
	if (*length - namelen != 0 && *length - namelen >= type_index_size) {
		/* copy the info from the oid */
		vrf_name_len = *length - (namelen + type_index_size);
		oid2string(name + namelen, vrf_name_len, vrf_name);
		oid2int(name + namelen + vrf_name_len, (int *)rt_index);
		*rt_type = name[namelen + vrf_name_len + sizeof(uint32_t)];
	}

	/* validate the RT index is in range */
	if (*rt_index > AFI_IP6)
		return NULL;

	if (exact) {
		l3vpn_bgp = bgp_lookup_by_name(vrf_name);
		if (l3vpn_bgp && !is_bgp_vrf_mplsvpn(l3vpn_bgp))
			return NULL;
		if (!l3vpn_bgp)
			return NULL;
		if ((*rt_index != AFI_IP) && (*rt_index != AFI_IP6))
			return NULL;
		/* do we have RT config */
		if (!(l3vpn_bgp->vpn_policy[*rt_index]
			      .rtlist[BGP_VPN_POLICY_DIR_FROMVPN]
		      || l3vpn_bgp->vpn_policy[*rt_index]
				 .rtlist[BGP_VPN_POLICY_DIR_TOVPN]))
			return NULL;
		return l3vpn_bgp;
	}
	if (strnlen(vrf_name, VRF_NAMSIZ) == 0)
		l3vpn_bgp = bgp_lookup_by_name_next(vrf_name);
	else
		l3vpn_bgp = bgp_lookup_by_name(vrf_name);
	while (l3vpn_bgp) {
		switch (*rt_index) {
		case 0:
			*rt_index = AFI_IP;
			break;
		case AFI_IP:
			*rt_index = AFI_IP6;
			break;
		case AFI_IP6:
			*rt_index = 0;
			continue;
		}
		if (*rt_index) {
			switch (*rt_type) {
			case 0:
				*rt_type = MPLSVPNVRFRTTYPEIMPORT;
				break;
			case MPLSVPNVRFRTTYPEIMPORT:
				*rt_type = MPLSVPNVRFRTTYPEEXPORT;
				break;
			case MPLSVPNVRFRTTYPEEXPORT:
			case MPLSVPNVRFRTTYPEBOTH:
				*rt_type = 0;
				break;
			}
			if (*rt_type) {
				bool import, export;

				import =
					(!!l3vpn_bgp->vpn_policy[*rt_index].rtlist
						   [BGP_VPN_POLICY_DIR_FROMVPN]);
				export =
					(!!l3vpn_bgp->vpn_policy[*rt_index].rtlist
						   [BGP_VPN_POLICY_DIR_TOVPN]);
				if (*rt_type == MPLSVPNVRFRTTYPEIMPORT
				    && !import)
					continue;
				if (*rt_type == MPLSVPNVRFRTTYPEEXPORT
				    && !export)
					continue;
				/* ckeck for both */
				if (*rt_type == MPLSVPNVRFRTTYPEIMPORT && import
				    && export
				    && ecommunity_cmp(
					    l3vpn_bgp->vpn_policy[*rt_index].rtlist
						    [BGP_VPN_POLICY_DIR_FROMVPN],
					    l3vpn_bgp->vpn_policy[*rt_index].rtlist
						    [BGP_VPN_POLICY_DIR_TOVPN]))
					*rt_type = MPLSVPNVRFRTTYPEBOTH;

				/* we have a match copy the oid info */
				vrf_name_len =
					strnlen(l3vpn_bgp->name, VRF_NAMSIZ);
				len = vrf_name_len + sizeof(uint32_t)
				      + sizeof(uint8_t);
				oid_copy_str(name + namelen, l3vpn_bgp->name,
					     vrf_name_len);
				oid_copy_int(name + namelen + vrf_name_len,
					     (int *)rt_index);
				name[(namelen + len) - 1] = *rt_type;
				*length = len + namelen;
				return l3vpn_bgp;
			}
			l3vpn_bgp = bgp_lookup_by_name_next(l3vpn_bgp->name);
		}
	}
	return NULL;
}

static const char *rt_type2str(uint8_t rt_type)
{
	switch (rt_type) {
	case MPLSVPNVRFRTTYPEIMPORT:
		return "import";
	case MPLSVPNVRFRTTYPEEXPORT:
		return "export";
	case MPLSVPNVRFRTTYPEBOTH:
		return "both";
	default:
		return "unknown";
	}
}
static uint8_t *mplsL3vpnVrfRtTable(struct variable *v, oid name[],
				    size_t *length, int exact, size_t *var_len,
				    WriteMethod **write_method)
{
	char vrf_name[VRF_NAMSIZ];
	struct bgp *l3vpn_bgp;
	uint32_t rt_index = 0;
	uint8_t rt_type = 0;
	char *rt_b = NULL;
	static char rt_b_str[BUFSIZ] = {};

	if (smux_header_table(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	memset(vrf_name, 0, VRF_NAMSIZ);
	l3vpn_bgp = bgpL3vpnVrfRt_lookup(v, name, length, vrf_name, &rt_index,
					 &rt_type, exact);

	if (!l3vpn_bgp)
		return NULL;

	switch (v->magic) {
	case MPLSL3VPNVRFRT:
		switch (rt_type) {
		case MPLSVPNVRFRTTYPEIMPORT:
			rt_b = ecommunity_ecom2str(
				l3vpn_bgp->vpn_policy[rt_index]
					.rtlist[BGP_VPN_POLICY_DIR_FROMVPN],
				ECOMMUNITY_FORMAT_ROUTE_MAP,
				ECOMMUNITY_ROUTE_TARGET);
			break;
		case MPLSVPNVRFRTTYPEEXPORT:
		case MPLSVPNVRFRTTYPEBOTH:
			rt_b = ecommunity_ecom2str(
				l3vpn_bgp->vpn_policy[rt_index]
					.rtlist[BGP_VPN_POLICY_DIR_TOVPN],
				ECOMMUNITY_FORMAT_ROUTE_MAP,
				ECOMMUNITY_ROUTE_TARGET);
			break;
		default:
			break;
		}
		if (rt_b) {
			*var_len = strnlen(rt_b, ECOMMUNITY_STRLEN);
			strlcpy(rt_b_str, rt_b, sizeof(rt_b_str));
			XFREE(MTYPE_ECOMMUNITY_STR, rt_b);
		} else {
			*var_len = 0;
		}
		return (uint8_t *)rt_b_str;
	case MPLSL3VPNVRFRTDESCR:
		/* since we dont have a description generate one */
		memset(rt_description, 0, VRF_NAMSIZ + RT_PREAMBLE_SIZE);
		snprintf(rt_description, VRF_NAMSIZ + RT_PREAMBLE_SIZE,
			 "RT %s for VRF %s", rt_type2str(rt_type),
			 l3vpn_bgp->name);
		*var_len =
			strnlen(rt_description, VRF_NAMSIZ + RT_PREAMBLE_SIZE);
		return (uint8_t *)rt_description;
	case MPLSL3VPNVRFRTROWSTATUS:
		return SNMP_INTEGER(1);
	case MPLSL3VPNVRFRTSTORAGETYPE:
		return SNMP_INTEGER(2);
	}
	return NULL;
}

/* 1.3.6.1.2.1.10.166.11.1.3.1.1.x = 14*/
#define PERFTAB_NAMELEN 14

static uint8_t *mplsL3vpnPerfTable(struct variable *v, oid name[],
				   size_t *length, int exact, size_t *var_len,
				   WriteMethod **write_method)
{
	char vrf_name[VRF_NAMSIZ];
	struct bgp *l3vpn_bgp;

	if (smux_header_table(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	memset(vrf_name, 0, VRF_NAMSIZ);
	l3vpn_bgp = bgpL3vpnVrf_lookup(v, name, length, vrf_name, exact);

	if (!l3vpn_bgp)
		return NULL;

	switch (v->magic) {
	case MPLSL3VPNVRFPERFROUTESADDED:
		return SNMP_INTEGER(l3vpn_bgp->snmp_stats->routes_added);
	case MPLSL3VPNVRFPERFROUTESDELETED:
		return SNMP_INTEGER(l3vpn_bgp->snmp_stats->routes_deleted);
	case MPLSL3VPNVRFPERFCURRNUMROUTES:
		return SNMP_INTEGER(bgp_mpls_l3vpn_current_routes(l3vpn_bgp));
	}
	return NULL;
}

static struct bgp_path_info *
bgp_lookup_route(struct bgp *l3vpn_bgp, struct bgp_dest **dest,
		 struct prefix *prefix, uint16_t policy, struct ipaddr *nexthop)
{
	struct bgp_path_info *pi = NULL;
	struct bgp_table *table;

	switch (prefix->family) {
	case AF_INET:
		table = l3vpn_bgp->rib[AFI_IP][SAFI_UNICAST];
		break;
	case AF_INET6:
		table = l3vpn_bgp->rib[AFI_IP6][SAFI_UNICAST];
		break;
	default:
		return NULL;
	}

	/*get the prefix */
	*dest = bgp_node_lookup(table, prefix);
	if (*dest == NULL)
		return NULL;

	/* now find the right path */
	pi = bgp_dest_get_bgp_path_info(*dest);
	for (; pi; pi = pi->next) {
		switch (nexthop->ipa_type) {
		case IPADDR_V4:
			if (nexthop->ip._v4_addr.s_addr
			    == pi->attr->nexthop.s_addr)
				return pi;
			break;
		case IPADDR_V6:
			if (memcmp(&nexthop->ip._v6_addr,
				   &pi->attr->mp_nexthop_global,
				   sizeof(struct in6_addr))
			    == 0)
				return pi;
			break;
		case IPADDR_NONE:
			return pi;
		}
	}
	return NULL;
}

static struct bgp_path_info *bgp_lookup_route_next(struct bgp **l3vpn_bgp,
						   struct bgp_dest **dest,
						   struct prefix *prefix,
						   uint16_t *policy,
						   struct ipaddr *nexthop)
{
	struct bgp_path_info *pi = NULL;
	struct bgp_table *table;
	const struct prefix *p;
	uint8_t family;

	/* First route?*/
	if (prefix->prefixlen == 0) {
		/* try V4 table */
		table = (*l3vpn_bgp)->rib[AFI_IP][SAFI_UNICAST];
		for (*dest = bgp_table_top(table); *dest;
		     *dest = bgp_route_next(*dest)) {
			pi = bgp_dest_get_bgp_path_info(*dest);
			if (pi)
				break;
		}

		if (!pi) {
			/* try V6 table */
			table = (*l3vpn_bgp)->rib[AFI_IP6][SAFI_UNICAST];
			for (*dest = bgp_table_top(table); *dest;
			     *dest = bgp_route_next(*dest)) {
				pi = bgp_dest_get_bgp_path_info(*dest);
				if (pi)
					break;
			}
		}
		return pi;
	}
	/* real next search for the entry first use exact lookup */
	pi = bgp_lookup_route(*l3vpn_bgp, dest, prefix, *policy, nexthop);

	if (pi == NULL)
		return NULL;

	p = bgp_dest_get_prefix(*dest);
	family = p->family;

	/* We have found the input path let's find the next one in the list */
	if (pi->next) {
		/* ensure OID is always higher for multipath routes by
		 * incrementing opaque policy oid
		 */
		*policy += 1;
		return pi->next;
	}

	/* No more paths in the input route so find the next route */
	for (; *l3vpn_bgp;
	     *l3vpn_bgp = bgp_lookup_by_name_next((*l3vpn_bgp)->name)) {
		*policy = 0;
		if (!*dest) {
			table = (*l3vpn_bgp)->rib[AFI_IP][SAFI_UNICAST];
			*dest = bgp_table_top(table);
			family = AF_INET;
		} else
			*dest = bgp_route_next(*dest);

		while (true) {
			for (; *dest; *dest = bgp_route_next(*dest)) {
				pi = bgp_dest_get_bgp_path_info(*dest);

				if (pi)
					return pi;
			}
			if (family == AF_INET) {
				table = (*l3vpn_bgp)
						->rib[AFI_IP6][SAFI_UNICAST];
				*dest = bgp_table_top(table);
				family = AF_INET6;
				continue;
			}
			break;
		}
	}

	return NULL;
}

static bool is_addr_type(oid id)
{
	switch (id) {
	case INETADDRESSTYPEUNKNOWN:
	case INETADDRESSTYPEIPV4:
	case INETADDRESSTYPEIPV6:
		return true;
	}
	return false;
}

/* 1.3.6.1.2.1.10.166.11.1.4.1.1.x = 14*/
#define PERFTAB_NAMELEN 14

static struct bgp_path_info *bgpL3vpnRte_lookup(struct variable *v, oid name[],
						size_t *length, char *vrf_name,
						struct bgp **l3vpn_bgp,
						struct bgp_dest **dest,
						uint16_t *policy, int exact)
{
	uint8_t i;
	uint8_t vrf_name_len = 0;
	struct bgp_path_info *pi = NULL;
	size_t namelen = v ? v->namelen : IFCONFTAB_NAMELEN;
	struct prefix prefix = {0};
	struct ipaddr nexthop = {0};
	uint8_t prefix_type;
	uint8_t nexthop_type;

	if ((uint32_t)(*length - namelen) > (VRF_NAMSIZ + 37))
		return NULL;

	if (*length - namelen != 0) {
		/* parse incoming OID */
		for (i = namelen; i < (*length); i++) {
			if (is_addr_type(name[i]))
				break;
			vrf_name_len++;
		}
		if (vrf_name_len > VRF_NAMSIZ)
			return NULL;

		oid2string(name + namelen, vrf_name_len, vrf_name);
		prefix_type = name[i++];
		switch (prefix_type) {
		case INETADDRESSTYPEUNKNOWN:
			prefix.family = AF_UNSPEC;
			break;
		case INETADDRESSTYPEIPV4:
			prefix.family = AF_INET;
			oid2in_addr(&name[i], sizeof(struct in_addr),
				    &prefix.u.prefix4);
			i += sizeof(struct in_addr);
			break;
		case INETADDRESSTYPEIPV6:
			prefix.family = AF_INET6;
			oid2in6_addr(&name[i], &prefix.u.prefix6);
			i += sizeof(struct in6_addr);
			break;
		}
		prefix.prefixlen = (uint8_t)name[i++];
		*policy |= name[i++] << 8;
		*policy |= name[i++];
		nexthop_type = name[i++];
		switch (nexthop_type) {
		case INETADDRESSTYPEUNKNOWN:
			nexthop.ipa_type = (prefix.family == AF_INET)
						   ? IPADDR_V4
						   : IPADDR_V6;
			break;
		case INETADDRESSTYPEIPV4:
			nexthop.ipa_type = IPADDR_V4;
			oid2in_addr(&name[i], sizeof(struct in_addr),
				    &nexthop.ip._v4_addr);
			/* i += sizeof(struct in_addr); */
			break;
		case INETADDRESSTYPEIPV6:
			nexthop.ipa_type = IPADDR_V6;
			oid2in6_addr(&name[i], &nexthop.ip._v6_addr);
			/* i += sizeof(struct in6_addr); */
			break;
		}
	}

	if (exact) {
		*l3vpn_bgp = bgp_lookup_by_name(vrf_name);
		if (*l3vpn_bgp && !is_bgp_vrf_mplsvpn(*l3vpn_bgp))
			return NULL;
		if (*l3vpn_bgp == NULL)
			return NULL;

		/* now lookup the route in this bgp table */
		pi = bgp_lookup_route(*l3vpn_bgp, dest, &prefix, *policy,
				      &nexthop);
	} else {
		int str_len;

		str_len = strnlen(vrf_name, VRF_NAMSIZ);
		if (str_len == 0) {
			*l3vpn_bgp = bgp_lookup_by_name_next(vrf_name);
		} else
			/* otherwise lookup the one we have */
			*l3vpn_bgp = bgp_lookup_by_name(vrf_name);

		if (*l3vpn_bgp == NULL)
			return NULL;

		pi = bgp_lookup_route_next(l3vpn_bgp, dest, &prefix, policy,
					   &nexthop);
		if (pi) {
			const struct prefix *p = bgp_dest_get_prefix(*dest);
			uint8_t oid_index;
			bool v4 = (p->family == AF_INET);
			uint8_t addr_len = v4 ? sizeof(struct in_addr)
					      : sizeof(struct in6_addr);
			struct attr *attr = pi->attr;

			vrf_name_len = strnlen((*l3vpn_bgp)->name, VRF_NAMSIZ);

			/* copy the index parameters */
			oid_copy_str(&name[namelen], (*l3vpn_bgp)->name,
				     vrf_name_len);
			oid_index = namelen + vrf_name_len;
			if (v4) {
				name[oid_index++] = INETADDRESSTYPEIPV4;
				oid_copy_in_addr(&name[oid_index],
						 &p->u.prefix4);
			} else {
				name[oid_index++] = INETADDRESSTYPEIPV6;
				oid_copy_in6_addr(&name[oid_index],
						  &p->u.prefix6);
			}

			oid_index += addr_len;
			name[oid_index++] = p->prefixlen;
			name[oid_index++] = *policy >> 8;
			name[oid_index++] = *policy & 0xff;

			if (!BGP_ATTR_NEXTHOP_AFI_IP6(attr)) {
				if (attr->nexthop.s_addr == INADDR_ANY)
					name[oid_index++] =
						INETADDRESSTYPEUNKNOWN;
				else {
					name[oid_index++] = INETADDRESSTYPEIPV4;
					oid_copy_in_addr(&name[oid_index],
							 &attr->nexthop);
					oid_index += sizeof(struct in_addr);
				}
			} else {
				if (IN6_IS_ADDR_UNSPECIFIED(
					    &attr->mp_nexthop_global))
					name[oid_index++] =
						INETADDRESSTYPEUNKNOWN;
				else {
					name[oid_index++] = INETADDRESSTYPEIPV6;
					oid_copy_in6_addr(
						&name[oid_index],
						&attr->mp_nexthop_global);
					oid_index += sizeof(struct in6_addr);
				}
			}
			*length = oid_index;
		}
	}
	return pi;
}

static uint8_t *mplsL3vpnRteTable(struct variable *v, oid name[],
				  size_t *length, int exact, size_t *var_len,
				  WriteMethod **write_method)
{
	char vrf_name[VRF_NAMSIZ];
	struct bgp *l3vpn_bgp;
	struct bgp_dest *dest;
	struct bgp_path_info *pi, *bpi_ultimate;
	const struct prefix *p;
	uint16_t policy = 0;

	if (smux_header_table(v, name, length, exact, var_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	memset(vrf_name, 0, VRF_NAMSIZ);
	pi = bgpL3vpnRte_lookup(v, name, length, vrf_name, &l3vpn_bgp, &dest,
				&policy, exact);


	if (!pi)
		return NULL;

	bpi_ultimate = bgp_get_imported_bpi_ultimate(pi);

	p = bgp_dest_get_prefix(dest);

	if (!p)
		return NULL;

	switch (v->magic) {
	case MPLSL3VPNVRFRTEINETCIDRDESTTYPE:
		switch (p->family) {
		case AF_INET:
			return SNMP_INTEGER(INETADDRESSTYPEIPV4);
		case AF_INET6:
			return SNMP_INTEGER(INETADDRESSTYPEIPV6);
		default:
			return SNMP_INTEGER(INETADDRESSTYPEUNKNOWN);
		}
	case MPLSL3VPNVRFRTEINETCIDRDEST:
		switch (p->family) {
		case AF_INET:
			return SNMP_IPADDRESS(p->u.prefix4);
		case AF_INET6:
			return SNMP_IP6ADDRESS(p->u.prefix6);
		default:
			*var_len = 0;
			return NULL;
		}
	case MPLSL3VPNVRFRTEINETCIDRPFXLEN:
		return SNMP_INTEGER(p->prefixlen);
	case MPLSL3VPNVRFRTEINETCIDRPOLICY:
		*var_len = sizeof(mpls_l3vpn_policy_oid);
		mpls_l3vpn_policy_oid[0] = policy >> 8;
		mpls_l3vpn_policy_oid[1] = policy & 0xff;
		return (uint8_t *)mpls_l3vpn_policy_oid;
	case MPLSL3VPNVRFRTEINETCIDRNHOPTYPE:
		if (!BGP_ATTR_NEXTHOP_AFI_IP6(pi->attr)) {
			if (pi->attr->nexthop.s_addr == INADDR_ANY)
				return SNMP_INTEGER(INETADDRESSTYPEUNKNOWN);
			else
				return SNMP_INTEGER(INETADDRESSTYPEIPV4);
		} else if (IN6_IS_ADDR_UNSPECIFIED(
				   &pi->attr->mp_nexthop_global))
			return SNMP_INTEGER(INETADDRESSTYPEUNKNOWN);
		else
			return SNMP_INTEGER(INETADDRESSTYPEIPV6);

	case MPLSL3VPNVRFRTEINETCIDRNEXTHOP:
		if (!BGP_ATTR_NEXTHOP_AFI_IP6(pi->attr))
			if (pi->attr->nexthop.s_addr == INADDR_ANY) {
				*var_len = 0;
				return (uint8_t *)empty_nhop;
			} else
				return SNMP_IPADDRESS(pi->attr->nexthop);
		else if (IN6_IS_ADDR_UNSPECIFIED(
				 &pi->attr->mp_nexthop_global)) {
			*var_len = 0;
			return (uint8_t *)empty_nhop;
		} else
			return SNMP_IP6ADDRESS(pi->attr->mp_nexthop_global);

	case MPLSL3VPNVRFRTEINETCIDRIFINDEX:
		if (pi->nexthop && pi->nexthop->nexthop)
			return SNMP_INTEGER(pi->nexthop->nexthop->ifindex);
		else
			return SNMP_INTEGER(0);
	case MPLSL3VPNVRFRTEINETCIDRTYPE:
		if (pi->nexthop && pi->nexthop->nexthop) {
			switch (pi->nexthop->nexthop->type) {
			case NEXTHOP_TYPE_IFINDEX:
				return SNMP_INTEGER(
					MPLSL3VPNVRFRTECIDRTYPELOCAL);
			case NEXTHOP_TYPE_IPV4:
			case NEXTHOP_TYPE_IPV4_IFINDEX:
			case NEXTHOP_TYPE_IPV6:
			case NEXTHOP_TYPE_IPV6_IFINDEX:
				return SNMP_INTEGER(
					MPLSL3VPNVRFRTECIDRTYPEREMOTE);
			case NEXTHOP_TYPE_BLACKHOLE:
				switch (pi->nexthop->nexthop->bh_type) {
				case BLACKHOLE_REJECT:
					return SNMP_INTEGER(
						MPLSL3VPNVRFRTECIDRTYPEREJECT);
				case BLACKHOLE_UNSPEC:
				case BLACKHOLE_NULL:
				case BLACKHOLE_ADMINPROHIB:
					return SNMP_INTEGER(
						MPLSL3VPNVRFRTECIDRTYPEBLACKHOLE);
				}
				break;
			}
		} else
			return SNMP_INTEGER(MPLSL3VPNVRFRTECIDRTYPEOTHER);
		break;
	case MPLSL3VPNVRFRTEINETCIDRPROTO:
		switch (pi->type) {
		case ZEBRA_ROUTE_CONNECT:
			return SNMP_INTEGER(IANAIPROUTEPROTOCOLLOCAL);
		case ZEBRA_ROUTE_STATIC:
			return SNMP_INTEGER(IANAIPROUTEPROTOCOLNETMGMT);
		case ZEBRA_ROUTE_RIP:
		case ZEBRA_ROUTE_RIPNG:
			return SNMP_INTEGER(IANAIPROUTEPROTOCOLRIP);
		case ZEBRA_ROUTE_OSPF:
		case ZEBRA_ROUTE_OSPF6:
			return SNMP_INTEGER(IANAIPROUTEPROTOCOLOSPF);
		case ZEBRA_ROUTE_ISIS:
			return SNMP_INTEGER(IANAIPROUTEPROTOCOLISIS);
		case ZEBRA_ROUTE_BGP:
			return SNMP_INTEGER(IANAIPROUTEPROTOCOLBGP);
		case ZEBRA_ROUTE_EIGRP:
			return SNMP_INTEGER(IANAIPROUTEPROTOCOLCISCOEIGRP);
		default:
			return SNMP_INTEGER(IANAIPROUTEPROTOCOLOTHER);
		}
	case MPLSL3VPNVRFRTEINETCIDRAGE:
		return SNMP_INTEGER(pi->uptime);
	case MPLSL3VPNVRFRTEINETCIDRNEXTHOPAS:
		return SNMP_INTEGER(pi->peer ? pi->peer->as : 0);
	case MPLSL3VPNVRFRTEINETCIDRMETRIC1:
		if (bpi_ultimate->extra)
			return SNMP_INTEGER(bpi_ultimate->extra->igpmetric);
		else
			return SNMP_INTEGER(0);
	case MPLSL3VPNVRFRTEINETCIDRMETRIC2:
		return SNMP_INTEGER(-1);
	case MPLSL3VPNVRFRTEINETCIDRMETRIC3:
		return SNMP_INTEGER(-1);
	case MPLSL3VPNVRFRTEINETCIDRMETRIC4:
		return SNMP_INTEGER(-1);
	case MPLSL3VPNVRFRTEINETCIDRMETRIC5:
		return SNMP_INTEGER(-1);
	case MPLSL3VPNVRFRTEINETCIDRXCPOINTER:
		return SNMP_OCTET(0);
	case MPLSL3VPNVRFRTEINETCIDRSTATUS:
		return SNMP_INTEGER(1);
	}
	return NULL;
}

void bgp_mpls_l3vpn_module_init(void)
{
	hook_register(bgp_vrf_status_changed, bgp_vrf_check_update_active);
	hook_register(bgp_snmp_init_stats, bgp_init_snmp_stats);
	hook_register(bgp_snmp_update_last_changed,
		      bgp_mpls_l3vpn_update_last_changed);
	hook_register(bgp_snmp_update_stats, bgp_snmp_update_route_stats);
	REGISTER_MIB("mplsL3VpnMIB", mpls_l3vpn_variables, variable,
		     mpls_l3vpn_oid);
}
