// SPDX-License-Identifier: GPL-2.0-or-later
/* zebra routemap.
 * Copyright (C) 2006 IBM Corporation
 */

#include <zebra.h>

#include "memory.h"
#include "prefix.h"
#include "rib.h"
#include "vty.h"
#include "routemap.h"
#include "command.h"
#include "filter.h"
#include "plist.h"
#include "nexthop.h"
#include "lib/route_types.h"
#include "vrf.h"
#include "frrstr.h"

#include "zebra/zebra_router.h"
#include "zebra/redistribute.h"
#include "zebra/debug.h"
#include "zebra/zebra_rnh.h"
#include "zebra/zebra_routemap.h"
#include "zebra/zebra_vrf.h"

#include "zebra/zebra_routemap_clippy.c"

static uint32_t zebra_rmap_update_timer = ZEBRA_RMAP_DEFAULT_UPDATE_TIMER;
static struct event *zebra_t_rmap_update = NULL;
char *zebra_import_table_routemap[AFI_MAX][SAFI_MAX][ZEBRA_KERNEL_TABLE_MAX];

struct zebra_rmap_obj {
	struct nexthop *nexthop;
	struct route_entry *re;
};

/* 'match tag TAG'
 * Match function return 1 if match is success else return 0
 */
static enum route_map_cmd_result_t
route_match_tag(void *rule, const struct prefix *prefix, void *object)
{
	route_tag_t *tag;
	struct zebra_rmap_obj *rm_data;

	tag = rule;
	rm_data = object;

	if (rm_data->re->tag == *tag)
		return RMAP_MATCH;

	return RMAP_NOMATCH;
}

/* Route map commands for tag matching */
static const struct route_map_rule_cmd route_match_tag_cmd = {
	"tag",
	route_match_tag,
	route_map_rule_tag_compile,
	route_map_rule_tag_free,
};


/* `match interface IFNAME' */
/* Match function return 1 if match is success else return zero. */
static enum route_map_cmd_result_t
route_match_interface(void *rule, const struct prefix *prefix, void *object)
{
	struct zebra_rmap_obj *rm_data;
	char *ifname = rule;
	ifindex_t ifindex;

	if (strcasecmp(ifname, "any") == 0)
		return RMAP_MATCH;
	rm_data = object;
	if (!rm_data || !rm_data->nexthop)
		return RMAP_NOMATCH;
	ifindex = ifname2ifindex(ifname, rm_data->nexthop->vrf_id);
	if (ifindex == 0)
		return RMAP_NOMATCH;
	if (rm_data->nexthop->ifindex == ifindex)
		return RMAP_MATCH;

	return RMAP_NOMATCH;
}

/* Route map `match interface' match statement. `arg' is IFNAME value */
static void *route_match_interface_compile(const char *arg)
{
	return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
}

/* Free route map's compiled `match interface' value. */
static void route_match_interface_free(void *rule)
{
	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}

static void show_vrf_proto_rm(struct vty *vty, struct zebra_vrf *zvrf,
			      int af_type)
{
	int i;

	vty_out(vty, "Protocol                  : route-map\n");
	vty_out(vty, "-------------------------------------\n");

	for (i = 0; i < ZEBRA_ROUTE_MAX; i++) {
		if (PROTO_RM_NAME(zvrf, af_type, i))
			vty_out(vty, "%-24s  : %-10s\n", zebra_route_string(i),
				PROTO_RM_NAME(zvrf, af_type, i));
		else
			vty_out(vty, "%-24s  : none\n", zebra_route_string(i));
	}

}

static void show_vrf_nht_rm(struct vty *vty, struct zebra_vrf *zvrf,
			    int af_type, json_object *json)
{
	int i;

	if (!json) {
		vty_out(vty, "Protocol                  : route-map\n");
		vty_out(vty, "-------------------------------------\n");
	}

	for (i = 0; i < ZEBRA_ROUTE_MAX; i++) {
		if (json) {
			if (NHT_RM_NAME(zvrf, af_type, i))
				json_object_string_add(
					json, zebra_route_string(i),
					NHT_RM_NAME(zvrf, af_type, i));
			else
				json_object_string_add(
					json, zebra_route_string(i), "none");
		} else {
			if (NHT_RM_NAME(zvrf, af_type, i))
				vty_out(vty, "%-24s  : %-10s\n",
					zebra_route_string(i),
					NHT_RM_NAME(zvrf, af_type, i));
			else
				vty_out(vty, "%-24s  : none\n",
					zebra_route_string(i));
		}
	}

	if (json) {
		if (NHT_RM_NAME(zvrf, af_type, i))
			json_object_string_add(json, "any",
					       NHT_RM_NAME(zvrf, af_type, i));
		else
			json_object_string_add(json, "any", "none");
	} else {
		if (NHT_RM_NAME(zvrf, af_type, i))
			vty_out(vty, "%-24s  : %-10s\n", "any",
				NHT_RM_NAME(zvrf, af_type, i));
		else
			vty_out(vty, "%-24s  : none\n", "any");
	}
}

static int show_proto_rm(struct vty *vty, int af_type, const char *vrf_all,
			 const char *vrf_name)
{
	struct zebra_vrf *zvrf;

	if (vrf_all) {
		struct vrf *vrf;

		RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
			zvrf = (struct zebra_vrf *)vrf->info;
			if (zvrf == NULL)
				continue;
			vty_out(vty, "VRF: %s\n", zvrf->vrf->name);
			show_vrf_proto_rm(vty, zvrf, af_type);
		}
	} else {
		vrf_id_t vrf_id = VRF_DEFAULT;

		if (vrf_name)
			VRF_GET_ID(vrf_id, vrf_name, false);

		zvrf = zebra_vrf_lookup_by_id(vrf_id);
		if (!zvrf)
			return CMD_SUCCESS;

		vty_out(vty, "VRF: %s\n", zvrf->vrf->name);
		show_vrf_proto_rm(vty, zvrf, af_type);
	}

	return CMD_SUCCESS;
}

static int show_nht_rm(struct vty *vty, int af_type, const char *vrf_all,
		       const char *vrf_name, bool use_json)
{
	struct zebra_vrf *zvrf;
	json_object *json = NULL;
	json_object *json_vrfs = NULL;

	if (use_json) {
		json = json_object_new_object();
		json_vrfs = json_object_new_object();
		json_object_string_add(json, "afi",
				       (af_type == AFI_IP) ? "ipv4" : "ipv6");
	}

	if (vrf_all) {
		struct vrf *vrf;

		if (use_json)
			json_object_object_add(json, "vrfs", json_vrfs);

		RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
			zvrf = (struct zebra_vrf *)vrf->info;
			if (zvrf == NULL)
				continue;

			if (use_json) {
				json_object *json_proto = NULL;
				json_object *json_vrf = NULL;
				json_vrf = json_object_new_object();
				json_object_object_add(
					json_vrfs, zvrf->vrf->name, json_vrf);
				json_proto = json_object_new_object();
				json_object_object_add(json_vrf, "protocols",
						       json_proto);
				show_vrf_nht_rm(vty, zvrf, af_type, json_proto);
			} else {
				vty_out(vty, "VRF: %s\n", zvrf->vrf->name);
				show_vrf_nht_rm(vty, zvrf, af_type, NULL);
			}
		}
	} else {
		json_object *json_proto = NULL;
		json_object *json_vrf = NULL;
		vrf_id_t vrf_id = VRF_DEFAULT;

		if (vrf_name)
			VRF_GET_ID(vrf_id, vrf_name, false);

		zvrf = zebra_vrf_lookup_by_id(vrf_id);
		if (!zvrf) {
			json_object_free(json);
			json_object_free(json_vrfs);
			return CMD_SUCCESS;
		}

		if (use_json) {
			json_object_object_add(json, "vrfs", json_vrfs);
			json_vrf = json_object_new_object();
			json_object_object_add(json_vrfs, zvrf->vrf->name,
					       json_vrf);
			json_proto = json_object_new_object();
			json_object_object_add(json_vrf, "protocols",
					       json_proto);
			show_vrf_nht_rm(vty, zvrf, af_type, json_proto);
		} else {
			vty_out(vty, "VRF: %s\n", zvrf->vrf->name);
			show_vrf_nht_rm(vty, zvrf, af_type, NULL);
		}
	}

	if (use_json)
		vty_json(vty, json);

	return CMD_SUCCESS;
}

/* Route map commands for interface matching */
static const struct route_map_rule_cmd route_match_interface_cmd = {
	"interface",
	route_match_interface,
	route_match_interface_compile,
	route_match_interface_free
};

int ip_protocol_rm_add(struct zebra_vrf *zvrf, const char *rmap, int rtype,
		       afi_t afi, safi_t safi)
{
	struct route_table *table;

	if (PROTO_RM_NAME(zvrf, afi, rtype)) {
		if (strcmp(PROTO_RM_NAME(zvrf, afi, rtype), rmap) == 0)
			return CMD_SUCCESS;

		XFREE(MTYPE_ROUTE_MAP_NAME, PROTO_RM_NAME(zvrf, afi, rtype));
	}
	route_map_counter_decrement(PROTO_RM_MAP(zvrf, afi, rtype));
	PROTO_RM_NAME(zvrf, afi, rtype) = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap);
	PROTO_RM_MAP(zvrf, afi, rtype) =
		route_map_lookup_by_name(PROTO_RM_NAME(zvrf, afi, rtype));
	route_map_counter_increment(PROTO_RM_MAP(zvrf, afi, rtype));

	if (PROTO_RM_NAME(zvrf, afi, rtype)) {
		if (IS_ZEBRA_DEBUG_RIB_DETAILED)
			zlog_debug(
				"%u: IPv4 Routemap config for protocol %d scheduling RIB processing",
				zvrf->vrf->vrf_id, rtype);
		/* Process routes of interested address-families. */
		table = zebra_vrf_table(afi, safi, zvrf->vrf->vrf_id);
		if (table)
			rib_update_table(table, RIB_UPDATE_RMAP_CHANGE,
					 rtype);
	}

	return CMD_SUCCESS;
}

int ip_protocol_rm_del(struct zebra_vrf *zvrf, const char *rmap, int rtype,
		       afi_t afi, safi_t safi)
{
	struct route_table *table;

	if (!PROTO_RM_NAME(zvrf, afi, rtype))
		return CMD_SUCCESS;

	if (!rmap || strcmp(rmap, PROTO_RM_NAME(zvrf, afi, rtype)) == 0) {

		route_map_counter_decrement(PROTO_RM_MAP(zvrf, afi, rtype));
		if (PROTO_RM_NAME(zvrf, afi, rtype)) {
			if (IS_ZEBRA_DEBUG_RIB_DETAILED)
				zlog_debug(
					"%u: IPv4 Routemap unconfig for protocol %d, scheduling RIB processing",
					zvrf->vrf->vrf_id, rtype);
			PROTO_RM_MAP(zvrf, afi, rtype) = NULL;

			/* Process routes of interested address-families. */
			table = zebra_vrf_table(afi, safi, zvrf->vrf->vrf_id);
			if (table)
				rib_update_table(table, RIB_UPDATE_RMAP_CHANGE,
						 rtype);
		}
		XFREE(MTYPE_ROUTE_MAP_NAME, PROTO_RM_NAME(zvrf, afi, rtype));
	}
	return CMD_SUCCESS;
}

int ip_nht_rm_add(struct zebra_vrf *zvrf, const char *rmap, int rtype, int afi)
{

	if (NHT_RM_NAME(zvrf, afi, rtype)) {
		if (strcmp(NHT_RM_NAME(zvrf, afi, rtype), rmap) == 0)
			return CMD_SUCCESS;

		XFREE(MTYPE_ROUTE_MAP_NAME, NHT_RM_NAME(zvrf, afi, rtype));
	}
	route_map_counter_decrement(NHT_RM_MAP(zvrf, afi, rtype));
	NHT_RM_NAME(zvrf, afi, rtype) = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap);
	NHT_RM_MAP(zvrf, afi, rtype) =
		route_map_lookup_by_name(NHT_RM_NAME(zvrf, afi, rtype));
	route_map_counter_increment(NHT_RM_MAP(zvrf, afi, rtype));

	if (NHT_RM_MAP(zvrf, afi, rtype))
		zebra_evaluate_rnh(zvrf, afi, 1, NULL, SAFI_UNICAST);

	return CMD_SUCCESS;
}

int ip_nht_rm_del(struct zebra_vrf *zvrf, const char *rmap, int rtype, int afi)
{

	if (!NHT_RM_NAME(zvrf, afi, rtype))
		return CMD_SUCCESS;

	if (!rmap || strcmp(rmap, NHT_RM_NAME(zvrf, afi, rtype)) == 0) {
		route_map_counter_decrement(NHT_RM_MAP(zvrf, afi, rtype));
		if (NHT_RM_MAP(zvrf, afi, rtype)) {
			if (IS_ZEBRA_DEBUG_RIB_DETAILED)
				zlog_debug(
					"%u: IPv4 Routemap unconfig for protocol %d, scheduling RIB processing",
					zvrf->vrf->vrf_id, rtype);
			NHT_RM_MAP(zvrf, afi, rtype) = NULL;

			zebra_evaluate_rnh(zvrf, afi, 1, NULL, SAFI_UNICAST);
		}
		XFREE(MTYPE_ROUTE_MAP_NAME, NHT_RM_NAME(zvrf, afi, rtype));
	}
	return CMD_SUCCESS;
}

DEFPY (show_ip_protocol,
       show_ip_protocol_cmd,
       "show ip protocol [vrf <NAME$vrf_name|all$vrf_all>]",
       SHOW_STR
       IP_STR
       "IP protocol filtering status\n"
       VRF_FULL_CMD_HELP_STR)
{
	int ret = show_proto_rm(vty, AFI_IP, vrf_all, vrf_name);

	return ret;
}

DEFPY (show_ipv6_protocol,
       show_ipv6_protocol_cmd,
       "show ipv6 protocol [vrf <NAME$vrf_name|all$vrf_all>]",
       SHOW_STR
       IP6_STR
       "IPv6 protocol filtering status\n"
       VRF_FULL_CMD_HELP_STR)
{
	int ret = show_proto_rm(vty, AFI_IP6, vrf_all, vrf_name);

	return ret;
}

DEFPY (show_ip_protocol_nht,
       show_ip_protocol_nht_cmd,
       "show ip nht route-map [vrf <NAME$vrf_name|all$vrf_all>] [json]",
       SHOW_STR
       IP_STR
       "IPv4 nexthop tracking table\n"
       "IPv4 Next Hop tracking filtering status\n"
       VRF_CMD_HELP_STR
       "All VRFs\n"
       JSON_STR)
{
	int ret;
	bool uj = use_json(argc, argv);

	ret = show_nht_rm(vty, AFI_IP, vrf_all, vrf_name, uj);

	return ret;
}

DEFPY (show_ipv6_protocol_nht,
       show_ipv6_protocol_nht_cmd,
       "show ipv6 nht route-map [vrf <NAME$vrf_name|all$vrf_all>] [json]",
       SHOW_STR
       IP6_STR
       "IPv6 nexthop tracking table\n"
       "IPv6 Next Hop tracking filtering status\n"
       VRF_CMD_HELP_STR
       "All VRFs\n"
       JSON_STR)
{
	int ret;
	bool uj = use_json(argc, argv);

	ret = show_nht_rm(vty, AFI_IP6, vrf_all, vrf_name, uj);

	return ret;
}

/*XXXXXXXXXXXXXXXXXXXXXXXXXXXX*/

/* `match ip next-hop IP_ACCESS_LIST' */

/* Match function return 1 if match is success else return zero. */
static enum route_map_cmd_result_t
route_match_ip_next_hop(void *rule, const struct prefix *prefix, void *object)
{
	struct access_list *alist;
	struct zebra_rmap_obj *rm_data;
	struct prefix_ipv4 p;

	rm_data = object;
	if (!rm_data)
		return RMAP_NOMATCH;

	switch (rm_data->nexthop->type) {
	case NEXTHOP_TYPE_IFINDEX:
		/* Interface routes can't match ip next-hop */
		return RMAP_NOMATCH;
	case NEXTHOP_TYPE_IPV4_IFINDEX:
	case NEXTHOP_TYPE_IPV4:
		p.family = AF_INET;
		p.prefix = rm_data->nexthop->gate.ipv4;
		p.prefixlen = IPV4_MAX_BITLEN;
		break;
	case NEXTHOP_TYPE_IPV6:
	case NEXTHOP_TYPE_IPV6_IFINDEX:
	case NEXTHOP_TYPE_BLACKHOLE:
		return RMAP_NOMATCH;
	}
	alist = access_list_lookup(AFI_IP, (char *)rule);
	if (alist == NULL) {
		if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL)))
			zlog_debug(
				"%s: Access-List Specified: %s does not exist defaulting to NO_MATCH",
				__func__, (char *)rule);
		return RMAP_NOMATCH;
	}

	return (access_list_apply(alist, &p) == FILTER_DENY ? RMAP_NOMATCH
							    : RMAP_MATCH);
}

/* Route map `ip next-hop' match statement.  `arg' should be
   access-list name. */
static void *route_match_ip_next_hop_compile(const char *arg)
{
	return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
}

/* Free route map's compiled `. */
static void route_match_ip_next_hop_free(void *rule)
{
	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}

/* Route map commands for ip next-hop matching. */
static const struct route_map_rule_cmd route_match_ip_next_hop_cmd = {
	"ip next-hop",
	route_match_ip_next_hop,
	route_match_ip_next_hop_compile,
	route_match_ip_next_hop_free
};

/* `match ip next-hop prefix-list PREFIX_LIST' */

static enum route_map_cmd_result_t
route_match_ip_next_hop_prefix_list(void *rule, const struct prefix *prefix,
				    void *object)
{
	struct prefix_list *plist;
	struct zebra_rmap_obj *rm_data;
	struct prefix_ipv4 p;

	rm_data = (struct zebra_rmap_obj *)object;
	if (!rm_data)
		return RMAP_NOMATCH;

	switch (rm_data->nexthop->type) {
	case NEXTHOP_TYPE_IFINDEX:
		/* Interface routes can't match ip next-hop */
		return RMAP_NOMATCH;
	case NEXTHOP_TYPE_IPV4_IFINDEX:
	case NEXTHOP_TYPE_IPV4:
		p.family = AF_INET;
		p.prefix = rm_data->nexthop->gate.ipv4;
		p.prefixlen = IPV4_MAX_BITLEN;
		break;
	case NEXTHOP_TYPE_IPV6:
	case NEXTHOP_TYPE_IPV6_IFINDEX:
	case NEXTHOP_TYPE_BLACKHOLE:
		return RMAP_NOMATCH;
	}
	plist = prefix_list_lookup(AFI_IP, (char *)rule);
	if (plist == NULL) {
		if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL)))
			zlog_debug(
				"%s: Prefix List %s specified does not exist defaulting to NO_MATCH",
				__func__, (char *)rule);
		return RMAP_NOMATCH;
	}

	return (prefix_list_apply(plist, &p) == PREFIX_DENY ? RMAP_NOMATCH
							    : RMAP_MATCH);
}

static void *route_match_ip_next_hop_prefix_list_compile(const char *arg)
{
	return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
}

static void route_match_ip_next_hop_prefix_list_free(void *rule)
{
	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}

static const struct route_map_rule_cmd
		route_match_ip_next_hop_prefix_list_cmd = {
	"ip next-hop prefix-list",
	route_match_ip_next_hop_prefix_list,
	route_match_ip_next_hop_prefix_list_compile,
	route_match_ip_next_hop_prefix_list_free
};

/* `match ip address IP_ACCESS_LIST' */

/* Match function should return 1 if match is success else return
   zero. */
static enum route_map_cmd_result_t
route_match_address(afi_t afi, void *rule, const struct prefix *prefix,
		    void *object)
{
	struct access_list *alist;

	alist = access_list_lookup(afi, (char *)rule);
	if (alist == NULL) {
		if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL)))
			zlog_debug(
				"%s: Access-List Specified: %s does not exist defaulting to NO_MATCH",
				__func__, (char *)rule);
		return RMAP_NOMATCH;
	}

	return (access_list_apply(alist, prefix) == FILTER_DENY ? RMAP_NOMATCH
								: RMAP_MATCH);
}

static enum route_map_cmd_result_t
route_match_ip_address(void *rule, const struct prefix *prefix, void *object)
{
	return route_match_address(AFI_IP, rule, prefix, object);
}

static enum route_map_cmd_result_t
route_match_ipv6_address(void *rule, const struct prefix *prefix, void *object)
{
	return route_match_address(AFI_IP6, rule, prefix, object);
}

/* Route map `ip address' match statement.  `arg' should be
   access-list name. */
static void *route_match_address_compile(const char *arg)
{
	return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
}

/* Free route map's compiled `ip address' value. */
static void route_match_address_free(void *rule)
{
	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}

/* Route map commands for ip address matching. */
static const struct route_map_rule_cmd route_match_ip_address_cmd = {
	"ip address",
	route_match_ip_address,
	route_match_address_compile,
	route_match_address_free
};

/* Route map commands for ipv6 address matching. */
static const struct route_map_rule_cmd route_match_ipv6_address_cmd = {
	"ipv6 address",
	route_match_ipv6_address,
	route_match_address_compile,
	route_match_address_free
};

/* `match ip address prefix-list PREFIX_LIST' */

static enum route_map_cmd_result_t
route_match_address_prefix_list(void *rule, const struct prefix *prefix,
				void *object, afi_t afi)
{
	struct prefix_list *plist;

	plist = prefix_list_lookup(afi, (char *)rule);
	if (plist == NULL) {
		if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL)))
			zlog_debug("%s: Prefix List %s (%s) specified does not exist defaulting to NO_MATCH",
				   __func__, (char *)rule, afi2str(afi));
		return RMAP_NOMATCH;
	}

	return (prefix_list_apply(plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH
								: RMAP_MATCH);
}

static enum route_map_cmd_result_t
route_match_ip_address_prefix_list(void *rule, const struct prefix *prefix,
				   void *object)
{
	return (route_match_address_prefix_list(rule, prefix, object, AFI_IP));
}

static void *route_match_address_prefix_list_compile(const char *arg)
{
	return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
}

static void route_match_address_prefix_list_free(void *rule)
{
	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}

static const struct route_map_rule_cmd
		route_match_ip_address_prefix_list_cmd = {
	"ip address prefix-list",
	route_match_ip_address_prefix_list,
	route_match_address_prefix_list_compile,
	route_match_address_prefix_list_free
};

static enum route_map_cmd_result_t
route_match_ipv6_address_prefix_list(void *rule, const struct prefix *prefix,
				     void *object)
{
	return (route_match_address_prefix_list(rule, prefix, object, AFI_IP6));
}

static const struct route_map_rule_cmd
		route_match_ipv6_address_prefix_list_cmd = {
	"ipv6 address prefix-list",
	route_match_ipv6_address_prefix_list,
	route_match_address_prefix_list_compile,
	route_match_address_prefix_list_free
};

/* `match ipv6 next-hop type <TYPE>' */

static enum route_map_cmd_result_t
route_match_ipv6_next_hop_type(void *rule, const struct prefix *prefix,
			       void *object)
{
	struct zebra_rmap_obj *rm_data;

	if (prefix->family == AF_INET6) {
		rm_data = (struct zebra_rmap_obj *)object;
		if (!rm_data)
			return RMAP_NOMATCH;

		if (rm_data->nexthop->type == NEXTHOP_TYPE_BLACKHOLE)
			return RMAP_MATCH;
	}

	return RMAP_NOMATCH;
}

static void *route_match_ipv6_next_hop_type_compile(const char *arg)
{
	return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
}

static void route_match_ipv6_next_hop_type_free(void *rule)
{
	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}

static const struct route_map_rule_cmd
		route_match_ipv6_next_hop_type_cmd = {
	"ipv6 next-hop type",
	route_match_ipv6_next_hop_type,
	route_match_ipv6_next_hop_type_compile,
	route_match_ipv6_next_hop_type_free
};

/* `match ip address prefix-len PREFIXLEN' */

static enum route_map_cmd_result_t
route_match_address_prefix_len(void *rule, const struct prefix *prefix,
			       void *object)
{
	uint32_t *prefixlen = (uint32_t *)rule;

	return ((prefix->prefixlen == *prefixlen) ? RMAP_MATCH : RMAP_NOMATCH);
}

static void *route_match_address_prefix_len_compile(const char *arg)
{
	uint32_t *prefix_len;
	char *endptr = NULL;
	unsigned long tmpval;

	/* prefix len value shoud be integer. */
	if (!all_digit(arg))
		return NULL;

	errno = 0;
	tmpval = strtoul(arg, &endptr, 10);
	if (*endptr != '\0' || errno || tmpval > UINT32_MAX)
		return NULL;

	prefix_len = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t));

	*prefix_len = tmpval;
	return prefix_len;
}

static void route_match_address_prefix_len_free(void *rule)
{
	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}

static const struct route_map_rule_cmd
		route_match_ip_address_prefix_len_cmd = {
	"ip address prefix-len",
	route_match_address_prefix_len,
	route_match_address_prefix_len_compile,
	route_match_address_prefix_len_free
};

static const struct route_map_rule_cmd
		route_match_ipv6_address_prefix_len_cmd = {
	"ipv6 address prefix-len",
	route_match_address_prefix_len,
	route_match_address_prefix_len_compile,
	route_match_address_prefix_len_free
};

/* `match ip nexthop prefix-len PREFIXLEN' */

static enum route_map_cmd_result_t
route_match_ip_nexthop_prefix_len(void *rule, const struct prefix *prefix,
				  void *object)
{
	uint32_t *prefixlen = (uint32_t *)rule;
	struct zebra_rmap_obj *rm_data;
	struct prefix_ipv4 p;

	rm_data = (struct zebra_rmap_obj *)object;
	if (!rm_data || !rm_data->nexthop)
		return RMAP_NOMATCH;

	switch (rm_data->nexthop->type) {
	case NEXTHOP_TYPE_IFINDEX:
		/* Interface routes can't match ip next-hop */
		return RMAP_NOMATCH;
	case NEXTHOP_TYPE_IPV4_IFINDEX:
	case NEXTHOP_TYPE_IPV4:
		p.family = AF_INET;
		p.prefix = rm_data->nexthop->gate.ipv4;
		p.prefixlen = IPV4_MAX_BITLEN;
		break;
	case NEXTHOP_TYPE_IPV6:
	case NEXTHOP_TYPE_IPV6_IFINDEX:
	case NEXTHOP_TYPE_BLACKHOLE:
		return RMAP_NOMATCH;
	}
	return ((p.prefixlen == *prefixlen) ? RMAP_MATCH : RMAP_NOMATCH);
}

static const struct route_map_rule_cmd
		route_match_ip_nexthop_prefix_len_cmd = {
	"ip next-hop prefix-len",
	route_match_ip_nexthop_prefix_len,
	route_match_address_prefix_len_compile, /* reuse */
	route_match_address_prefix_len_free     /* reuse */
};

/* `match ip next-hop type <blackhole>' */

static enum route_map_cmd_result_t
route_match_ip_next_hop_type(void *rule, const struct prefix *prefix,
			     void *object)
{
	struct zebra_rmap_obj *rm_data;

	if (prefix->family == AF_INET) {
		rm_data = (struct zebra_rmap_obj *)object;
		if (!rm_data)
			return RMAP_NOMATCH;

		if (rm_data->nexthop->type == NEXTHOP_TYPE_BLACKHOLE)
			return RMAP_MATCH;
	}

	return RMAP_NOMATCH;
}

static void *route_match_ip_next_hop_type_compile(const char *arg)
{
	return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
}

static void route_match_ip_next_hop_type_free(void *rule)
{
	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}

static const struct route_map_rule_cmd
		route_match_ip_next_hop_type_cmd = {
	"ip next-hop type",
	route_match_ip_next_hop_type,
	route_match_ip_next_hop_type_compile,
	route_match_ip_next_hop_type_free
};

/* `match source-protocol PROTOCOL' */

static enum route_map_cmd_result_t
route_match_source_protocol(void *rule, const struct prefix *p, void *object)
{
	int32_t *rib_type = (int32_t *)rule;
	struct zebra_rmap_obj *rm_data;

	rm_data = (struct zebra_rmap_obj *)object;
	if (!rm_data)
		return RMAP_NOMATCH;

	return ((rm_data->re->type == *rib_type) ? RMAP_MATCH : RMAP_NOMATCH);
}

static void *route_match_source_protocol_compile(const char *arg)
{
	uint32_t *rib_type;
	int i;

	i = proto_name2num(arg);
	rib_type = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t));

	*rib_type = i;

	return rib_type;
}

static void route_match_source_protocol_free(void *rule)
{
	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}

static const struct route_map_rule_cmd route_match_source_protocol_cmd = {
	"source-protocol",
	route_match_source_protocol,
	route_match_source_protocol_compile,
	route_match_source_protocol_free
};

/* `source-instance` */
static enum route_map_cmd_result_t
route_match_source_instance(void *rule, const struct prefix *p, void *object)
{
	uint8_t *instance = (uint8_t *)rule;
	struct zebra_rmap_obj *rm_data;

	rm_data = (struct zebra_rmap_obj *)object;
	if (!rm_data)
		return RMAP_NOMATCH;

	return (rm_data->re->instance == *instance) ? RMAP_MATCH : RMAP_NOMATCH;
}

static void *route_match_source_instance_compile(const char *arg)
{
	uint8_t *instance;
	int i;

	i = atoi(arg);
	instance = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint8_t));

	*instance = i;

	return instance;
}

static void route_match_source_instance_free(void *rule)
{
	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}

static const struct route_map_rule_cmd route_match_source_instance_cmd = {
	"source-instance",
	route_match_source_instance,
	route_match_source_instance_compile,
	route_match_source_instance_free
};

/* `set src A.B.C.D' */

/* Set src. */
static enum route_map_cmd_result_t
route_set_src(void *rule, const struct prefix *prefix, void *object)
{
	struct zebra_rmap_obj *rm_data;

	rm_data = (struct zebra_rmap_obj *)object;
	rm_data->nexthop->rmap_src = *(union g_addr *)rule;

	return RMAP_OKAY;
}

/* set src compilation. */
static void *route_set_src_compile(const char *arg)
{
	union g_addr src = {}, *psrc;

	/* IPv4 first, to ensure no garbage in the 12 unused bytes */
	if ((inet_pton(AF_INET, arg, &src.ipv4) == 1) ||
	    (inet_pton(AF_INET6, arg, &src.ipv6) == 1)) {
		psrc = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(union g_addr));
		*psrc = src;
		return psrc;
	}
	return NULL;
}

/* Free route map's compiled `set src' value. */
static void route_set_src_free(void *rule)
{
	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}

/* Set src rule structure. */
static const struct route_map_rule_cmd route_set_src_cmd = {
	"src",
	route_set_src,
	route_set_src_compile,
	route_set_src_free,
};

/* The function checks if the changed routemap specified by parameter rmap
 * matches the configured protocol routemaps in proto_rm table. If there is
 * a match then rib_update_table() to process the routes.
 */
static void zebra_rib_table_rm_update(const char *rmap)
{
	int i = 0;
	struct route_table *table;
	struct vrf *vrf = NULL;
	struct zebra_vrf *zvrf = NULL;
	char *rmap_name;
	struct route_map *old = NULL;

	RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
		zvrf = vrf->info;
		if (!zvrf)
			continue;
		for (i = 0; i <= ZEBRA_ROUTE_MAX; i++) {
			rmap_name = PROTO_RM_NAME(zvrf, AFI_IP, i);
			if (rmap_name && (strcmp(rmap_name, rmap) == 0)) {
				if (IS_ZEBRA_DEBUG_EVENT)
					zlog_debug(
						"%s : AFI_IP rmap %s, route type %s",
						__func__, rmap,
						zebra_route_string(i));

				old = PROTO_RM_MAP(zvrf, AFI_IP, i);

				PROTO_RM_MAP(zvrf, AFI_IP, i) =
					route_map_lookup_by_name(rmap_name);
				/* old is NULL. i.e Route map creation event.
				 * So update applied_counter.
				 * If Old is not NULL, i.e It may be routemap
				 * updation or deletion.
				 * So no need to update the counter.
				 */
				if (!old)
					route_map_counter_increment(
						PROTO_RM_MAP(zvrf, AFI_IP, i));
				/* There is single rib table for all protocols
				 */
				table = zvrf->table[AFI_IP][SAFI_UNICAST];
				if (table) {
					rib_update_table(
						table,
						RIB_UPDATE_RMAP_CHANGE,
						i);
				}
			}
			rmap_name = PROTO_RM_NAME(zvrf, AFI_IP6, i);
			if (rmap_name && (strcmp(rmap_name, rmap) == 0)) {
				if (IS_ZEBRA_DEBUG_EVENT)
					zlog_debug(
						"%s : AFI_IP6 rmap %s, route type %s",
						__func__, rmap,
						zebra_route_string(i));

				old = PROTO_RM_MAP(zvrf, AFI_IP6, i);

				PROTO_RM_MAP(zvrf, AFI_IP6, i) =
					route_map_lookup_by_name(rmap_name);
				if (!old)
					route_map_counter_increment(
						PROTO_RM_MAP(zvrf, AFI_IP6, i));
				/* There is single rib table for all protocols
				 */
				table = zvrf->table[AFI_IP6][SAFI_UNICAST];
				if (table) {
					rib_update_table(
						table,
						RIB_UPDATE_RMAP_CHANGE,
						i);
				}
			}
		}
	}
}

/* The function checks if the changed routemap specified by parameter rmap
 * matches the configured protocol routemaps in nht_rm table. If there is
 * a match then zebra_evaluate_rnh() to process the nexthops.
 */
static void zebra_nht_rm_update(const char *rmap)
{
	int i = 0;
	struct route_table *table;
	struct vrf *vrf = NULL;
	struct zebra_vrf *zvrf = NULL;
	char *rmap_name;
	char afi_ip = 0;
	char afi_ipv6 = 0;
	struct route_map *old = NULL;

	RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
		zvrf = vrf->info;
		if (!zvrf)
			continue;
		for (i = 0; i <= ZEBRA_ROUTE_MAX; i++) {
			rmap_name = NHT_RM_NAME(zvrf, AFI_IP, i);
			if (rmap_name && (strcmp(rmap_name, rmap) == 0)) {
				if (IS_ZEBRA_DEBUG_EVENT)
					zlog_debug(
						"%s : AFI_IP rmap %s, route type %s",
						__func__, rmap,
						zebra_route_string(i));

				old = NHT_RM_MAP(zvrf, AFI_IP, i);

				NHT_RM_MAP(zvrf, AFI_IP, i) =
					route_map_lookup_by_name(rmap_name);
				if (!old)
					route_map_counter_increment(
						NHT_RM_MAP(zvrf, AFI_IP, i));
				/* There is single rib table for all protocols
				 */
				if (afi_ip == 0) {
					table = zvrf->table[AFI_IP]
							   [SAFI_UNICAST];
					if (table) {

						afi_ip = 1;

						zebra_evaluate_rnh(
							zvrf, AFI_IP, 1, NULL,
							SAFI_UNICAST);
					}
				}
			}

			rmap_name = NHT_RM_NAME(zvrf, AFI_IP6, i);
			if (rmap_name && (strcmp(rmap_name, rmap) == 0)) {
				if (IS_ZEBRA_DEBUG_EVENT)
					zlog_debug(
						"%s : AFI_IP6 rmap %s, route type %s",
						__func__, rmap,
						zebra_route_string(i));

				old = NHT_RM_MAP(zvrf, AFI_IP6, i);

				NHT_RM_MAP(zvrf, AFI_IP6, i) =
					route_map_lookup_by_name(rmap_name);
				if (!old)
					route_map_counter_increment(
						NHT_RM_MAP(zvrf, AFI_IP6, i));
				/* There is single rib table for all protocols
				 */
				if (afi_ipv6 == 0) {
					table = zvrf->table[AFI_IP6]
							   [SAFI_UNICAST];
					if (table) {

						afi_ipv6 = 1;

						zebra_evaluate_rnh(
							zvrf, AFI_IP6, 1, NULL,
							SAFI_UNICAST);
					}
				}
			}
		}
	}
}

static void zebra_route_map_process_update_cb(char *rmap_name)
{
	if (IS_ZEBRA_DEBUG_EVENT)
		zlog_debug("Event handler for route-map: %s",
			   rmap_name);
	zebra_import_table_rm_update(rmap_name);
	zebra_rib_table_rm_update(rmap_name);
	zebra_nht_rm_update(rmap_name);
}

static void zebra_route_map_update_timer(struct event *thread)
{
	if (IS_ZEBRA_DEBUG_EVENT)
		zlog_debug("Event driven route-map update triggered");

	if (IS_ZEBRA_DEBUG_RIB_DETAILED)
		zlog_debug(
			"%u: Routemap update-timer fired, scheduling RIB processing",
			VRF_DEFAULT);

	route_map_walk_update_list(zebra_route_map_process_update_cb);

	/*
	 * This code needs to be updated to be:
	 * 1) VRF Aware <sigh>
	 * 2) Route-map aware
	 */
}

void zebra_route_map_set_delay_timer(uint32_t value)
{
	zebra_rmap_update_timer = value;
	if (!value && zebra_t_rmap_update) {
		/* Event driven route map updates is being disabled */
		/* But there's a pending timer. Fire it off now */
		EVENT_OFF(zebra_t_rmap_update);
		zebra_route_map_update_timer(NULL);
	}
}

void zebra_routemap_finish(void)
{
	/* Set zebra_rmap_update_timer to 0 so that it wont schedule again */
	zebra_rmap_update_timer = 0;
	/* Thread off if any scheduled already */
	EVENT_OFF(zebra_t_rmap_update);
	route_map_finish();
}

route_map_result_t zebra_route_map_check(afi_t family, struct route_entry *re,
					 const struct prefix *p,
					 struct nexthop *nexthop,
					 struct zebra_vrf *zvrf)
{
	struct route_map *rmap = NULL;
	char *rm_name;
	route_map_result_t ret = RMAP_PERMITMATCH;
	struct zebra_rmap_obj rm_obj;

	rm_obj.nexthop = nexthop;
	rm_obj.re = re;

	if (re->type >= 0 && re->type < ZEBRA_ROUTE_MAX) {
		rm_name = PROTO_RM_NAME(zvrf, family, re->type);
		rmap = PROTO_RM_MAP(zvrf, family, re->type);

		if (rm_name && !rmap)
			return RMAP_DENYMATCH;
	}
	if (!rmap) {
		rm_name = PROTO_RM_NAME(zvrf, family, ZEBRA_ROUTE_ALL);
		rmap = PROTO_RM_MAP(zvrf, family, ZEBRA_ROUTE_ALL);

		if (rm_name && !rmap)
			return RMAP_DENYMATCH;
	}
	if (rmap) {
		ret = route_map_apply(rmap, p, &rm_obj);
	}

	return (ret);
}

char *zebra_get_import_table_route_map(afi_t afi, safi_t safi, uint32_t table)
{
	return zebra_import_table_routemap[afi][safi][table];
}

void zebra_add_import_table_route_map(afi_t afi, safi_t safi, const char *rmap_name, uint32_t table)
{
	zebra_import_table_routemap[afi][safi][table] = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap_name);
}

void zebra_del_import_table_route_map(afi_t afi, safi_t safi, uint32_t table)
{
	XFREE(MTYPE_ROUTE_MAP_NAME, zebra_import_table_routemap[afi][safi][table]);
}

route_map_result_t zebra_import_table_route_map_check(int family,
						      struct route_entry *re,
						      const struct prefix *p,
						      struct nexthop *nexthop,
						      const char *rmap_name)
{
	struct route_map *rmap = NULL;
	route_map_result_t ret = RMAP_DENYMATCH;
	struct zebra_rmap_obj rm_obj;

	rm_obj.nexthop = nexthop;
	rm_obj.re = re;

	if (re->type >= 0 && re->type < ZEBRA_ROUTE_MAX)
		rmap = route_map_lookup_by_name(rmap_name);
	if (rmap) {
		ret = route_map_apply(rmap, p, &rm_obj);
	}

	return (ret);
}

route_map_result_t zebra_nht_route_map_check(afi_t afi, int client_proto,
					     const struct prefix *p,
					     struct zebra_vrf *zvrf,
					     struct route_entry *re,
					     struct nexthop *nexthop)
{
	struct route_map *rmap = NULL;
	route_map_result_t ret = RMAP_PERMITMATCH;
	struct zebra_rmap_obj rm_obj;

	rm_obj.nexthop = nexthop;
	rm_obj.re = re;

	if (client_proto >= 0 && client_proto < ZEBRA_ROUTE_MAX)
		rmap = NHT_RM_MAP(zvrf, afi, client_proto);
	if (!rmap && NHT_RM_MAP(zvrf, afi, ZEBRA_ROUTE_MAX))
		rmap = NHT_RM_MAP(zvrf, afi, ZEBRA_ROUTE_MAX);
	if (rmap)
		ret = route_map_apply(rmap, p, &rm_obj);

	return ret;
}

static void zebra_route_map_mark_update(const char *rmap_name)
{
	/* rmap_update_timer of 0 means don't do route updates */
	if (zebra_rmap_update_timer)
		EVENT_OFF(zebra_t_rmap_update);

	event_add_timer(zrouter.master, zebra_route_map_update_timer, NULL,
			zebra_rmap_update_timer, &zebra_t_rmap_update);
}

static void zebra_route_map_add(const char *rmap_name)
{
	if (route_map_mark_updated(rmap_name) == 0)
		zebra_route_map_mark_update(rmap_name);

	route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_ADDED);
}

static void zebra_route_map_delete(const char *rmap_name)
{
	if (route_map_mark_updated(rmap_name) == 0)
		zebra_route_map_mark_update(rmap_name);

	route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_DELETED);
}

static void zebra_route_map_event(const char *rmap_name)
{
	if (route_map_mark_updated(rmap_name) == 0)
		zebra_route_map_mark_update(rmap_name);

	route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_ADDED);
}

void zebra_routemap_vrf_delete(struct zebra_vrf *zvrf)
{
	afi_t afi;
	uint8_t type;

	for (afi = AFI_IP; afi < AFI_MAX; afi++) {
		for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) {
			if (PROTO_RM_NAME(zvrf, afi, type))
				XFREE(MTYPE_ROUTE_MAP_NAME,
				      PROTO_RM_NAME(zvrf, afi, type));
			if (NHT_RM_NAME(zvrf, afi, type))
				XFREE(MTYPE_ROUTE_MAP_NAME,
				      NHT_RM_NAME(zvrf, afi, type));
		}
	}
}

void zebra_route_map_init(void)
{
	install_element(VIEW_NODE, &show_ip_protocol_cmd);
	install_element(VIEW_NODE, &show_ipv6_protocol_cmd);
	install_element(VIEW_NODE, &show_ip_protocol_nht_cmd);
	install_element(VIEW_NODE, &show_ipv6_protocol_nht_cmd);

	route_map_init_new(true);

	route_map_add_hook(zebra_route_map_add);
	route_map_delete_hook(zebra_route_map_delete);
	route_map_event_hook(zebra_route_map_event);

	route_map_match_interface_hook(generic_match_add);
	route_map_no_match_interface_hook(generic_match_delete);

	route_map_match_ip_address_hook(generic_match_add);
	route_map_no_match_ip_address_hook(generic_match_delete);

	route_map_match_ip_address_prefix_list_hook(generic_match_add);
	route_map_no_match_ip_address_prefix_list_hook(generic_match_delete);

	route_map_match_ip_next_hop_hook(generic_match_add);
	route_map_no_match_ip_next_hop_hook(generic_match_delete);

	route_map_match_ip_next_hop_prefix_list_hook(generic_match_add);
	route_map_no_match_ip_next_hop_prefix_list_hook(generic_match_delete);

	route_map_match_ip_next_hop_type_hook(generic_match_add);
	route_map_no_match_ip_next_hop_type_hook(generic_match_delete);

	route_map_match_tag_hook(generic_match_add);
	route_map_no_match_tag_hook(generic_match_delete);

	route_map_match_ipv6_address_hook(generic_match_add);
	route_map_no_match_ipv6_address_hook(generic_match_delete);

	route_map_match_ipv6_address_prefix_list_hook(generic_match_add);
	route_map_no_match_ipv6_address_prefix_list_hook(generic_match_delete);

	route_map_match_ipv6_next_hop_type_hook(generic_match_add);
	route_map_no_match_ipv6_next_hop_type_hook(generic_match_delete);

	route_map_install_match(&route_match_tag_cmd);
	route_map_install_match(&route_match_interface_cmd);
	route_map_install_match(&route_match_ip_next_hop_cmd);
	route_map_install_match(&route_match_ip_next_hop_prefix_list_cmd);
	route_map_install_match(&route_match_ip_address_cmd);
	route_map_install_match(&route_match_ipv6_address_cmd);
	route_map_install_match(&route_match_ip_address_prefix_list_cmd);
	route_map_install_match(&route_match_ipv6_address_prefix_list_cmd);
	route_map_install_match(&route_match_ip_address_prefix_len_cmd);
	route_map_install_match(&route_match_ipv6_address_prefix_len_cmd);
	route_map_install_match(&route_match_ip_nexthop_prefix_len_cmd);
	route_map_install_match(&route_match_ip_next_hop_type_cmd);
	route_map_install_match(&route_match_ipv6_next_hop_type_cmd);
	route_map_install_match(&route_match_source_protocol_cmd);
	route_map_install_match(&route_match_source_instance_cmd);

	/* */
	route_map_install_set(&route_set_src_cmd);
}
