/* FIB SNMP.
 * Copyright (C) 1999 Kunihiro Ishiguro
 *
 * 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 this program; see the file COPYING; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

/*
 * Currently SNMP is only running properly for MIBs in the default VRF.
 */

#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 "smux.h"
#include "table.h"
#include "vrf.h"
#include "hook.h"
#include "libfrr.h"
#include "lib/version.h"

#include "zebra/rib.h"
#include "zebra/zserv.h"
#include "zebra/zebra_vrf.h"

#define IPFWMIB 1,3,6,1,2,1,4,24

/* ipForwardTable */
#define IPFORWARDDEST                         1
#define IPFORWARDMASK                         2
#define IPFORWARDPOLICY                       3
#define IPFORWARDNEXTHOP                      4
#define IPFORWARDIFINDEX                      5
#define IPFORWARDTYPE                         6
#define IPFORWARDPROTO                        7
#define IPFORWARDAGE                          8
#define IPFORWARDINFO                         9
#define IPFORWARDNEXTHOPAS                   10
#define IPFORWARDMETRIC1                     11
#define IPFORWARDMETRIC2                     12
#define IPFORWARDMETRIC3                     13
#define IPFORWARDMETRIC4                     14
#define IPFORWARDMETRIC5                     15

/* ipCidrRouteTable */
#define IPCIDRROUTEDEST                       1
#define IPCIDRROUTEMASK                       2
#define IPCIDRROUTETOS                        3
#define IPCIDRROUTENEXTHOP                    4
#define IPCIDRROUTEIFINDEX                    5
#define IPCIDRROUTETYPE                       6
#define IPCIDRROUTEPROTO                      7
#define IPCIDRROUTEAGE                        8
#define IPCIDRROUTEINFO                       9
#define IPCIDRROUTENEXTHOPAS                 10
#define IPCIDRROUTEMETRIC1                   11
#define IPCIDRROUTEMETRIC2                   12
#define IPCIDRROUTEMETRIC3                   13
#define IPCIDRROUTEMETRIC4                   14
#define IPCIDRROUTEMETRIC5                   15
#define IPCIDRROUTESTATUS                    16

#define INTEGER32 ASN_INTEGER
#define GAUGE32 ASN_GAUGE
#define ENUMERATION ASN_INTEGER
#define ROWSTATUS ASN_INTEGER
#define IPADDRESS ASN_IPADDRESS
#define OBJECTIDENTIFIER ASN_OBJECT_ID

static oid ipfw_oid[] = {IPFWMIB};

/* Hook functions. */
static uint8_t *ipFwNumber(struct variable *, oid[], size_t *, int, size_t *,
			   WriteMethod **);
static uint8_t *ipFwTable(struct variable *, oid[], size_t *, int, size_t *,
			  WriteMethod **);
static uint8_t *ipCidrNumber(struct variable *, oid[], size_t *, int, size_t *,
			     WriteMethod **);
static uint8_t *ipCidrTable(struct variable *, oid[], size_t *, int, size_t *,
			    WriteMethod **);

static struct variable zebra_variables[] = {
	{0, GAUGE32, RONLY, ipFwNumber, 1, {1}},
	{IPFORWARDDEST, IPADDRESS, RONLY, ipFwTable, 3, {2, 1, 1}},
	{IPFORWARDMASK, IPADDRESS, RONLY, ipFwTable, 3, {2, 1, 2}},
	{IPFORWARDPOLICY, INTEGER32, RONLY, ipFwTable, 3, {2, 1, 3}},
	{IPFORWARDNEXTHOP, IPADDRESS, RONLY, ipFwTable, 3, {2, 1, 4}},
	{IPFORWARDIFINDEX, INTEGER32, RONLY, ipFwTable, 3, {2, 1, 5}},
	{IPFORWARDTYPE, ENUMERATION, RONLY, ipFwTable, 3, {2, 1, 6}},
	{IPFORWARDPROTO, ENUMERATION, RONLY, ipFwTable, 3, {2, 1, 7}},
	{IPFORWARDAGE, INTEGER32, RONLY, ipFwTable, 3, {2, 1, 8}},
	{IPFORWARDINFO, OBJECTIDENTIFIER, RONLY, ipFwTable, 3, {2, 1, 9}},
	{IPFORWARDNEXTHOPAS, INTEGER32, RONLY, ipFwTable, 3, {2, 1, 10}},
	{IPFORWARDMETRIC1, INTEGER32, RONLY, ipFwTable, 3, {2, 1, 11}},
	{IPFORWARDMETRIC2, INTEGER32, RONLY, ipFwTable, 3, {2, 1, 12}},
	{IPFORWARDMETRIC3, INTEGER32, RONLY, ipFwTable, 3, {2, 1, 13}},
	{IPFORWARDMETRIC4, INTEGER32, RONLY, ipFwTable, 3, {2, 1, 14}},
	{IPFORWARDMETRIC5, INTEGER32, RONLY, ipFwTable, 3, {2, 1, 15}},
	{0, GAUGE32, RONLY, ipCidrNumber, 1, {3}},
	{IPCIDRROUTEDEST, IPADDRESS, RONLY, ipCidrTable, 3, {4, 1, 1}},
	{IPCIDRROUTEMASK, IPADDRESS, RONLY, ipCidrTable, 3, {4, 1, 2}},
	{IPCIDRROUTETOS, INTEGER32, RONLY, ipCidrTable, 3, {4, 1, 3}},
	{IPCIDRROUTENEXTHOP, IPADDRESS, RONLY, ipCidrTable, 3, {4, 1, 4}},
	{IPCIDRROUTEIFINDEX, INTEGER32, RONLY, ipCidrTable, 3, {4, 1, 5}},
	{IPCIDRROUTETYPE, ENUMERATION, RONLY, ipCidrTable, 3, {4, 1, 6}},
	{IPCIDRROUTEPROTO, ENUMERATION, RONLY, ipCidrTable, 3, {4, 1, 7}},
	{IPCIDRROUTEAGE, INTEGER32, RONLY, ipCidrTable, 3, {4, 1, 8}},
	{IPCIDRROUTEINFO, OBJECTIDENTIFIER, RONLY, ipCidrTable, 3, {4, 1, 9}},
	{IPCIDRROUTENEXTHOPAS, INTEGER32, RONLY, ipCidrTable, 3, {4, 1, 10}},
	{IPCIDRROUTEMETRIC1, INTEGER32, RONLY, ipCidrTable, 3, {4, 1, 11}},
	{IPCIDRROUTEMETRIC2, INTEGER32, RONLY, ipCidrTable, 3, {4, 1, 12}},
	{IPCIDRROUTEMETRIC3, INTEGER32, RONLY, ipCidrTable, 3, {4, 1, 13}},
	{IPCIDRROUTEMETRIC4, INTEGER32, RONLY, ipCidrTable, 3, {4, 1, 14}},
	{IPCIDRROUTEMETRIC5, INTEGER32, RONLY, ipCidrTable, 3, {4, 1, 15}},
	{IPCIDRROUTESTATUS, ROWSTATUS, RONLY, ipCidrTable, 3, {4, 1, 16}}};


static uint8_t *ipFwNumber(struct variable *v, oid objid[], size_t *objid_len,
			   int exact, size_t *val_len,
			   WriteMethod **write_method)
{
	static int result;
	struct route_table *table;
	struct route_node *rn;
	struct route_entry *re;

	if (smux_header_generic(v, objid, objid_len, exact, val_len,
				write_method)
	    == MATCH_FAILED)
		return NULL;

	table = zebra_vrf_table(AFI_IP, SAFI_UNICAST, VRF_DEFAULT);
	if (!table)
		return NULL;

	/* Return number of routing entries. */
	result = 0;
	for (rn = route_top(table); rn; rn = route_next(rn))
		RNODE_FOREACH_RE (rn, re) {
			result++;
		}

	return (uint8_t *)&result;
}

static uint8_t *ipCidrNumber(struct variable *v, oid objid[], size_t *objid_len,
			     int exact, size_t *val_len,
			     WriteMethod **write_method)
{
	static int result;
	struct route_table *table;
	struct route_node *rn;
	struct route_entry *re;

	if (smux_header_generic(v, objid, objid_len, exact, val_len,
				write_method)
	    == MATCH_FAILED)
		return NULL;

	table = zebra_vrf_table(AFI_IP, SAFI_UNICAST, VRF_DEFAULT);
	if (!table)
		return 0;

	/* Return number of routing entries. */
	result = 0;
	for (rn = route_top(table); rn; rn = route_next(rn))
		RNODE_FOREACH_RE (rn, re) {
			result++;
		}

	return (uint8_t *)&result;
}

static int in_addr_cmp(uint8_t *p1, uint8_t *p2)
{
	int i;

	for (i = 0; i < 4; i++) {
		if (*p1 < *p2)
			return -1;
		if (*p1 > *p2)
			return 1;
		p1++;
		p2++;
	}
	return 0;
}

static int in_addr_add(uint8_t *p, int num)
{
	int i, ip0;

	ip0 = *p;
	p += 4;
	for (i = 3; 0 <= i; i--) {
		p--;
		if (*p + num > 255) {
			*p += num;
			num = 1;
		} else {
			*p += num;
			return 1;
		}
	}
	if (ip0 > *p) {
		/* ip + num > 0xffffffff */
		return 0;
	}

	return 1;
}

static int proto_trans(int type)
{
	switch (type) {
	case ZEBRA_ROUTE_SYSTEM:
		return 1; /* other */
	case ZEBRA_ROUTE_KERNEL:
		return 1; /* other */
	case ZEBRA_ROUTE_CONNECT:
		return 2; /* local interface */
	case ZEBRA_ROUTE_STATIC:
		return 3; /* static route */
	case ZEBRA_ROUTE_RIP:
		return 8; /* rip */
	case ZEBRA_ROUTE_RIPNG:
		return 1; /* shouldn't happen */
	case ZEBRA_ROUTE_OSPF:
		return 13; /* ospf */
	case ZEBRA_ROUTE_OSPF6:
		return 1; /* shouldn't happen */
	case ZEBRA_ROUTE_BGP:
		return 14; /* bgp */
	default:
		return 1; /* other */
	}
}

static void check_replace(struct route_node *np2, struct route_entry *re2,
			  struct route_node **np, struct route_entry **re)
{
	int proto, proto2;

	if (!*np) {
		*np = np2;
		*re = re2;
		return;
	}

	if (prefix_cmp(&(*np)->p, &np2->p) < 0)
		return;
	if (prefix_cmp(&(*np)->p, &np2->p) > 0) {
		*np = np2;
		*re = re2;
		return;
	}

	proto = proto_trans((*re)->type);
	proto2 = proto_trans(re2->type);

	if (proto2 > proto)
		return;
	if (proto2 < proto) {
		*np = np2;
		*re = re2;
		return;
	}

	if (in_addr_cmp((uint8_t *)&(*re)->nhe->nhg.nexthop->gate.ipv4,
			(uint8_t *)&re2->nhe->nhg.nexthop->gate.ipv4)
	    <= 0)
		return;

	*np = np2;
	*re = re2;
	return;
}

static void get_fwtable_route_node(struct variable *v, oid objid[],
				   size_t *objid_len, int exact,
				   struct route_node **np,
				   struct route_entry **re)
{
	struct in_addr dest;
	struct route_table *table;
	struct route_node *np2;
	struct route_entry *re2;
	int proto;
	int policy;
	struct in_addr nexthop;
	uint8_t *pnt;
	int i;

	/* Init index variables */

	pnt = (uint8_t *)&dest;
	for (i = 0; i < 4; i++)
		*pnt++ = 0;

	pnt = (uint8_t *)&nexthop;
	for (i = 0; i < 4; i++)
		*pnt++ = 0;

	proto = 0;
	policy = 0;

	/* Init return variables */

	*np = NULL;
	*re = NULL;

	/* Short circuit exact matches of wrong length */

	if (exact && (*objid_len != (unsigned)v->namelen + 10))
		return;

	table = zebra_vrf_table(AFI_IP, SAFI_UNICAST, VRF_DEFAULT);
	if (!table)
		return;

	/* Get INDEX information out of OID.
	 * ipForwardDest, ipForwardProto, ipForwardPolicy, ipForwardNextHop
	 */

	if (*objid_len > (unsigned)v->namelen)
		oid2in_addr(objid + v->namelen,
			    MIN(4U, *objid_len - v->namelen), &dest);

	if (*objid_len > (unsigned)v->namelen + 4)
		proto = objid[v->namelen + 4];

	if (*objid_len > (unsigned)v->namelen + 5)
		policy = objid[v->namelen + 5];

	if (*objid_len > (unsigned)v->namelen + 6)
		oid2in_addr(objid + v->namelen + 6,
			    MIN(4U, *objid_len - v->namelen - 6), &nexthop);

	/* Apply GETNEXT on not exact search */

	if (!exact && (*objid_len >= (unsigned)v->namelen + 10)) {
		if (!in_addr_add((uint8_t *)&nexthop, 1))
			return;
	}

	/* For exact: search matching entry in rib table. */

	if (exact) {
		if (policy) /* Not supported (yet?) */
			return;
		for (*np = route_top(table); *np; *np = route_next(*np)) {
			if (!in_addr_cmp(&(*np)->p.u.prefix,
					 (uint8_t *)&dest)) {
				RNODE_FOREACH_RE (*np, *re) {
					if (!in_addr_cmp((uint8_t *)&(*re)->nhe
							 ->nhg.nexthop
							 ->gate.ipv4,
							 (uint8_t *)&nexthop))
						if (proto
						    == proto_trans((*re)->type))
							return;
				}
			}
		}
		return;
	}

	/* Search next best entry */

	for (np2 = route_top(table); np2; np2 = route_next(np2)) {

		/* Check destination first */
		if (in_addr_cmp(&np2->p.u.prefix, (uint8_t *)&dest) > 0)
			RNODE_FOREACH_RE (np2, re2) {
				check_replace(np2, re2, np, re);
			}

		if (in_addr_cmp(&np2->p.u.prefix, (uint8_t *)&dest)
		    == 0) { /* have to look at each re individually */
			RNODE_FOREACH_RE (np2, re2) {
				int proto2, policy2;

				proto2 = proto_trans(re2->type);
				policy2 = 0;

				if ((policy < policy2)
				    || ((policy == policy2) && (proto < proto2))
				    || ((policy == policy2) && (proto == proto2)
					&& (in_addr_cmp(
						    (uint8_t *)&re2->nhe
						    ->nhg.nexthop->gate.ipv4,
						    (uint8_t *)&nexthop)
					    >= 0)))
					check_replace(np2, re2, np, re);
			}
		}
	}

	if (!*re)
		return;

	policy = 0;
	proto = proto_trans((*re)->type);

	*objid_len = v->namelen + 10;
	pnt = (uint8_t *)&(*np)->p.u.prefix;
	for (i = 0; i < 4; i++)
		objid[v->namelen + i] = *pnt++;

	objid[v->namelen + 4] = proto;
	objid[v->namelen + 5] = policy;

	{
		struct nexthop *nexthop;

		nexthop = (*re)->nhe->nhg.nexthop;
		if (nexthop) {
			pnt = (uint8_t *)&nexthop->gate.ipv4;
			for (i = 0; i < 4; i++)
				objid[i + v->namelen + 6] = *pnt++;
		}
	}

	return;
}

static uint8_t *ipFwTable(struct variable *v, oid objid[], size_t *objid_len,
			  int exact, size_t *val_len,
			  WriteMethod **write_method)
{
	struct route_node *np;
	struct route_entry *re;
	static int result;
	static int resarr[2];
	static struct in_addr netmask;
	struct nexthop *nexthop;

	if (smux_header_table(v, objid, objid_len, exact, val_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	get_fwtable_route_node(v, objid, objid_len, exact, &np, &re);
	if (!np)
		return NULL;

	nexthop = re->nhe->nhg.nexthop;
	if (!nexthop)
		return NULL;

	switch (v->magic) {
	case IPFORWARDDEST:
		*val_len = 4;
		return &np->p.u.prefix;
	case IPFORWARDMASK:
		masklen2ip(np->p.prefixlen, &netmask);
		*val_len = 4;
		return (uint8_t *)&netmask;
	case IPFORWARDPOLICY:
		result = 0;
		*val_len = sizeof(int);
		return (uint8_t *)&result;
	case IPFORWARDNEXTHOP:
		*val_len = 4;
		return (uint8_t *)&nexthop->gate.ipv4;
	case IPFORWARDIFINDEX:
		*val_len = sizeof(int);
		return (uint8_t *)&nexthop->ifindex;
	case IPFORWARDTYPE:
		if (nexthop->type == NEXTHOP_TYPE_IFINDEX)
			result = 3;
		else
			result = 4;
		*val_len = sizeof(int);
		return (uint8_t *)&result;
	case IPFORWARDPROTO:
		result = proto_trans(re->type);
		*val_len = sizeof(int);
		return (uint8_t *)&result;
	case IPFORWARDAGE:
		result = 0;
		*val_len = sizeof(int);
		return (uint8_t *)&result;
	case IPFORWARDINFO:
		resarr[0] = 0;
		resarr[1] = 0;
		*val_len = 2 * sizeof(int);
		return (uint8_t *)resarr;
	case IPFORWARDNEXTHOPAS:
		result = -1;
		*val_len = sizeof(int);
		return (uint8_t *)&result;
	case IPFORWARDMETRIC1:
		result = 0;
		*val_len = sizeof(int);
		return (uint8_t *)&result;
	case IPFORWARDMETRIC2:
		result = 0;
		*val_len = sizeof(int);
		return (uint8_t *)&result;
	case IPFORWARDMETRIC3:
		result = 0;
		*val_len = sizeof(int);
		return (uint8_t *)&result;
	case IPFORWARDMETRIC4:
		result = 0;
		*val_len = sizeof(int);
		return (uint8_t *)&result;
	case IPFORWARDMETRIC5:
		result = 0;
		*val_len = sizeof(int);
		return (uint8_t *)&result;
	default:
		return NULL;
	}
	return NULL;
}

static uint8_t *ipCidrTable(struct variable *v, oid objid[], size_t *objid_len,
			    int exact, size_t *val_len,
			    WriteMethod **write_method)
{
	if (smux_header_table(v, objid, objid_len, exact, val_len, write_method)
	    == MATCH_FAILED)
		return NULL;

	switch (v->magic) {
	case IPCIDRROUTEDEST:
		break;
	default:
		return NULL;
	}
	return NULL;
}

static int zebra_snmp_init(struct thread_master *tm)
{
	smux_init(tm);
	REGISTER_MIB("mibII/ipforward", zebra_variables, variable, ipfw_oid);
	return 0;
}

static int zebra_snmp_module_init(void)
{
	hook_register(frr_late_init, zebra_snmp_init);
	return 0;
}

FRR_MODULE_SETUP(.name = "zebra_snmp", .version = FRR_VERSION,
		 .description = "zebra AgentX SNMP module",
		 .init = zebra_snmp_module_init,
);
