// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2003 Yasuhiro Ohara
 */

#include <zebra.h>

#include "log.h"
#include "memory.h"
#include "prefix.h"
#include "command.h"
#include "vty.h"
#include "routemap.h"
#include "table.h"
#include "plist.h"
#include "frrevent.h"
#include "frrstr.h"
#include "linklist.h"
#include "lib/northbound_cli.h"

#include "ospf6_proto.h"
#include "ospf6_lsa.h"
#include "ospf6_lsdb.h"
#include "ospf6_route.h"
#include "ospf6_zebra.h"
#include "ospf6_message.h"
#include "ospf6_spf.h"

#include "ospf6_top.h"
#include "ospf6d.h"
#include "ospf6_area.h"
#include "ospf6_interface.h"
#include "ospf6_neighbor.h"
#include "ospf6_asbr.h"
#include "ospf6_abr.h"
#include "ospf6_intra.h"
#include "ospf6_flood.h"
#include "ospf6_nssa.h"
#include "ospf6d.h"
#include "ospf6_spf.h"
#include "ospf6_nssa.h"
#include "ospf6_tlv.h"
#include "ospf6_gr.h"
#include "lib/json.h"

DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_EXTERNAL_INFO, "OSPF6 ext. info");
DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_DIST_ARGS,     "OSPF6 Distribute arguments");
DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_REDISTRIBUTE, "OSPF6 Redistribute arguments");
DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_EXTERNAL_RT_AGGR, "OSPF6 ASBR Summarisation");

static void ospf6_asbr_redistribute_set(struct ospf6 *ospf6, int type);
static void ospf6_asbr_redistribute_unset(struct ospf6 *ospf6,
					  struct ospf6_redist *red, int type);

#include "ospf6d/ospf6_asbr_clippy.c"

unsigned char conf_debug_ospf6_asbr = 0;

#define ZROUTE_NAME(x) zebra_route_string(x)

/* Originate Type-5 and Type-7 LSA */
static struct ospf6_lsa *ospf6_originate_type5_type7_lsas(
						struct ospf6_route *route,
						struct ospf6 *ospf6)
{
	struct ospf6_lsa *lsa;
	struct listnode *lnode;
	struct ospf6_area *oa = NULL;

	lsa = ospf6_as_external_lsa_originate(route, ospf6);

	for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, lnode, oa)) {
		if (IS_AREA_NSSA(oa))
			ospf6_nssa_lsa_originate(route, oa, true);
	}

	return lsa;
}

/* AS External LSA origination */
struct ospf6_lsa *ospf6_as_external_lsa_originate(struct ospf6_route *route,
					    struct ospf6 *ospf6)
{
	char buffer[OSPF6_MAX_LSASIZE];
	struct ospf6_lsa_header *lsa_header;
	struct ospf6_lsa *lsa;
	struct ospf6_external_info *info = route->route_option;

	struct ospf6_as_external_lsa *as_external_lsa;
	caddr_t p;

	if (ospf6->gr_info.restart_in_progress) {
		if (IS_DEBUG_OSPF6_GR)
			zlog_debug(
				"Graceful Restart in progress, don't originate LSA");
		return NULL;
	}

	if (IS_OSPF6_DEBUG_ASBR || IS_OSPF6_DEBUG_ORIGINATE(AS_EXTERNAL))
		zlog_debug("Originate AS-External-LSA for %pFX",
			   &route->prefix);

	/* prepare buffer */
	memset(buffer, 0, sizeof(buffer));
	lsa_header = (struct ospf6_lsa_header *)buffer;
	as_external_lsa = lsa_after_header(lsa_header);
	p = (caddr_t)((caddr_t)as_external_lsa
		      + sizeof(struct ospf6_as_external_lsa));

	/* Fill AS-External-LSA */
	/* Metric type */
	if (route->path.metric_type == 2)
		SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E);
	else
		UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E);

	/* forwarding address */
	if (!IN6_IS_ADDR_UNSPECIFIED(&info->forwarding))
		SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F);
	else
		UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F);

	/* external route tag */
	if (info->tag)
		SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T);
	else
		UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T);

	/* Set metric */
	OSPF6_ASBR_METRIC_SET(as_external_lsa, route->path.cost);

	/* prefixlen */
	as_external_lsa->prefix.prefix_length = route->prefix.prefixlen;

	/* PrefixOptions */
	as_external_lsa->prefix.prefix_options = route->prefix_options;

	/* don't use refer LS-type */
	as_external_lsa->prefix.prefix_refer_lstype = htons(0);

	/* set Prefix */
	memcpy(p, &route->prefix.u.prefix6,
	       OSPF6_PREFIX_SPACE(route->prefix.prefixlen));
	ospf6_prefix_apply_mask(&as_external_lsa->prefix);
	p += OSPF6_PREFIX_SPACE(route->prefix.prefixlen);

	/* Forwarding address */
	if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F)) {
		memcpy(p, &info->forwarding, sizeof(struct in6_addr));
		p += sizeof(struct in6_addr);
	}

	/* External Route Tag */
	if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T)) {
		route_tag_t network_order = htonl(info->tag);

		memcpy(p, &network_order, sizeof(network_order));
		p += sizeof(network_order);
	}

	/* Fill LSA Header */
	lsa_header->age = 0;
	lsa_header->type = htons(OSPF6_LSTYPE_AS_EXTERNAL);
	lsa_header->id = route->path.origin.id;
	lsa_header->adv_router = ospf6->router_id;
	lsa_header->seqnum =
		ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id,
				    lsa_header->adv_router, ospf6->lsdb);
	lsa_header->length = htons((caddr_t)p - (caddr_t)lsa_header);

	/* LSA checksum */
	ospf6_lsa_checksum(lsa_header);

	/* create LSA */
	lsa = ospf6_lsa_create(lsa_header);

	/* Originate */
	ospf6_lsa_originate_process(lsa, ospf6);

	return lsa;
}

void ospf6_orig_as_external_lsa(struct event *thread)
{
	struct ospf6_interface *oi;
	struct ospf6_lsa *lsa;
	uint32_t type, adv_router;

	oi = (struct ospf6_interface *)EVENT_ARG(thread);

	if (oi->state == OSPF6_INTERFACE_DOWN)
		return;
	if (IS_AREA_NSSA(oi->area) || IS_AREA_STUB(oi->area))
		return;

	type = htons(OSPF6_LSTYPE_AS_EXTERNAL);
	adv_router = oi->area->ospf6->router_id;
	for (ALL_LSDB_TYPED_ADVRTR(oi->area->ospf6->lsdb, type, adv_router,
				   lsa)) {
		if (IS_OSPF6_DEBUG_ASBR)
			zlog_debug(
				"%s: Send update of AS-External LSA %s seq 0x%x",
				__func__, lsa->name,
				ntohl(lsa->header->seqnum));

		ospf6_flood_interface(NULL, lsa, oi);
	}
}

static route_tag_t ospf6_as_external_lsa_get_tag(struct ospf6_lsa *lsa)
{
	struct ospf6_as_external_lsa *external;
	ptrdiff_t tag_offset;
	route_tag_t network_order;

	if (!lsa)
		return 0;

	external = lsa_after_header(lsa->header);

	if (!CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T))
		return 0;

	tag_offset = sizeof(*external)
		     + OSPF6_PREFIX_SPACE(external->prefix.prefix_length);
	if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F))
		tag_offset += sizeof(struct in6_addr);

	memcpy(&network_order, (caddr_t)external + tag_offset,
	       sizeof(network_order));
	return ntohl(network_order);
}

void ospf6_asbr_update_route_ecmp_path(struct ospf6_route *old,
				       struct ospf6_route *route,
				       struct ospf6 *ospf6)
{
	struct ospf6_route *old_route, *next_route;
	struct ospf6_path *ecmp_path, *o_path = NULL;
	struct listnode *anode, *anext;
	struct listnode *nnode, *rnode, *rnext;
	struct ospf6_nexthop *nh, *rnh;
	bool route_found = false;

	/* check for old entry match with new route origin,
	 * delete old entry.
	 */
	for (old_route = old; old_route; old_route = next_route) {
		bool route_updated = false;

		next_route = old_route->next;

		/* The route linked-list is grouped in batches of prefix.
		 * If the new prefix is not the same as the one of interest
		 * then we have walked over the end of the batch and so we
		 * should break rather than continuing unnecessarily.
		 */
		if (!ospf6_route_is_same(old_route, route))
			break;
		if (old_route->path.type != route->path.type)
			continue;

		/* Current and New route has same origin,
		 * delete old entry.
		 */
		for (ALL_LIST_ELEMENTS(old_route->paths, anode, anext,
				       o_path)) {
			/* Check old route path and route has same
			 * origin.
			 */
			if (o_path->area_id != route->path.area_id
			    || !ospf6_ls_origin_same(o_path, &route->path))
				continue;

			/* Cost is not same then delete current path */
			if ((o_path->cost == route->path.cost)
			    && (o_path->u.cost_e2 == route->path.u.cost_e2))
				continue;

			if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) {
				zlog_debug(
					"%s: route %pFX cost old %u new %u is not same, replace route",
					__func__, &old_route->prefix, o_path->cost,
					route->path.cost);
			}

			/* Remove selected current rout path's nh from
			 * effective nh list.
			 */
			for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) {
				for (ALL_LIST_ELEMENTS(old_route->nh_list,
						       rnode, rnext, rnh)) {
					if (!ospf6_nexthop_is_same(rnh, nh))
						continue;
					listnode_delete(old_route->nh_list,
							rnh);
					ospf6_nexthop_delete(rnh);
				}
			}

			listnode_delete(old_route->paths, o_path);
			ospf6_path_free(o_path);
			route_updated = true;

			/* Current route's path (adv_router info) is similar
			 * to route being added.
			 * Replace current route's path with paths list head.
			 * Update FIB with effective NHs.
			 */
			if (listcount(old_route->paths)) {
				for (ALL_LIST_ELEMENTS(old_route->paths,
						anode, anext, o_path)) {
					ospf6_merge_nexthops(
						old_route->nh_list,
						o_path->nh_list);
				}
				/* Update RIB/FIB with effective
				 * nh_list
				 */
				if (ospf6->route_table->hook_add)
					(*ospf6->route_table->hook_add)(
						old_route);

				if (old_route->path.origin.id
					    == route->path.origin.id
				    && old_route->path.origin.adv_router
					       == route->path.origin
							  .adv_router) {
					struct ospf6_path *h_path;

					h_path = (struct ospf6_path *)
						listgetdata(listhead(
							old_route->paths));
					old_route->path.origin.type =
						h_path->origin.type;
					old_route->path.origin.id =
						h_path->origin.id;
					old_route->path.origin.adv_router =
						h_path->origin.adv_router;
				}
			} else {
				if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) {
					zlog_debug(
						"%s: route %pFX old cost %u new cost %u, delete old entry.",
						__func__, &old_route->prefix,
						old_route->path.cost,
						route->path.cost);
				}
				if (old == old_route)
					old = next_route;
				ospf6_route_remove(old_route,
						   ospf6->route_table);
			}
		}
		if (route_updated)
			break;
	}

	/* Add new route */
	for (old_route = old; old_route; old_route = old_route->next) {

		/* The route linked-list is grouped in batches of prefix.
		 * If the new prefix is not the same as the one of interest
		 * then we have walked over the end of the batch and so we
		 * should break rather than continuing unnecessarily.
		 */
		if (!ospf6_route_is_same(old_route, route))
			break;
		if (old_route->path.type != route->path.type)
			continue;

		/* Old Route and New Route have Equal Cost, Merge NHs */
		if ((old_route->path.cost == route->path.cost)
		    && (old_route->path.u.cost_e2 == route->path.u.cost_e2)) {

			if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) {
				zlog_debug(
					"%s: old route %pFX path  cost %u e2 %u",
					__func__, &old_route->prefix,
					old_route->path.cost,
					old_route->path.u.cost_e2);
			}
			route_found = true;
			/* check if this path exists already in
			 * route->paths list, if so, replace nh_list
			 * from asbr_entry.
			 */
			for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode,
						  o_path)) {
				if (o_path->area_id == route->path.area_id
				    && ospf6_ls_origin_same(o_path, &route->path))
					break;
			}
			/* If path is not found in old_route paths's list,
			 * add a new path to route paths list and merge
			 * nexthops in route->path->nh_list.
			 * Otherwise replace existing path's nh_list.
			 */
			if (o_path == NULL) {
				ecmp_path = ospf6_path_dup(&route->path);

				/* Add a nh_list to new ecmp path */
				ospf6_copy_nexthops(ecmp_path->nh_list,
						    route->nh_list);

				/* Add the new path to route's path list */
				listnode_add_sort(old_route->paths, ecmp_path);

				if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) {
					zlog_debug(
						"%s: route %pFX another path added with nh %u, effective paths %u nh %u",
						__func__, &route->prefix,
						listcount(ecmp_path->nh_list),
						old_route->paths ? listcount(
							old_route->paths)
								 : 0,
						listcount(old_route->nh_list));
				}
			} else {
				list_delete_all_node(o_path->nh_list);
				ospf6_copy_nexthops(o_path->nh_list,
						    route->nh_list);
			}

			/* Reset nexthop lists, rebuild from brouter table
			 * for each adv. router.
			 */
			list_delete_all_node(old_route->nh_list);

			for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode,
						  o_path)) {
				struct ospf6_route *asbr_entry;

				asbr_entry = ospf6_route_lookup(
							&o_path->ls_prefix,
							ospf6->brouter_table);
				if (asbr_entry == NULL) {
					if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
						zlog_debug(
							"%s: ls_prfix %pFX asbr_entry not found.",
							__func__,
							&old_route->prefix);
					continue;
				}
				ospf6_route_merge_nexthops(old_route,
							   asbr_entry);
			}

			if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
				zlog_debug(
					"%s: route %pFX with effective paths %u nh %u",
					__func__, &route->prefix,
					old_route->paths
						? listcount(old_route->paths)
						: 0,
					old_route->nh_list
						? listcount(old_route->nh_list)
						: 0);

			/* Update RIB/FIB */
			if (ospf6->route_table->hook_add)
				(*ospf6->route_table->hook_add)(old_route);

			/* Delete the new route its info added to existing
			 * route.
			 */
			ospf6_route_delete(route);

			break;
		}
	}

	if (!route_found) {
		/* Add new route to existing node in ospf6 route table. */
		ospf6_route_add(route, ospf6->route_table);
	}
}

/* Check if the forwarding address is local address */
static int ospf6_ase_forward_address_check(struct ospf6 *ospf6,
					   struct in6_addr *fwd_addr)
{
	struct listnode *anode, *node;
	struct ospf6_interface *oi;
	struct ospf6_area *oa;
	struct interface *ifp;
	struct connected *c;

	for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, anode, oa)) {
		for (ALL_LIST_ELEMENTS_RO(oa->if_list, node, oi)) {
			if (!if_is_operative(oi->interface)
			    || oi->type == OSPF_IFTYPE_VIRTUALLINK)
				continue;

			ifp = oi->interface;
			frr_each (if_connected, ifp->connected, c) {
				if (IPV6_ADDR_SAME(&c->address->u.prefix6,
						   fwd_addr))
					return 0;
			}
		}
	}

	return 1;
}

void ospf6_asbr_lsa_add(struct ospf6_lsa *lsa)
{
	struct ospf6_as_external_lsa *external;
	struct prefix asbr_id;
	struct ospf6_route *asbr_entry, *route, *old = NULL;
	struct ospf6_path *path;
	struct ospf6 *ospf6;
	int type;
	struct ospf6_area *oa = NULL;
	struct prefix fwd_addr;
	ptrdiff_t offset;

	type = ntohs(lsa->header->type);
	oa = lsa->lsdb->data;

	external = lsa_after_header(lsa->header);

	if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
		zlog_debug("Calculate AS-External route for %s", lsa->name);

	ospf6 = ospf6_get_by_lsdb(lsa);

	if (lsa->header->adv_router == ospf6->router_id) {
		if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
			zlog_debug("Ignore self-originated AS-External-LSA");
		return;
	}

	if (OSPF6_ASBR_METRIC(external) == OSPF_LS_INFINITY) {
		if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
			zlog_debug("Ignore LSA with LSInfinity Metric");
		return;
	}

	if (CHECK_FLAG(external->prefix.prefix_options,
		       OSPF6_PREFIX_OPTION_NU)) {
		if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
			zlog_debug("Ignore LSA with NU bit set Metric");
		return;
	}

	ospf6_linkstate_prefix(lsa->header->adv_router, htonl(0), &asbr_id);
	asbr_entry = ospf6_route_lookup(&asbr_id, ospf6->brouter_table);
	if (asbr_entry == NULL) {
		if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
			zlog_debug("ASBR entry not found: %pFX", &asbr_id);
		return;
	} else {
		/* The router advertising external LSA can be ASBR or ABR */
		if (!CHECK_FLAG(asbr_entry->path.router_bits,
				OSPF6_ROUTER_BIT_E)) {
			if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
				zlog_debug(
					"External bit reset ASBR route entry : %pFX",
					&asbr_id);
			return;
		}

		/*
		 * RFC 3101 - Section 2.5:
		 * "For a Type-7 LSA the matching routing table entry must
		 * specify an intra-area path through the LSA's originating
		 * NSSA".
		 */
		if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7
		    && (asbr_entry->path.area_id != oa->area_id
			|| asbr_entry->path.type != OSPF6_PATH_TYPE_INTRA)) {
			if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
				zlog_debug(
					"Intra-area route to NSSA ASBR not found: %pFX",
					&asbr_id);
			return;
		}
	}

	/*
	 * RFC 3101 - Section 2.5:
	 * "If the destination is a Type-7 default route (destination ID =
	 * DefaultDestination) and one of the following is true, then do
	 * nothing with this LSA and consider the next in the list:
	 *
	 *  o  The calculating router is a border router and the LSA has
	 *     its P-bit clear.  Appendix E describes a technique
	 *     whereby an NSSA border router installs a Type-7 default
	 *     LSA without propagating it.
	 *
	 *  o  The calculating router is a border router and is
	 *     suppressing the import of summary routes as Type-3
	 *     summary-LSAs".
	 */
	if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7
	    && external->prefix.prefix_length == 0
	    && CHECK_FLAG(ospf6->flag, OSPF6_FLAG_ABR)
	    && (CHECK_FLAG(external->prefix.prefix_options,
			   OSPF6_PREFIX_OPTION_P)
		|| oa->no_summary)) {
		if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
			zlog_debug("Skipping Type-7 default route");
		return;
	}

	/* Check the forwarding address */
	if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) {
		offset = sizeof(*external)
			 + OSPF6_PREFIX_SPACE(external->prefix.prefix_length);
		memset(&fwd_addr, 0, sizeof(fwd_addr));
		fwd_addr.family = AF_INET6;
		fwd_addr.prefixlen = IPV6_MAX_BITLEN;
		memcpy(&fwd_addr.u.prefix6, (caddr_t)external + offset,
		       sizeof(struct in6_addr));

		if (!IN6_IS_ADDR_UNSPECIFIED(&fwd_addr.u.prefix6)) {
			if (!ospf6_ase_forward_address_check(
				    ospf6, &fwd_addr.u.prefix6)) {
				if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
					zlog_debug(
						"Fwd address %pFX is local address",
						&fwd_addr);
				return;
			}

			/* Find the forwarding entry */
			asbr_entry = ospf6_route_lookup_bestmatch(
				&fwd_addr, ospf6->route_table);
			if (asbr_entry == NULL) {
				if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
					zlog_debug(
						"Fwd address not found: %pFX",
						&fwd_addr);
				return;
			}
		}
	}

	route = ospf6_route_create(ospf6);
	route->type = OSPF6_DEST_TYPE_NETWORK;
	route->prefix.family = AF_INET6;
	route->prefix.prefixlen = external->prefix.prefix_length;
	ospf6_prefix_in6_addr(&route->prefix.u.prefix6, external,
			      &external->prefix);
	route->prefix_options = external->prefix.prefix_options;

	route->path.area_id = asbr_entry->path.area_id;
	route->path.origin.type = lsa->header->type;
	route->path.origin.id = lsa->header->id;
	route->path.origin.adv_router = lsa->header->adv_router;
	memcpy(&route->path.ls_prefix, &asbr_id, sizeof(struct prefix));

	if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E)) {
		route->path.type = OSPF6_PATH_TYPE_EXTERNAL2;
		route->path.metric_type = 2;
		route->path.cost = asbr_entry->path.cost;
		route->path.u.cost_e2 = OSPF6_ASBR_METRIC(external);
	} else {
		route->path.type = OSPF6_PATH_TYPE_EXTERNAL1;
		route->path.metric_type = 1;
		route->path.cost =
			asbr_entry->path.cost + OSPF6_ASBR_METRIC(external);
		route->path.u.cost_e2 = 0;
	}

	route->path.tag = ospf6_as_external_lsa_get_tag(lsa);

	ospf6_route_copy_nexthops(route, asbr_entry);

	path = ospf6_path_dup(&route->path);
	ospf6_copy_nexthops(path->nh_list, asbr_entry->nh_list);
	listnode_add_sort(route->paths, path);


	if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
		zlog_debug(
			"%s: %s %u route add %pFX cost %u(%u) nh %u", __func__,
			(type == OSPF6_LSTYPE_AS_EXTERNAL) ? "AS-External"
							   : "NSSA",
			(route->path.type == OSPF6_PATH_TYPE_EXTERNAL1) ? 1 : 2,
			&route->prefix, route->path.cost, route->path.u.cost_e2,
			listcount(route->nh_list));

	if (type == OSPF6_LSTYPE_AS_EXTERNAL)
		old = ospf6_route_lookup(&route->prefix, ospf6->route_table);
	else if (type == OSPF6_LSTYPE_TYPE_7)
		old = ospf6_route_lookup(&route->prefix, oa->route_table);
	if (!old) {
		if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
			zlog_debug("%s: Adding new route", __func__);
		/* Add the new route to ospf6 instance route table. */
		if (type == OSPF6_LSTYPE_AS_EXTERNAL)
			ospf6_route_add(route, ospf6->route_table);
		/* Add the route to the area route table */
		else if (type == OSPF6_LSTYPE_TYPE_7) {
			ospf6_route_add(route, oa->route_table);
		}
	} else {
		/* RFC 2328 16.4 (6)
		 * ECMP: Keep new equal preference path in current
		 * route's path list, update zebra with new effective
		 * list along with addition of ECMP path.
		 */
		if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
			zlog_debug("%s : old route %pFX cost %u(%u) nh %u",
				   __func__, &route->prefix, route->path.cost,
				   route->path.u.cost_e2,
				   listcount(route->nh_list));
		ospf6_asbr_update_route_ecmp_path(old, route, ospf6);
	}
}

void ospf6_asbr_lsa_remove(struct ospf6_lsa *lsa,
			   struct ospf6_route *asbr_entry)
{
	struct ospf6_as_external_lsa *external;
	struct prefix prefix;
	struct ospf6_route *route, *nroute, *route_to_del;
	struct ospf6_area *oa = NULL;
	struct ospf6 *ospf6;
	int type;
	bool debug = false;

	external = lsa_after_header(lsa->header);

	if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL) || (IS_OSPF6_DEBUG_NSSA))
		debug = true;

	ospf6 = ospf6_get_by_lsdb(lsa);
	type = ntohs(lsa->header->type);

	if (type == OSPF6_LSTYPE_TYPE_7) {
		if (debug)
			zlog_debug("%s: Withdraw  Type 7 route for %s",
				   __func__, lsa->name);
		oa = lsa->lsdb->data;
	} else {
		if (debug)
			zlog_debug("%s: Withdraw AS-External route for %s",
				   __func__, lsa->name);

		if (ospf6_check_and_set_router_abr(ospf6))
			oa = ospf6->backbone;
		else
			oa = listnode_head(ospf6->area_list);
	}

	if (oa == NULL) {
		if (debug)
			zlog_debug("%s: Invalid area", __func__);
		return;
	}

	if (lsa->header->adv_router == oa->ospf6->router_id) {
		if (debug)
			zlog_debug("Ignore self-originated AS-External-LSA");
		return;
	}

	route_to_del = ospf6_route_create(ospf6);
	route_to_del->type = OSPF6_DEST_TYPE_NETWORK;
	route_to_del->prefix.family = AF_INET6;
	route_to_del->prefix.prefixlen = external->prefix.prefix_length;
	ospf6_prefix_in6_addr(&route_to_del->prefix.u.prefix6, external,
			      &external->prefix);

	route_to_del->path.origin.type = lsa->header->type;
	route_to_del->path.origin.id = lsa->header->id;
	route_to_del->path.origin.adv_router = lsa->header->adv_router;

	if (asbr_entry) {
		route_to_del->path.area_id = asbr_entry->path.area_id;
		if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E)) {
			route_to_del->path.type = OSPF6_PATH_TYPE_EXTERNAL2;
			route_to_del->path.metric_type = 2;
			route_to_del->path.cost = asbr_entry->path.cost;
			route_to_del->path.u.cost_e2 =
				OSPF6_ASBR_METRIC(external);
		} else {
			route_to_del->path.type = OSPF6_PATH_TYPE_EXTERNAL1;
			route_to_del->path.metric_type = 1;
			route_to_del->path.cost = asbr_entry->path.cost
						  + OSPF6_ASBR_METRIC(external);
			route_to_del->path.u.cost_e2 = 0;
		}
	}

	memset(&prefix, 0, sizeof(struct prefix));
	prefix.family = AF_INET6;
	prefix.prefixlen = external->prefix.prefix_length;
	ospf6_prefix_in6_addr(&prefix.u.prefix6, external, &external->prefix);

	if (type == OSPF6_LSTYPE_TYPE_7)
		route = ospf6_route_lookup(&prefix, oa->route_table);
	else
		route = ospf6_route_lookup(&prefix, oa->ospf6->route_table);

	if (route == NULL) {
		if (debug)
			zlog_debug("AS-External route %pFX not found", &prefix);
		ospf6_route_delete(route_to_del);
		return;
	}

	if (debug)
		zlog_debug(
			"%s: Current route %pFX cost %u e2 %u, route to del cost %u e2 %u",
			__func__, &prefix, route->path.cost, route->path.u.cost_e2,
			route_to_del->path.cost, route_to_del->path.u.cost_e2);

	for (ospf6_route_lock(route);
	     route && ospf6_route_is_prefix(&prefix, route); route = nroute) {
		nroute = ospf6_route_next(route);

		if (route->type != OSPF6_DEST_TYPE_NETWORK)
			continue;

		/* Route has multiple ECMP paths, remove matching
		 * path. Update current route's effective nh list
		 * after removal of one of the path.
		 */
		if (listcount(route->paths) > 1) {
			struct listnode *anode, *anext;
			struct listnode *nnode, *rnode, *rnext;
			struct ospf6_nexthop *nh, *rnh;
			struct ospf6_path *o_path;
			bool nh_updated = false;

			/* Iterate all paths of route to find maching with LSA
			 * remove from route path list. If route->path is same,
			 * replace from paths list.
			 */
			for (ALL_LIST_ELEMENTS(route->paths, anode, anext,
					       o_path)) {
				if ((o_path->origin.type != lsa->header->type)
				    || (o_path->origin.adv_router
					!= lsa->header->adv_router)
				    || (o_path->origin.id != lsa->header->id))
					continue;

				/* Compare LSA cost with current
				 * route info.
				 */
				if (asbr_entry
				    && (o_path->cost != route_to_del->path.cost
					|| o_path->u.cost_e2
						   != route_to_del->path.u
							      .cost_e2)) {
					if (IS_OSPF6_DEBUG_EXAMIN(
						    AS_EXTERNAL)) {
						zlog_debug(
							"%s: route %pFX to delete is not same, cost %u del cost %u. skip",
							__func__, &prefix,
							route->path.cost,
							route_to_del->path
								.cost);
					}
					continue;
				}

				if (debug) {
					zlog_debug(
						"%s: route %pFX path found with cost %u nh %u to remove.",
						__func__, &prefix, route->path.cost,
						listcount(o_path->nh_list));
				}

				/* Remove found path's nh_list from
				 * the route's nh_list.
				 */
				for (ALL_LIST_ELEMENTS_RO(o_path->nh_list,
							  nnode, nh)) {
					for (ALL_LIST_ELEMENTS(route->nh_list,
							       rnode, rnext,
							       rnh)) {
						if (!ospf6_nexthop_is_same(rnh,
									   nh))
							continue;
						listnode_delete(route->nh_list,
								rnh);
						ospf6_nexthop_delete(rnh);
					}
				}
				/* Delete the path from route's path list */
				listnode_delete(route->paths, o_path);
				ospf6_path_free(o_path);
				nh_updated = true;
			}

			if (nh_updated) {
				/* Iterate all paths and merge nexthop,
				 * unlesss any of the nexthop similar to
				 * ones deleted as part of path deletion.
				 */

				for (ALL_LIST_ELEMENTS(route->paths, anode,
						       anext, o_path)) {
					ospf6_merge_nexthops(route->nh_list,
							     o_path->nh_list);
				}

				if (debug) {
					zlog_debug(
						"%s: AS-External %u route %pFX update paths %u nh %u",
						__func__,
						(route->path.type
						 == OSPF6_PATH_TYPE_EXTERNAL1)
							? 1
							: 2,
						&route->prefix, listcount(route->paths),
						route->nh_list ? listcount(
							route->nh_list)
							       : 0);
				}

				if (listcount(route->paths)) {
					/* Update RIB/FIB with effective
					 * nh_list
					 */
					if (oa->ospf6->route_table->hook_add)
						(*oa->ospf6->route_table
							  ->hook_add)(route);

					/* route's primary path is similar
					 * to LSA, replace route's primary
					 * path with route's paths list head.
					 */
					if ((route->path.origin.id ==
					    lsa->header->id) &&
					    (route->path.origin.adv_router
						 == lsa->header->adv_router)) {
						struct ospf6_path *h_path;

						h_path = (struct ospf6_path *)
						listgetdata(
							listhead(route->paths));
						route->path.origin.type =
							h_path->origin.type;
						route->path.origin.id =
							h_path->origin.id;
						route->path.origin.adv_router =
						h_path->origin.adv_router;
					}
				} else {
					if (type == OSPF6_LSTYPE_TYPE_7)
						ospf6_route_remove(
							route, oa->route_table);
					else
						ospf6_route_remove(
							route,
							oa->ospf6->route_table);
				}
			}
			continue;

		} else {
			/* Compare LSA origin and cost with current route info.
			 * if any check fails skip del this route node.
			 */
			if (asbr_entry
			    && (!ospf6_route_is_same_origin(route, route_to_del)
				|| (route->path.type != route_to_del->path.type)
				|| (route->path.cost != route_to_del->path.cost)
				|| (route->path.u.cost_e2
				    != route_to_del->path.u.cost_e2))) {
				if (debug) {
					zlog_debug(
						"%s: route %pFX to delete is not same, cost %u del cost %u. skip",
						__func__, &prefix, route->path.cost,
						route_to_del->path.cost);
				}
				continue;
			}

			if ((route->path.origin.type != lsa->header->type)
			    || (route->path.origin.adv_router
				!= lsa->header->adv_router)
			    || (route->path.origin.id != lsa->header->id))
				continue;
		}
		if (debug) {
			zlog_debug(
				"%s: AS-External %u route remove %pFX cost %u(%u) nh %u",
				__func__,
				route->path.type == OSPF6_PATH_TYPE_EXTERNAL1
					? 1
					: 2,
				&route->prefix, route->path.cost, route->path.u.cost_e2,
				listcount(route->nh_list));
		}
		if (type == OSPF6_LSTYPE_TYPE_7)
			ospf6_route_remove(route, oa->route_table);
		else
			ospf6_route_remove(route, oa->ospf6->route_table);
	}
	if (route != NULL)
		ospf6_route_unlock(route);

	ospf6_route_delete(route_to_del);
}

void ospf6_asbr_lsentry_add(struct ospf6_route *asbr_entry, struct ospf6 *ospf6)
{
	struct ospf6_lsa *lsa;
	uint16_t type;
	uint32_t router;

	if (!CHECK_FLAG(asbr_entry->flag, OSPF6_ROUTE_BEST)) {
		char buf[16];
		inet_ntop(AF_INET, &ADV_ROUTER_IN_PREFIX(&asbr_entry->prefix),
			  buf, sizeof(buf));
		zlog_info("ignore non-best path: lsentry %s add", buf);
		return;
	}

	type = htons(OSPF6_LSTYPE_AS_EXTERNAL);
	router = ospf6_linkstate_prefix_adv_router(&asbr_entry->prefix);
	for (ALL_LSDB_TYPED_ADVRTR(ospf6->lsdb, type, router, lsa)) {
		if (!OSPF6_LSA_IS_MAXAGE(lsa))
			ospf6_asbr_lsa_add(lsa);
	}
}

void ospf6_asbr_lsentry_remove(struct ospf6_route *asbr_entry,
			       struct ospf6 *ospf6)
{
	struct ospf6_lsa *lsa;
	uint16_t type;
	uint32_t router;

	type = htons(OSPF6_LSTYPE_AS_EXTERNAL);
	router = ospf6_linkstate_prefix_adv_router(&asbr_entry->prefix);
	for (ALL_LSDB_TYPED_ADVRTR(ospf6->lsdb, type, router, lsa))
		ospf6_asbr_lsa_remove(lsa, asbr_entry);
}


/* redistribute function */
static void ospf6_asbr_routemap_set(struct ospf6_redist *red,
				    const char *mapname)
{
	if (ROUTEMAP_NAME(red)) {
		route_map_counter_decrement(ROUTEMAP(red));
		free(ROUTEMAP_NAME(red));
	}

	ROUTEMAP_NAME(red) = strdup(mapname);
	ROUTEMAP(red) = route_map_lookup_by_name(mapname);
	route_map_counter_increment(ROUTEMAP(red));
}

static void ospf6_asbr_routemap_unset(struct ospf6_redist *red)
{
	if (ROUTEMAP_NAME(red))
		free(ROUTEMAP_NAME(red));

	route_map_counter_decrement(ROUTEMAP(red));

	ROUTEMAP_NAME(red) = NULL;
	ROUTEMAP(red) = NULL;
}

static void ospf6_asbr_routemap_update_timer(struct event *thread)
{
	struct ospf6 *ospf6 = EVENT_ARG(thread);
	struct ospf6_redist *red;
	int type;

	for (type = 0; type < ZEBRA_ROUTE_MAX; type++) {
		red = ospf6_redist_lookup(ospf6, type, 0);

		if (!red)
			continue;

		if (!CHECK_FLAG(red->flag, OSPF6_IS_RMAP_CHANGED))
			continue;

		if (ROUTEMAP_NAME(red))
			ROUTEMAP(red) =
				route_map_lookup_by_name(ROUTEMAP_NAME(red));

		if (ROUTEMAP(red)) {
			if (IS_OSPF6_DEBUG_ASBR)
				zlog_debug(
					"%s: route-map %s update, reset redist %s",
					__func__, ROUTEMAP_NAME(red),
					ZROUTE_NAME(type));

			ospf6_zebra_no_redistribute(type, ospf6->vrf_id);
			ospf6_zebra_redistribute(type, ospf6->vrf_id);
		}

		UNSET_FLAG(red->flag, OSPF6_IS_RMAP_CHANGED);
	}
}

void ospf6_asbr_distribute_list_update(struct ospf6 *ospf6,
				       struct ospf6_redist *red)
{
	SET_FLAG(red->flag, OSPF6_IS_RMAP_CHANGED);

	if (event_is_scheduled(ospf6->t_distribute_update))
		return;

	if (IS_OSPF6_DEBUG_ASBR)
		zlog_debug("%s: trigger redistribute reset thread", __func__);

	event_add_timer_msec(master, ospf6_asbr_routemap_update_timer, ospf6,
			     OSPF_MIN_LS_INTERVAL, &ospf6->t_distribute_update);
}

void ospf6_asbr_routemap_update(const char *mapname)
{
	int type;
	struct listnode *node, *nnode;
	struct ospf6 *ospf6 = NULL;
	struct ospf6_redist *red;

	if (om6 == NULL)
		return;

	for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) {
		for (type = 0; type < ZEBRA_ROUTE_MAX; type++) {
			red = ospf6_redist_lookup(ospf6, type, 0);
			if (!red || (ROUTEMAP_NAME(red) == NULL))
				continue;
			ROUTEMAP(red) =
				route_map_lookup_by_name(ROUTEMAP_NAME(red));

			if (mapname == NULL
			    || strcmp(ROUTEMAP_NAME(red), mapname))
				continue;
			if (ROUTEMAP(red)) {
				if (IS_OSPF6_DEBUG_ASBR)
					zlog_debug(
							"%s: route-map %s update, reset redist %s",
							__func__,
							mapname,
							ZROUTE_NAME(
								type));

				route_map_counter_increment(ROUTEMAP(red));
				ospf6_asbr_distribute_list_update(ospf6, red);
			} else {
				/*
				* if the mapname matches a
				* route-map on ospf6 but the
				* map doesn't exist, it is
				* being deleted. flush and then
				* readvertise
				*/
				if (IS_OSPF6_DEBUG_ASBR)
					zlog_debug(
							"%s: route-map %s deleted, reset redist %s",
							__func__,
							mapname,
							ZROUTE_NAME(
								type));
				ospf6_asbr_redistribute_unset(ospf6, red, type);
				ospf6_asbr_routemap_set(red, mapname);
				ospf6_asbr_redistribute_set(ospf6, type);
			}
		}
	}
}

static void ospf6_asbr_routemap_event(const char *name)
{
	int type;
	struct listnode *node, *nnode;
	struct ospf6 *ospf6;
	struct ospf6_redist *red;

	if (om6 == NULL)
		return;
	for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) {
		for (type = 0; type < ZEBRA_ROUTE_MAX; type++) {
			red = ospf6_redist_lookup(ospf6, type, 0);
			if (red && ROUTEMAP_NAME(red)
			    && (strcmp(ROUTEMAP_NAME(red), name) == 0))
				ospf6_asbr_distribute_list_update(ospf6, red);
		}
	}
}

int ospf6_asbr_is_asbr(struct ospf6 *o)
{
	return (o->external_table->count || IS_OSPF6_ASBR(o));
}

struct ospf6_redist *ospf6_redist_lookup(struct ospf6 *ospf6, int type,
					 unsigned short instance)
{
	struct list *red_list;
	struct listnode *node;
	struct ospf6_redist *red;

	red_list = ospf6->redist[type];
	if (!red_list)
		return (NULL);

	for (ALL_LIST_ELEMENTS_RO(red_list, node, red))
		if (red->instance == instance)
			return red;

	return NULL;
}

static struct ospf6_redist *ospf6_redist_add(struct ospf6 *ospf6, int type,
					     uint8_t instance)
{
	struct ospf6_redist *red;

	red = ospf6_redist_lookup(ospf6, type, instance);
	if (red)
		return red;

	if (!ospf6->redist[type])
		ospf6->redist[type] = list_new();

	red = XCALLOC(MTYPE_OSPF6_REDISTRIBUTE, sizeof(struct ospf6_redist));
	red->instance = instance;
	red->dmetric.type = -1;
	red->dmetric.value = -1;
	ROUTEMAP_NAME(red) = NULL;
	ROUTEMAP(red) = NULL;

	listnode_add(ospf6->redist[type], red);
	ospf6->redistribute++;

	return red;
}

static void ospf6_redist_del(struct ospf6 *ospf6, struct ospf6_redist *red,
			     int type)
{
	if (red) {
		listnode_delete(ospf6->redist[type], red);
		if (!ospf6->redist[type]->count) {
			list_delete(&ospf6->redist[type]);
		}
		XFREE(MTYPE_OSPF6_REDISTRIBUTE, red);
		ospf6->redistribute--;
	}
}

/*Set the status of the ospf instance to ASBR based on the status parameter,
 * rechedule SPF calculation, originate router LSA*/
void ospf6_asbr_status_update(struct ospf6 *ospf6, int status)
{
	struct listnode *lnode, *lnnode;
	struct ospf6_area *oa;

	zlog_info("ASBR[%s:Status:%d]: Update", ospf6->name, status);

	if (status) {
		if (IS_OSPF6_ASBR(ospf6)) {
			zlog_info("ASBR[%s:Status:%d]: Already ASBR",
				  ospf6->name, status);
			return;
		}
		SET_FLAG(ospf6->flag, OSPF6_FLAG_ASBR);
	} else {
		if (!IS_OSPF6_ASBR(ospf6)) {
			zlog_info("ASBR[%s:Status:%d]: Already non ASBR",
				  ospf6->name, status);
			return;
		}
		UNSET_FLAG(ospf6->flag, OSPF6_FLAG_ASBR);
	}

	/* Transition from/to status ASBR, schedule timer. */
	ospf6_spf_schedule(ospf6, OSPF6_SPF_FLAGS_ASBR_STATUS_CHANGE);

	/* Reoriginate router LSA for all areas */
	for (ALL_LIST_ELEMENTS(ospf6->area_list, lnode, lnnode, oa))
		OSPF6_ROUTER_LSA_SCHEDULE(oa);
}

static void ospf6_asbr_redistribute_set(struct ospf6 *ospf6, int type)
{
	ospf6_zebra_redistribute(type, ospf6->vrf_id);

	++ospf6->redist_count;
	ospf6_asbr_status_update(ospf6, ospf6->redist_count);
}

static void ospf6_asbr_redistribute_unset(struct ospf6 *ospf6,
					  struct ospf6_redist *red, int type)
{
	struct ospf6_route *route;
	struct ospf6_external_info *info;

	ospf6_zebra_no_redistribute(type, ospf6->vrf_id);

	for (route = ospf6_route_head(ospf6->external_table); route;
	     route = ospf6_route_next(route)) {
		info = route->route_option;
		if (info->type != type)
			continue;

		ospf6_asbr_redistribute_remove(info->type, 0, &route->prefix,
					       ospf6);
	}

	ospf6_asbr_routemap_unset(red);
	--ospf6->redist_count;
	ospf6_asbr_status_update(ospf6, ospf6->redist_count);
}

/* When an area is unstubified, flood all the external LSAs in the area */
void ospf6_asbr_send_externals_to_area(struct ospf6_area *oa)
{
	struct ospf6_lsa *lsa, *lsanext;

	for (ALL_LSDB(oa->ospf6->lsdb, lsa, lsanext)) {
		if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL) {
			if (IS_OSPF6_DEBUG_ASBR)
				zlog_debug("%s: Flooding AS-External LSA %s",
					   __func__, lsa->name);

			ospf6_flood_area(NULL, lsa, oa);
		}
	}
}

/* When an area is stubified, remove all the external LSAs in the area */
void ospf6_asbr_remove_externals_from_area(struct ospf6_area *oa)
{
	struct ospf6_lsa *lsa, *lsanext;
	struct listnode *node, *nnode;
	struct ospf6_area *area;
	struct ospf6 *ospf6 = oa->ospf6;
	const struct route_node *iterend;

	/* skip if router is in other non-stub/non-NSSA areas */
	for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, area))
		if (!IS_AREA_STUB(area) && !IS_AREA_NSSA(area))
			return;

	/* if router is only in a stub area then purge AS-External LSAs */
	iterend = ospf6_lsdb_head(ospf6->lsdb, 0, 0, 0, &lsa);
	while (lsa != NULL) {
		assert(lsa->lock > 1);
		lsanext = ospf6_lsdb_next(iterend, lsa);
		if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL)
			ospf6_lsdb_remove(lsa, ospf6->lsdb);
		lsa = lsanext;
	}
}

static struct ospf6_external_aggr_rt *
ospf6_external_aggr_match(struct ospf6 *ospf6, struct prefix *p)
{
	struct route_node *node;

	node = route_node_match(ospf6->rt_aggr_tbl, p);
	if (node == NULL)
		return NULL;

	if (IS_OSPF6_DEBUG_AGGR) {
		struct ospf6_external_aggr_rt *ag = node->info;
		zlog_debug("%s: Matching aggregator found.prefix: %pFX Aggregator %pFX",
			__func__,
			p,
			&ag->p);
	}

	route_unlock_node(node);

	return node->info;
}

static void ospf6_external_lsa_fwd_addr_set(struct ospf6 *ospf6,
					    const struct in6_addr *nexthop,
					    struct in6_addr *fwd_addr)
{
	struct vrf *vrf;
	struct interface *ifp;
	struct prefix nh;

	/* Initialize forwarding address to zero. */
	memset(fwd_addr, 0, sizeof(*fwd_addr));

	vrf = vrf_lookup_by_id(ospf6->vrf_id);
	if (!vrf)
		return;

	nh.family = AF_INET6;
	nh.u.prefix6 = *nexthop;
	nh.prefixlen = IPV6_MAX_BITLEN;

	/*
	 * Use the route's nexthop as the forwarding address if it meets the
	 * following conditions:
	 * - It's a global address.
	 * - The associated nexthop interface is OSPF-enabled.
	 */
	if (IN6_IS_ADDR_UNSPECIFIED(nexthop) || IN6_IS_ADDR_LINKLOCAL(nexthop))
		return;

	FOR_ALL_INTERFACES (vrf, ifp) {
		struct ospf6_interface *oi = ifp->info;
		struct connected *connected;

		if (!oi || CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE))
			continue;

		frr_each (if_connected, ifp->connected, connected) {
			if (connected->address->family != AF_INET6)
				continue;
			if (IN6_IS_ADDR_LINKLOCAL(&connected->address->u.prefix6))
				continue;
			if (!prefix_match(connected->address, &nh))
				continue;

			*fwd_addr = *nexthop;
			return;
		}
	}
}

void ospf6_asbr_redistribute_add(int type, ifindex_t ifindex,
				 struct prefix *prefix, unsigned int nexthop_num,
				 const struct in6_addr *nexthop, route_tag_t tag,
				 struct ospf6 *ospf6, uint32_t metric)
{
	route_map_result_t ret;
	struct ospf6_route troute;
	struct ospf6_external_info tinfo;
	struct ospf6_route *route, *match;
	struct ospf6_external_info *info;
	struct ospf6_redist *red;

	red = ospf6_redist_lookup(ospf6, type, 0);

	if (!red)
		return;

	if ((type != DEFAULT_ROUTE)
	    && !ospf6_zebra_is_redistribute(type, ospf6->vrf_id))
		return;

	memset(&troute, 0, sizeof(troute));
	memset(&tinfo, 0, sizeof(tinfo));

	if (IS_OSPF6_DEBUG_ASBR)
		zlog_debug("Redistribute %pFX (%s)", prefix,
			   type == DEFAULT_ROUTE
				   ? "default-information-originate"
				   : ZROUTE_NAME(type));

	/* if route-map was specified but not found, do not advertise */
	if (ROUTEMAP_NAME(red)) {
		if (ROUTEMAP(red) == NULL)
			ospf6_asbr_routemap_update(NULL);
		if (ROUTEMAP(red) == NULL) {
			zlog_warn(
				"route-map \"%s\" not found, suppress redistributing",
				ROUTEMAP_NAME(red));
			return;
		}
	}

	/* apply route-map */
	if (ROUTEMAP(red)) {
		troute.route_option = &tinfo;
		troute.ospf6 = ospf6;
		troute.path.redistribute_cost = metric;
		tinfo.ifindex = ifindex;
		tinfo.tag = tag;

		ret = route_map_apply(ROUTEMAP(red), prefix, &troute);
		if (ret == RMAP_DENYMATCH) {
			if (IS_OSPF6_DEBUG_ASBR)
				zlog_debug("Denied by route-map \"%s\"",
					   ROUTEMAP_NAME(red));
			ospf6_asbr_redistribute_remove(type, ifindex, prefix,
						       ospf6);
			return;
		}
	}

	match = ospf6_route_lookup(prefix, ospf6->external_table);
	if (match) {
		info = match->route_option;
		/* copy result of route-map */
		if (ROUTEMAP(red)) {
			if (troute.path.metric_type)
				match->path.metric_type =
					troute.path.metric_type;
			else
				match->path.metric_type =
					metric_type(ospf6, type, 0);
			if (troute.path.cost)
				match->path.cost = troute.path.cost;
			else
				match->path.cost = metric_value(ospf6, type, 0);

			if (!IN6_IS_ADDR_UNSPECIFIED(&tinfo.forwarding))
				memcpy(&info->forwarding, &tinfo.forwarding,
				       sizeof(struct in6_addr));
			info->tag = tinfo.tag;
		} else {
			/* If there is no route-map, simply update the tag and
			 * metric fields
			 */
			match->path.metric_type = metric_type(ospf6, type, 0);
			match->path.cost = metric_value(ospf6, type, 0);
			info->tag = tag;
		}

		info->type = type;

		if (nexthop_num && nexthop) {
			ospf6_route_add_nexthop(match, ifindex, nexthop);
			ospf6_external_lsa_fwd_addr_set(ospf6, nexthop,
							&info->forwarding);
		} else
			ospf6_route_add_nexthop(match, ifindex, NULL);

		match->path.origin.id = htonl(info->id);
		ospf6_handle_external_lsa_origination(ospf6, match, prefix);

		ospf6_asbr_status_update(ospf6, ospf6->redistribute);

		return;
	}

	/* create new entry */
	route = ospf6_route_create(ospf6);
	route->type = OSPF6_DEST_TYPE_NETWORK;
	prefix_copy(&route->prefix, prefix);

	info = (struct ospf6_external_info *)XCALLOC(
		MTYPE_OSPF6_EXTERNAL_INFO, sizeof(struct ospf6_external_info));
	route->route_option = info;

	/* copy result of route-map */
	if (ROUTEMAP(red)) {
		if (troute.path.metric_type)
			route->path.metric_type = troute.path.metric_type;
		else
			route->path.metric_type = metric_type(ospf6, type, 0);
		if (troute.path.cost)
			route->path.cost = troute.path.cost;
		else
			route->path.cost = metric_value(ospf6, type, 0);
		if (!IN6_IS_ADDR_UNSPECIFIED(&tinfo.forwarding))
			memcpy(&info->forwarding, &tinfo.forwarding,
			       sizeof(struct in6_addr));
		info->tag = tinfo.tag;
	} else {
		/* If there is no route-map, simply update the tag and metric
		 * fields
		 */
		route->path.metric_type = metric_type(ospf6, type, 0);
		route->path.cost = metric_value(ospf6, type, 0);
		info->tag = tag;
	}

	info->type = type;
	if (nexthop_num && nexthop) {
		ospf6_route_add_nexthop(route, ifindex, nexthop);
		ospf6_external_lsa_fwd_addr_set(ospf6, nexthop,
						&info->forwarding);
	} else
		ospf6_route_add_nexthop(route, ifindex, NULL);

	route = ospf6_route_add(route, ospf6->external_table);
	ospf6_handle_external_lsa_origination(ospf6, route, prefix);

	ospf6_asbr_status_update(ospf6, ospf6->redistribute);

}

static void ospf6_asbr_external_lsa_remove_by_id(struct ospf6 *ospf6,
					 uint32_t id)
{
	struct ospf6_lsa *lsa;

	lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL),
				htonl(id), ospf6->router_id, ospf6->lsdb);
	if (!lsa)
		return;

	ospf6_external_lsa_purge(ospf6, lsa);

}

static void
ospf6_link_route_to_aggr(struct ospf6_external_aggr_rt *aggr,
			struct ospf6_route *rt)
{
	(void)hash_get(aggr->match_extnl_hash, rt, hash_alloc_intern);
	rt->aggr_route = aggr;
}

static void
ospf6_asbr_summary_remove_lsa_and_route(struct ospf6 *ospf6,
					struct ospf6_external_aggr_rt *aggr)
{

	/* Send a Max age LSA if it is already originated.*/
	if (!CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED))
		return;

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("%s: Flushing Aggregate route (%pFX)",
				__func__,
				&aggr->p);

	ospf6_asbr_external_lsa_remove_by_id(ospf6, aggr->id);

	if (aggr->route) {
		if (IS_OSPF6_DEBUG_AGGR)
			zlog_debug(
				"%s: Remove the blackhole route",
				__func__);

		ospf6_zebra_route_update_remove(aggr->route, ospf6);
		if (aggr->route->route_option)
			XFREE(MTYPE_OSPF6_EXTERNAL_INFO,
			      aggr->route->route_option);
		ospf6_route_delete(aggr->route);
		aggr->route = NULL;
	}

	aggr->id = 0;
	/* Unset the Origination flag */
	UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED);
}

static void
ospf6_unlink_route_from_aggr(struct ospf6 *ospf6,
			     struct ospf6_external_aggr_rt *aggr,
			     struct ospf6_route *rt)
{
	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("%s: Unlinking external route(%pFX) from aggregator(%pFX), external route count:%ld",
					__func__,
					&rt->prefix,
					&aggr->p,
					OSPF6_EXTERNAL_RT_COUNT(aggr));

	hash_release(aggr->match_extnl_hash, rt);
	rt->aggr_route = NULL;

	/* Flush the aggregate route if matching
	 * external route count becomes zero.
	 */
	if (!OSPF6_EXTERNAL_RT_COUNT(aggr))
		ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr);
}

void ospf6_asbr_redistribute_remove(int type, ifindex_t ifindex,
				    struct prefix *prefix, struct ospf6 *ospf6)
{
	struct ospf6_route *match;
	struct ospf6_external_info *info = NULL;

	match = ospf6_route_lookup(prefix, ospf6->external_table);
	if (match == NULL) {
		if (IS_OSPF6_DEBUG_ASBR)
			zlog_debug("No such route %pFX to withdraw", prefix);
		return;
	}

	info = match->route_option;
	assert(info);

	if (info->type != type) {
		if (IS_OSPF6_DEBUG_ASBR)
			zlog_debug("Original protocol mismatch: %pFX", prefix);
		return;
	}

	/* This means aggregation on this route was not done, hence remove LSA
	 * if any originated for this prefix
	 */
	if (!match->aggr_route)
		ospf6_asbr_external_lsa_remove_by_id(ospf6, info->id);
	else
		ospf6_unlink_route_from_aggr(ospf6, match->aggr_route, match);

	if (IS_OSPF6_DEBUG_ASBR)
		zlog_debug("Removing route from external table %pFX",
			   prefix);

	ospf6_route_remove(match, ospf6->external_table);
	XFREE(MTYPE_OSPF6_EXTERNAL_INFO, info);

	ospf6_asbr_status_update(ospf6, ospf6->redistribute);
}

DEFPY (ospf6_redistribute,
       ospf6_redistribute_cmd,
       "redistribute " FRR_REDIST_STR_OSPF6D "[{metric (0-16777214)|metric-type (1-2)$metric_type|route-map RMAP_NAME$rmap_str}]",
       "Redistribute\n"
       FRR_REDIST_HELP_STR_OSPF6D
       "Metric for redistributed routes\n"
       "OSPF default metric\n"
       "OSPF exterior metric type for redistributed routes\n"
       "Set OSPF External Type 1/2 metrics\n"
       "Route map reference\n"
       "Route map name\n")
{
	int type;
	struct ospf6_redist *red;
	int idx_protocol = 1;
	char *proto = argv[idx_protocol]->text;

	VTY_DECLVAR_CONTEXT(ospf6, ospf6);

	type = proto_redistnum(AFI_IP6, proto);
	if (type < 0)
		return CMD_WARNING_CONFIG_FAILED;

	if (!metric_str)
		metric = -1;
	if (!metric_type_str)
		metric_type = -1;

	red = ospf6_redist_lookup(ospf6, type, 0);
	if (!red) {
		red = ospf6_redist_add(ospf6, type, 0);
	} else {
		/* Check if nothing has changed. */
		if (red->dmetric.value == metric
		    && red->dmetric.type == metric_type
		    && ((!ROUTEMAP_NAME(red) && !rmap_str)
			|| (ROUTEMAP_NAME(red) && rmap_str
			    && strmatch(ROUTEMAP_NAME(red), rmap_str))))
			return CMD_SUCCESS;

		ospf6_asbr_redistribute_unset(ospf6, red, type);
	}

	red->dmetric.value = metric;
	red->dmetric.type = metric_type;
	if (rmap_str)
		ospf6_asbr_routemap_set(red, rmap_str);
	else
		ospf6_asbr_routemap_unset(red);
	ospf6_asbr_redistribute_set(ospf6, type);

	return CMD_SUCCESS;
}

DEFUN (no_ospf6_redistribute,
       no_ospf6_redistribute_cmd,
       "no redistribute " FRR_REDIST_STR_OSPF6D "[{metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]",
       NO_STR
       "Redistribute\n"
       FRR_REDIST_HELP_STR_OSPF6D
       "Metric for redistributed routes\n"
       "OSPF default metric\n"
       "OSPF exterior metric type for redistributed routes\n"
       "Set OSPF External Type 1/2 metrics\n"
       "Route map reference\n"
       "Route map name\n")
{
	int type;
	struct ospf6_redist *red;
	int idx_protocol = 2;
	char *proto = argv[idx_protocol]->text;

	VTY_DECLVAR_CONTEXT(ospf6, ospf6);

	type = proto_redistnum(AFI_IP6, proto);
	if (type < 0)
		return CMD_WARNING_CONFIG_FAILED;

	red = ospf6_redist_lookup(ospf6, type, 0);
	if (!red)
		return CMD_SUCCESS;

	ospf6_asbr_redistribute_unset(ospf6, red, type);
	ospf6_redist_del(ospf6, red, type);

	return CMD_SUCCESS;
}

int ospf6_redistribute_config_write(struct vty *vty, struct ospf6 *ospf6)
{
	int type;
	struct ospf6_redist *red;

	for (type = 0; type < ZEBRA_ROUTE_MAX; type++) {
		red = ospf6_redist_lookup(ospf6, type, 0);
		if (!red)
			continue;
		if (type == ZEBRA_ROUTE_OSPF6)
			continue;

		vty_out(vty, " redistribute %s", ZROUTE_NAME(type));
		if (red->dmetric.value >= 0)
			vty_out(vty, " metric %d", red->dmetric.value);
		if (red->dmetric.type == 1)
			vty_out(vty, " metric-type 1");
		if (ROUTEMAP_NAME(red))
			vty_out(vty, " route-map %s", ROUTEMAP_NAME(red));
		vty_out(vty, "\n");
	}

	return 0;
}

static void ospf6_redistribute_show_config(struct vty *vty, struct ospf6 *ospf6,
					   json_object *json_array,
					   json_object *json, bool use_json)
{
	int type;
	int nroute[ZEBRA_ROUTE_MAX];
	int total;
	struct ospf6_route *route;
	struct ospf6_external_info *info;
	json_object *json_route;
	struct ospf6_redist *red;

	total = 0;
	memset(nroute, 0, sizeof(nroute));
	for (route = ospf6_route_head(ospf6->external_table); route;
	     route = ospf6_route_next(route)) {
		info = route->route_option;
		nroute[info->type]++;
		total++;
	}

	if (!use_json)
		vty_out(vty, "Redistributing External Routes from:\n");

	for (type = 0; type < ZEBRA_ROUTE_MAX; type++) {

		red = ospf6_redist_lookup(ospf6, type, 0);

		if (!red)
			continue;
		if (type == ZEBRA_ROUTE_OSPF6)
			continue;

		if (use_json) {
			json_route = json_object_new_object();
			json_object_string_add(json_route, "routeType",
					       ZROUTE_NAME(type));
			json_object_int_add(json_route, "numberOfRoutes",
					    nroute[type]);
			json_object_boolean_add(json_route,
						"routeMapNamePresent",
						ROUTEMAP_NAME(red));
		}

		if (ROUTEMAP_NAME(red)) {
			if (use_json) {
				json_object_string_add(json_route,
						       "routeMapName",
						       ROUTEMAP_NAME(red));
				json_object_boolean_add(json_route,
							"routeMapFound",
							ROUTEMAP(red));
			} else
				vty_out(vty,
					"    %d: %s with route-map \"%s\"%s\n",
					nroute[type], ZROUTE_NAME(type),
					ROUTEMAP_NAME(red),
					(ROUTEMAP(red) ? ""
						       : " (not found !)"));
		} else {
			if (!use_json)
				vty_out(vty, "    %d: %s\n", nroute[type],
					ZROUTE_NAME(type));
		}

		if (use_json)
			json_object_array_add(json_array, json_route);
	}
	if (use_json) {
		json_object_object_add(json, "redistributedRoutes", json_array);
		json_object_int_add(json, "totalRoutes", total);
	} else
		vty_out(vty, "Total %d routes\n", total);
}

static void ospf6_redistribute_default_set(struct ospf6 *ospf6, int originate)
{
	struct prefix_ipv6 p = {};
	struct in6_addr nexthop = {};
	int cur_originate = ospf6->default_originate;

	p.family = AF_INET6;
	p.prefixlen = 0;

	ospf6->default_originate = originate;

	switch (cur_originate) {
	case DEFAULT_ORIGINATE_NONE:
		break;
	case DEFAULT_ORIGINATE_ZEBRA:
		zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE,
					     zclient, AFI_IP6, ospf6->vrf_id);
		ospf6_asbr_redistribute_remove(DEFAULT_ROUTE, 0,
					       (struct prefix *)&p, ospf6);

		break;
	case DEFAULT_ORIGINATE_ALWAYS:
		ospf6_asbr_redistribute_remove(DEFAULT_ROUTE, 0,
					       (struct prefix *)&p, ospf6);
		break;
	}

	switch (originate) {
	case DEFAULT_ORIGINATE_NONE:
		break;
	case DEFAULT_ORIGINATE_ZEBRA:
		zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_ADD,
					     zclient, AFI_IP6, ospf6->vrf_id);

		break;
	case DEFAULT_ORIGINATE_ALWAYS:
		ospf6_asbr_redistribute_add(DEFAULT_ROUTE, 0,
					    (struct prefix *)&p, 0, &nexthop, 0,
					    ospf6, 0);
		break;
	}
}

/* Default Route originate. */
DEFPY (ospf6_default_route_originate,
       ospf6_default_route_originate_cmd,
       "default-information originate [{always$always|metric (0-16777214)$mval|metric-type (1-2)$mtype|route-map RMAP_NAME$rtmap}]",
       "Control distribution of default route\n"
       "Distribute a default route\n"
       "Always advertise default route\n"
       "OSPFv3 default metric\n"
       "OSPFv3 metric\n"
       "OSPFv3 metric type for default routes\n"
       "Set OSPFv3 External Type 1/2 metrics\n"
       "Route map reference\n"
       "Pointer to route-map entries\n")
{
	int default_originate = DEFAULT_ORIGINATE_ZEBRA;
	struct ospf6_redist *red;
	bool sameRtmap = false;

	VTY_DECLVAR_CONTEXT(ospf6, ospf6);

	int cur_originate = ospf6->default_originate;

	red = ospf6_redist_add(ospf6, DEFAULT_ROUTE, 0);

	if (always != NULL)
		default_originate = DEFAULT_ORIGINATE_ALWAYS;

	if (mval_str == NULL)
		mval = -1;

	if (mtype_str == NULL)
		mtype = -1;

	/* To check if user is providing same route map */
	if ((!rtmap && !ROUTEMAP_NAME(red)) ||
	    (rtmap && ROUTEMAP_NAME(red) &&
	     (strcmp(rtmap, ROUTEMAP_NAME(red)) == 0)))
		sameRtmap = true;

	/* Don't allow if the same lsa is already originated. */
	if ((sameRtmap) && (red->dmetric.type == mtype)
	    && (red->dmetric.value == mval)
	    && (cur_originate == default_originate))
		return CMD_SUCCESS;

	/* Updating Metric details */
	red->dmetric.type = mtype;
	red->dmetric.value = mval;

	/* updating route map details */
	if (rtmap)
		ospf6_asbr_routemap_set(red, rtmap);
	else
		ospf6_asbr_routemap_unset(red);

	ospf6_redistribute_default_set(ospf6, default_originate);
	return CMD_SUCCESS;
}

DEFPY (no_ospf6_default_information_originate,
       no_ospf6_default_information_originate_cmd,
       "no default-information originate [{always|metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]",
       NO_STR
       "Control distribution of default information\n"
       "Distribute a default route\n"
       "Always advertise default route\n"
       "OSPFv3 default metric\n"
       "OSPFv3 metric\n"
       "OSPFv3 metric type for default routes\n"
       "Set OSPFv3 External Type 1/2 metrics\n"
       "Route map reference\n"
       "Pointer to route-map entries\n")
{
	struct ospf6_redist *red;

	VTY_DECLVAR_CONTEXT(ospf6, ospf6);

	red = ospf6_redist_lookup(ospf6, DEFAULT_ROUTE, 0);
	if (!red)
		return CMD_SUCCESS;

	ospf6_asbr_routemap_unset(red);
	ospf6_redist_del(ospf6, red, DEFAULT_ROUTE);

	ospf6_redistribute_default_set(ospf6, DEFAULT_ORIGINATE_NONE);
	return CMD_SUCCESS;
}

/* Routemap Functions */
static enum route_map_cmd_result_t
ospf6_routemap_rule_match_address_prefixlist(void *rule,
					     const struct prefix *prefix,

					     void *object)
{
	struct prefix_list *plist;

	plist = prefix_list_lookup(AFI_IP6, (char *)rule);
	if (plist == NULL)
		return RMAP_NOMATCH;

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

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

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

static const struct route_map_rule_cmd
		ospf6_routemap_rule_match_address_prefixlist_cmd = {
	"ipv6 address prefix-list",
	ospf6_routemap_rule_match_address_prefixlist,
	ospf6_routemap_rule_match_address_prefixlist_compile,
	ospf6_routemap_rule_match_address_prefixlist_free,
};

/* `match interface IFNAME' */
/* Match function should return 1 if match is success else return
   zero. */
static enum route_map_cmd_result_t
ospf6_routemap_rule_match_interface(void *rule, const struct prefix *prefix,
				    void *object)
{
	struct interface *ifp;
	struct ospf6_route *route;
	struct ospf6_external_info *ei;

	route = object;
	ei = route->route_option;
	ifp = if_lookup_by_name((char *)rule, route->ospf6->vrf_id);

	if (ifp != NULL && ei->ifindex == ifp->ifindex)
		return RMAP_MATCH;

	return RMAP_NOMATCH;
}

/* Route map `interface' match statement.  `arg' should be
   interface name. */
static void *ospf6_routemap_rule_match_interface_compile(const char *arg)
{
	return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
}

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

/* Route map commands for interface matching. */
static const struct route_map_rule_cmd
		ospf6_routemap_rule_match_interface_cmd = {
	"interface",
	ospf6_routemap_rule_match_interface,
	ospf6_routemap_rule_match_interface_compile,
	ospf6_routemap_rule_match_interface_free
};

/* Match function for matching route tags */
static enum route_map_cmd_result_t
ospf6_routemap_rule_match_tag(void *rule, const struct prefix *p, void *object)
{
	route_tag_t *tag = rule;
	struct ospf6_route *route = object;
	struct ospf6_external_info *info = route->route_option;

	if (info->tag == *tag)
		return RMAP_MATCH;

	return RMAP_NOMATCH;
}

static const struct route_map_rule_cmd
		ospf6_routemap_rule_match_tag_cmd = {
	"tag",
	ospf6_routemap_rule_match_tag,
	route_map_rule_tag_compile,
	route_map_rule_tag_free,
};

static enum route_map_cmd_result_t
ospf6_routemap_rule_set_metric_type(void *rule, const struct prefix *prefix,
				    void *object)
{
	char *metric_type = rule;
	struct ospf6_route *route = object;

	if (strcmp(metric_type, "type-2") == 0)
		route->path.metric_type = 2;
	else
		route->path.metric_type = 1;

	return RMAP_OKAY;
}

static void *ospf6_routemap_rule_set_metric_type_compile(const char *arg)
{
	if (strcmp(arg, "type-2") && strcmp(arg, "type-1"))
		return NULL;
	return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
}

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

static const struct route_map_rule_cmd
		ospf6_routemap_rule_set_metric_type_cmd = {
	"metric-type",
	ospf6_routemap_rule_set_metric_type,
	ospf6_routemap_rule_set_metric_type_compile,
	ospf6_routemap_rule_set_metric_type_free,
};

struct ospf6_metric {
	enum { metric_increment, metric_decrement, metric_absolute } type;
	bool used;
	uint32_t metric;
};

static enum route_map_cmd_result_t
ospf6_routemap_rule_set_metric(void *rule, const struct prefix *prefix,
			       void *object)
{
	struct ospf6_metric *metric;
	struct ospf6_route *route;

	/* Fetch routemap's rule information. */
	metric = rule;
	route = object;

	/* Set metric out value. */
	if (!metric->used)
		return RMAP_OKAY;

	if (route->path.redistribute_cost > OSPF6_EXT_PATH_METRIC_MAX)
		route->path.redistribute_cost = OSPF6_EXT_PATH_METRIC_MAX;

	if (metric->type == metric_increment) {
		route->path.cost = route->path.redistribute_cost +
				   metric->metric;

		/* Check overflow */
		if (route->path.cost > OSPF6_EXT_PATH_METRIC_MAX ||
		    route->path.cost < metric->metric)
			route->path.cost = OSPF6_EXT_PATH_METRIC_MAX;
	} else if (metric->type == metric_decrement) {
		route->path.cost = route->path.redistribute_cost -
				   metric->metric;

		/* Check overflow */
		if (route->path.cost == 0 ||
		    route->path.cost > route->path.redistribute_cost)
			route->path.cost = 1;
	} else if (metric->type == metric_absolute)
		route->path.cost = metric->metric;

	return RMAP_OKAY;
}

static void *ospf6_routemap_rule_set_metric_compile(const char *arg)
{
	struct ospf6_metric *metric;

	metric = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*metric));
	metric->used = false;

	if (all_digit(arg))
		metric->type = metric_absolute;

	if ((arg[0] == '+') && all_digit(arg + 1)) {
		metric->type = metric_increment;
		arg++;
	}

	if ((arg[0] == '-') && all_digit(arg + 1)) {
		metric->type = metric_decrement;
		arg++;
	}

	metric->metric = strtoul(arg, NULL, 10);

	if (metric->metric > OSPF6_EXT_PATH_METRIC_MAX)
		metric->metric = OSPF6_EXT_PATH_METRIC_MAX;

	if (metric->metric)
		metric->used = true;

	return metric;
}

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

static const struct route_map_rule_cmd
		ospf6_routemap_rule_set_metric_cmd = {
	"metric",
	ospf6_routemap_rule_set_metric,
	ospf6_routemap_rule_set_metric_compile,
	ospf6_routemap_rule_set_metric_free,
};

static enum route_map_cmd_result_t
ospf6_routemap_rule_set_forwarding(void *rule, const struct prefix *prefix,
				   void *object)
{
	char *forwarding = rule;
	struct ospf6_route *route = object;
	struct ospf6_external_info *info = route->route_option;

	if (inet_pton(AF_INET6, forwarding, &info->forwarding) != 1) {
		memset(&info->forwarding, 0, sizeof(struct in6_addr));
		return RMAP_ERROR;
	}

	return RMAP_OKAY;
}

static void *ospf6_routemap_rule_set_forwarding_compile(const char *arg)
{
	struct in6_addr a;
	if (inet_pton(AF_INET6, arg, &a) != 1)
		return NULL;
	return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
}

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

static const struct route_map_rule_cmd
		ospf6_routemap_rule_set_forwarding_cmd = {
	"forwarding-address",
	ospf6_routemap_rule_set_forwarding,
	ospf6_routemap_rule_set_forwarding_compile,
	ospf6_routemap_rule_set_forwarding_free,
};

static enum route_map_cmd_result_t
ospf6_routemap_rule_set_tag(void *rule, const struct prefix *p, void *object)
{
	route_tag_t *tag = rule;
	struct ospf6_route *route = object;
	struct ospf6_external_info *info = route->route_option;

	info->tag = *tag;
	return RMAP_OKAY;
}

static const struct route_map_rule_cmd ospf6_routemap_rule_set_tag_cmd = {
	"tag",
	ospf6_routemap_rule_set_tag,
	route_map_rule_tag_compile,
	route_map_rule_tag_free,
};

/* add "set metric-type" */
DEFUN_YANG (ospf6_routemap_set_metric_type, ospf6_routemap_set_metric_type_cmd,
      "set metric-type <type-1|type-2>",
       SET_STR
       "Type of metric for destination routing protocol\n"
       "OSPF[6] external type 1 metric\n"
       "OSPF[6] external type 2 metric\n")
{
	char *ext = argv[2]->text;

	const char *xpath =
		"./set-action[action='frr-ospf-route-map:metric-type']";
	char xpath_value[XPATH_MAXLEN];

	nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
	snprintf(xpath_value, sizeof(xpath_value),
		 "%s/rmap-set-action/frr-ospf-route-map:metric-type", xpath);
	nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, ext);
	return nb_cli_apply_changes(vty, NULL);
}

/* delete "set metric-type" */
DEFUN_YANG (ospf6_routemap_no_set_metric_type, ospf6_routemap_no_set_metric_type_cmd,
      "no set metric-type [<type-1|type-2>]",
      NO_STR
      SET_STR
      "Type of metric for destination routing protocol\n"
      "OSPF[6] external type 1 metric\n"
      "OSPF[6] external type 2 metric\n")
{
	const char *xpath =
		"./set-action[action='frr-ospf-route-map:metric-type']";

	nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
	return nb_cli_apply_changes(vty, NULL);
}

/* add "set forwarding-address" */
DEFUN_YANG (ospf6_routemap_set_forwarding, ospf6_routemap_set_forwarding_cmd,
      "set forwarding-address X:X::X:X",
      "Set value\n"
      "Forwarding Address\n"
      "IPv6 Address\n")
{
	int idx_ipv6 = 2;
	const char *xpath =
		"./set-action[action='frr-ospf6-route-map:forwarding-address']";
	char xpath_value[XPATH_MAXLEN];

	nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
	snprintf(xpath_value, sizeof(xpath_value),
		 "%s/rmap-set-action/frr-ospf6-route-map:ipv6-address", xpath);
	nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY,
			      argv[idx_ipv6]->arg);
	return nb_cli_apply_changes(vty, NULL);
}

/* delete "set forwarding-address" */
DEFUN_YANG (ospf6_routemap_no_set_forwarding, ospf6_routemap_no_set_forwarding_cmd,
      "no set forwarding-address [X:X::X:X]",
      NO_STR
      "Set value\n"
      "Forwarding Address\n"
      "IPv6 Address\n")
{
	const char *xpath =
		"./set-action[action='frr-ospf6-route-map:forwarding-address']";

	nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
	return nb_cli_apply_changes(vty, NULL);
}

static void ospf6_routemap_init(void)
{
	route_map_init();

	route_map_add_hook(ospf6_asbr_routemap_update);
	route_map_delete_hook(ospf6_asbr_routemap_update);
	route_map_event_hook(ospf6_asbr_routemap_event);

	route_map_set_metric_hook(generic_set_add);
	route_map_no_set_metric_hook(generic_set_delete);

	route_map_set_tag_hook(generic_set_add);
	route_map_no_set_tag_hook(generic_set_delete);

	route_map_match_tag_hook(generic_match_add);
	route_map_no_match_tag_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_interface_hook(generic_match_add);
	route_map_no_match_interface_hook(generic_match_delete);

	route_map_install_match(
		&ospf6_routemap_rule_match_address_prefixlist_cmd);
	route_map_install_match(&ospf6_routemap_rule_match_interface_cmd);
	route_map_install_match(&ospf6_routemap_rule_match_tag_cmd);

	route_map_install_set(&ospf6_routemap_rule_set_metric_type_cmd);
	route_map_install_set(&ospf6_routemap_rule_set_metric_cmd);
	route_map_install_set(&ospf6_routemap_rule_set_forwarding_cmd);
	route_map_install_set(&ospf6_routemap_rule_set_tag_cmd);

	/* ASE Metric Type (e.g. Type-1/Type-2) */
	install_element(RMAP_NODE, &ospf6_routemap_set_metric_type_cmd);
	install_element(RMAP_NODE, &ospf6_routemap_no_set_metric_type_cmd);

	/* ASE Metric */
	install_element(RMAP_NODE, &ospf6_routemap_set_forwarding_cmd);
	install_element(RMAP_NODE, &ospf6_routemap_no_set_forwarding_cmd);
}


/* Display functions */
static char *ospf6_as_external_lsa_get_prefix_str(struct ospf6_lsa *lsa,
						  char *buf, int buflen,
						  int pos)
{
	struct ospf6_as_external_lsa *external;
	struct in6_addr in6;
	int prefix_length = 0;
	char tbuf[16];

	if (lsa) {
		external = lsa_after_header(lsa->header);

		if (pos == 0) {
			ospf6_prefix_in6_addr(&in6, external,
					      &external->prefix);
			prefix_length = external->prefix.prefix_length;
		} else {
			in6 = *((struct in6_addr
					 *)((caddr_t)external
					    + sizeof(struct
						     ospf6_as_external_lsa)
					    + OSPF6_PREFIX_SPACE(
						      external->prefix
							      .prefix_length)));
		}
		if (buf) {
			inet_ntop(AF_INET6, &in6, buf, buflen);
			if (prefix_length) {
				snprintf(tbuf, sizeof(tbuf), "/%d",
					 prefix_length);
				strlcat(buf, tbuf, buflen);
			}
		}
	}
	return (buf);
}

static int ospf6_as_external_lsa_show(struct vty *vty, struct ospf6_lsa *lsa,
				      json_object *json_obj, bool use_json)
{
	struct ospf6_as_external_lsa *external;
	char buf[64];

	assert(lsa->header);
	external = lsa_after_header(lsa->header);

	/* bits */
	snprintf(buf, sizeof(buf), "%c%c%c",
		 (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E) ? 'E'
								      : '-'),
		 (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F) ? 'F'
								      : '-'),
		 (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T) ? 'T'
								      : '-'));

	if (use_json) {
		json_object_string_add(json_obj, "bits", buf);
		json_object_int_add(json_obj, "metric",
				    (unsigned long)OSPF6_ASBR_METRIC(external));
		ospf6_prefix_options_printbuf(external->prefix.prefix_options,
					      buf, sizeof(buf));
		json_object_string_add(json_obj, "prefixOptions", buf);
		json_object_int_add(
			json_obj, "referenceLsType",
			ntohs(external->prefix.prefix_refer_lstype));
		json_object_string_add(json_obj, "prefix",
				       ospf6_as_external_lsa_get_prefix_str(
					       lsa, buf, sizeof(buf), 0));

		/* Forwarding-Address */
		json_object_boolean_add(
			json_obj, "forwardingAddressPresent",
			CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F));
		if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F))
			json_object_string_add(
				json_obj, "forwardingAddress",
				ospf6_as_external_lsa_get_prefix_str(
					lsa, buf, sizeof(buf), 1));

		/* Tag */
		json_object_boolean_add(
			json_obj, "tagPresent",
			CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T));
		if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T))
			json_object_int_add(json_obj, "tag",
					    ospf6_as_external_lsa_get_tag(lsa));
	} else {
		vty_out(vty, "     Bits: %s\n", buf);
		vty_out(vty, "     Metric: %5lu\n",
			(unsigned long)OSPF6_ASBR_METRIC(external));

		ospf6_prefix_options_printbuf(external->prefix.prefix_options,
					      buf, sizeof(buf));
		vty_out(vty, "     Prefix Options: %s\n", buf);

		vty_out(vty, "     Referenced LSType: %d\n",
			ntohs(external->prefix.prefix_refer_lstype));

		vty_out(vty, "     Prefix: %s\n",
			ospf6_as_external_lsa_get_prefix_str(lsa, buf,
							     sizeof(buf), 0));

		/* Forwarding-Address */
		if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) {
			vty_out(vty, "     Forwarding-Address: %s\n",
				ospf6_as_external_lsa_get_prefix_str(
					lsa, buf, sizeof(buf), 1));
		}

		/* Tag */
		if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T)) {
			vty_out(vty, "     Tag: %" ROUTE_TAG_PRI "\n",
				ospf6_as_external_lsa_get_tag(lsa));
		}
	}

	return 0;
}

static void ospf6_asbr_external_route_show(struct vty *vty,
					   struct ospf6_route *route,
					   json_object *json_array,
					   bool use_json)
{
	struct ospf6_external_info *info = route->route_option;
	char prefix[PREFIX2STR_BUFFER], id[16], forwarding[64];
	uint32_t tmp_id;
	json_object *json_route;
	char route_type[2];

	prefix2str(&route->prefix, prefix, sizeof(prefix));
	tmp_id = ntohl(info->id);
	inet_ntop(AF_INET, &tmp_id, id, sizeof(id));
	if (!IN6_IS_ADDR_UNSPECIFIED(&info->forwarding))
		inet_ntop(AF_INET6, &info->forwarding, forwarding,
			  sizeof(forwarding));
	else
		snprintf(forwarding, sizeof(forwarding), ":: (ifindex %d)",
			 ospf6_route_get_first_nh_index(route));

	if (use_json) {
		json_route = json_object_new_object();
		snprintf(route_type, sizeof(route_type), "%c",
			 zebra_route_char(info->type));
		json_object_string_add(json_route, "routeType", route_type);
		json_object_string_add(json_route, "destination", prefix);
		json_object_string_add(json_route, "id", id);
		json_object_int_add(json_route, "metricType",
				    route->path.metric_type);
		json_object_int_add(
			json_route, "routeCost",
			(unsigned long)(route->path.metric_type == 2
						? route->path.u.cost_e2
						: route->path.cost));
		json_object_string_add(json_route, "forwarding", forwarding);

		json_object_array_add(json_array, json_route);
	} else

		vty_out(vty, "%c %-32pFX %-15s type-%d %5lu %s\n",
			zebra_route_char(info->type), &route->prefix, id,
			route->path.metric_type,
			(unsigned long)(route->path.metric_type == 2
						? route->path.u.cost_e2
						: route->path.cost),
			forwarding);
}

DEFUN(show_ipv6_ospf6_redistribute, show_ipv6_ospf6_redistribute_cmd,
      "show ipv6 ospf6 [vrf <NAME|all>] redistribute [json]",
      SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR
      "All VRFs\n"
      "redistributing External information\n" JSON_STR)
{
	struct ospf6_route *route;
	struct ospf6 *ospf6 = NULL;
	json_object *json = NULL;
	bool uj = use_json(argc, argv);
	struct listnode *node;
	const char *vrf_name = NULL;
	bool all_vrf = false;
	int idx_vrf = 0;

	json_object *json_array_routes = NULL;
	json_object *json_array_redistribute = NULL;

	OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);

	if (uj) {
		json = json_object_new_object();
		json_array_routes = json_object_new_array();
		json_array_redistribute = json_object_new_array();
	}

	for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) {
		if (all_vrf
		    || ((ospf6->name == NULL && vrf_name == NULL)
			|| (ospf6->name && vrf_name
			    && strcmp(ospf6->name, vrf_name) == 0))) {
			ospf6_redistribute_show_config(
				vty, ospf6, json_array_redistribute, json, uj);

			for (route = ospf6_route_head(ospf6->external_table);
			     route; route = ospf6_route_next(route)) {
				ospf6_asbr_external_route_show(
					vty, route, json_array_routes, uj);
			}

			if (uj) {
				json_object_object_add(json, "routes",
						       json_array_routes);
				vty_json(vty, json);
			}

			if (!all_vrf)
				break;
		}
	}

	OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6);

	return CMD_SUCCESS;
}

static struct ospf6_lsa_handler as_external_handler = {
	.lh_type = OSPF6_LSTYPE_AS_EXTERNAL,
	.lh_name = "AS-External",
	.lh_short_name = "ASE",
	.lh_show = ospf6_as_external_lsa_show,
	.lh_get_prefix_str = ospf6_as_external_lsa_get_prefix_str,
	.lh_debug = 0};

static struct ospf6_lsa_handler nssa_external_handler = {
	.lh_type = OSPF6_LSTYPE_TYPE_7,
	.lh_name = "NSSA",
	.lh_short_name = "Type7",
	.lh_show = ospf6_as_external_lsa_show,
	.lh_get_prefix_str = ospf6_as_external_lsa_get_prefix_str,
	.lh_debug = 0};

void ospf6_asbr_init(void)
{
	ospf6_routemap_init();

	ospf6_install_lsa_handler(&as_external_handler);
	ospf6_install_lsa_handler(&nssa_external_handler);

	install_element(VIEW_NODE, &show_ipv6_ospf6_redistribute_cmd);

	install_element(OSPF6_NODE, &ospf6_default_route_originate_cmd);
	install_element(OSPF6_NODE,
			&no_ospf6_default_information_originate_cmd);
	install_element(OSPF6_NODE, &ospf6_redistribute_cmd);
	install_element(OSPF6_NODE, &no_ospf6_redistribute_cmd);
}

void ospf6_asbr_redistribute_disable(struct ospf6 *ospf6)
{
	int type;
	struct ospf6_redist *red;

	for (type = 0; type < ZEBRA_ROUTE_MAX; type++) {
		red = ospf6_redist_lookup(ospf6, type, 0);
		if (!red)
			continue;
		if (type == ZEBRA_ROUTE_OSPF6)
			continue;
		ospf6_asbr_redistribute_unset(ospf6, red, type);
		ospf6_redist_del(ospf6, red, type);
	}
	red = ospf6_redist_lookup(ospf6, DEFAULT_ROUTE, 0);
	if (red) {
		ospf6_asbr_routemap_unset(red);
		ospf6_redist_del(ospf6, red, type);
		ospf6_redistribute_default_set(ospf6, DEFAULT_ORIGINATE_NONE);
	}
}

void ospf6_asbr_redistribute_reset(struct ospf6 *ospf6)
{
	int type;
	struct ospf6_redist *red;
	char buf[RMAP_NAME_MAXLEN];

	for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) {
		buf[0] = '\0';
		if (type == ZEBRA_ROUTE_OSPF6)
			continue;
		red = ospf6_redist_lookup(ospf6, type, 0);
		if (!red)
			continue;

		if (type == DEFAULT_ROUTE) {
			ospf6_redistribute_default_set(
				ospf6, ospf6->default_originate);
			continue;
		}
		if (ROUTEMAP_NAME(red))
			strlcpy(buf, ROUTEMAP_NAME(red), sizeof(buf));

		ospf6_asbr_redistribute_unset(ospf6, red, type);
		if (buf[0])
			ospf6_asbr_routemap_set(red, buf);
		ospf6_asbr_redistribute_set(ospf6, type);
	}
}

void ospf6_asbr_terminate(void)
{
	/* Cleanup route maps */
	route_map_finish();
}

DEFUN (debug_ospf6_asbr,
       debug_ospf6_asbr_cmd,
       "debug ospf6 asbr",
       DEBUG_STR
       OSPF6_STR
       "Debug OSPFv3 ASBR function\n"
      )
{
	OSPF6_DEBUG_ASBR_ON();
	return CMD_SUCCESS;
}

DEFUN (no_debug_ospf6_asbr,
       no_debug_ospf6_asbr_cmd,
       "no debug ospf6 asbr",
       NO_STR
       DEBUG_STR
       OSPF6_STR
       "Debug OSPFv3 ASBR function\n"
      )
{
	OSPF6_DEBUG_ASBR_OFF();
	return CMD_SUCCESS;
}

int config_write_ospf6_debug_asbr(struct vty *vty)
{
	if (IS_OSPF6_DEBUG_ASBR)
		vty_out(vty, "debug ospf6 asbr\n");
	return 0;
}

static void ospf6_default_originate_write(struct vty *vty, struct ospf6 *o)
{
	struct ospf6_redist *red;

	vty_out(vty, " default-information originate");
	if (o->default_originate == DEFAULT_ORIGINATE_ALWAYS)
		vty_out(vty, " always");

	red = ospf6_redist_lookup(o, DEFAULT_ROUTE, 0);
	if (red == NULL) {
		vty_out(vty, "\n");
		return;
	}

	if (red->dmetric.value >= 0)
		vty_out(vty, " metric %d", red->dmetric.value);

	if (red->dmetric.type >= 0)
		vty_out(vty, " metric-type %d", red->dmetric.type);

	if (ROUTEMAP_NAME(red))
		vty_out(vty, " route-map %s", ROUTEMAP_NAME(red));

	vty_out(vty, "\n");
}

int ospf6_distribute_config_write(struct vty *vty, struct ospf6 *o)
{
	if (o == NULL)
		return 0;

	/* Print default originate configuration. */
	if (o->default_originate != DEFAULT_ORIGINATE_NONE)
		ospf6_default_originate_write(vty, o);

	return 0;
}

void install_element_ospf6_debug_asbr(void)
{
	install_element(ENABLE_NODE, &debug_ospf6_asbr_cmd);
	install_element(ENABLE_NODE, &no_debug_ospf6_asbr_cmd);
	install_element(CONFIG_NODE, &debug_ospf6_asbr_cmd);
	install_element(CONFIG_NODE, &no_debug_ospf6_asbr_cmd);
}

/* ASBR Summarisation */
void ospf6_fill_aggr_route_details(struct ospf6 *ospf6,
				   struct ospf6_external_aggr_rt *aggr)
{
	struct ospf6_route *rt_aggr = aggr->route;
	struct ospf6_external_info *ei_aggr = rt_aggr->route_option;

	rt_aggr->prefix = aggr->p;
	ei_aggr->tag = aggr->tag;
	ei_aggr->type = 0;
	ei_aggr->id = aggr->id;

	/* When metric is not configured, apply the default metric */
	rt_aggr->path.cost = ((aggr->metric == -1) ?
				DEFAULT_DEFAULT_METRIC
				: (unsigned int)(aggr->metric));
	rt_aggr->path.metric_type = aggr->mtype;

	rt_aggr->path.origin.id = htonl(aggr->id);
}

static void
ospf6_summary_add_aggr_route_and_blackhole(struct ospf6 *ospf6,
					   struct ospf6_external_aggr_rt *aggr)
{
	struct ospf6_route *rt_aggr;
	struct ospf6_route *old_rt = NULL;
	struct ospf6_external_info *info;

	/* Check if a route is already present. */
	if (aggr->route)
		old_rt = aggr->route;

	/* Create summary route and save it. */
	rt_aggr = ospf6_route_create(ospf6);
	rt_aggr->type = OSPF6_DEST_TYPE_NETWORK;
	/* Needed to install route while calling zebra api */
	SET_FLAG(rt_aggr->flag, OSPF6_ROUTE_BEST);

	info = XCALLOC(MTYPE_OSPF6_EXTERNAL_INFO, sizeof(*info));
	rt_aggr->route_option = info;
	aggr->route = rt_aggr;

	/* Prepare the external_info for aggregator
	 * Fill all the details which will get advertised
	 */
	ospf6_fill_aggr_route_details(ospf6, aggr);

	/* Add next-hop to Null interface. */
	ospf6_add_route_nexthop_blackhole(rt_aggr);

	/* Free the old route, if any. */
	if (old_rt) {
		ospf6_zebra_route_update_remove(old_rt, ospf6);

		if (old_rt->route_option)
			XFREE(MTYPE_OSPF6_EXTERNAL_INFO, old_rt->route_option);

		ospf6_route_delete(old_rt);
	}

	ospf6_zebra_route_update_add(rt_aggr, ospf6);
}

static void ospf6_originate_new_aggr_lsa(struct ospf6 *ospf6,
					 struct ospf6_external_aggr_rt *aggr)
{
	struct prefix prefix_id;
	struct ospf6_lsa *lsa = NULL;

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("%s: Originate new aggregate route(%pFX)", __func__,
			   &aggr->p);

	aggr->id = ospf6->external_id++;

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug(
			"Advertise AS-External Id:%pI4 prefix %pFX metric %u",
			&prefix_id.u.prefix4, &aggr->p, aggr->metric);

	ospf6_summary_add_aggr_route_and_blackhole(ospf6, aggr);

	/* Originate summary LSA */
	lsa = ospf6_originate_type5_type7_lsas(aggr->route, ospf6);
	if (lsa) {
		if (IS_OSPF6_DEBUG_AGGR)
			zlog_debug("%s: Set the origination bit for aggregator",
					__func__);
		SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED);
	}
}

static void
ospf6_aggr_handle_advertise_change(struct ospf6 *ospf6,
		struct ospf6_external_aggr_rt *aggr)
{
	/* Check if advertise option modified. */
	if (CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) {
		if (IS_OSPF6_DEBUG_AGGR)
			zlog_debug("%s: Don't originate the summary address,It is configured to not-advertise.",
					__func__);
		ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr);

		return;
	}

	/* There are no routes present under this aggregation config, hence
	 * nothing to originate here
	 */
	if (OSPF6_EXTERNAL_RT_COUNT(aggr) == 0) {
		if (IS_OSPF6_DEBUG_AGGR)
			zlog_debug("%s: No routes present under this aggregation",
					__func__);
		return;
	}

	if (!CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED)) {
		if (IS_OSPF6_DEBUG_AGGR)
			zlog_debug("%s: Now it is advertisable",
					__func__);

		ospf6_originate_new_aggr_lsa(ospf6, aggr);

		return;
	}
}

static void
ospf6_originate_summary_lsa(struct ospf6 *ospf6,
			    struct ospf6_external_aggr_rt *aggr,
			    struct ospf6_route *rt)
{
	struct ospf6_lsa *lsa = NULL, *aggr_lsa = NULL;
	struct ospf6_external_info *info = NULL;
	struct ospf6_external_aggr_rt *old_aggr;
	struct ospf6_as_external_lsa *external;
	struct ospf6_route *rt_aggr = NULL;
	route_tag_t tag = 0;
	unsigned int metric = 0;
	int mtype;

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("%s: Prepare to originate Summary route(%pFX)",
			   __func__, &aggr->p);

	/* This case to handle when the overlapping aggregator address
	 * is available. Best match will be considered.So need to delink
	 * from old aggregator and link to the new aggr.
	 */
	if (rt->aggr_route) {
		if (rt->aggr_route != aggr) {
			old_aggr = rt->aggr_route;
			ospf6_unlink_route_from_aggr(ospf6, old_aggr, rt);
		}
	}

	/* Add the external route to hash table */
	ospf6_link_route_to_aggr(aggr, rt);

	/* The key for ID field is a running number and not prefix */
	info = rt->route_option;
	assert(info);
	if (info->id)
		lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL),
					htonl(info->id), ospf6->router_id,
					ospf6->lsdb);

	aggr_lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL),
				htonl(aggr->id), ospf6->router_id, ospf6->lsdb);

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("%s: Aggr LSA ID: %d flags %x.",
		   __func__, aggr->id, aggr->aggrflags);
	/* Don't originate external LSA,
	 * If it is configured not to advertise.
	 */
	if (CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) {
		/* If it is already originated as external LSA,
		 * But, it is configured not to advertise then
		 * flush the originated external lsa.
		 */
		if (lsa) {
			if (IS_OSPF6_DEBUG_AGGR)
				zlog_debug("%s: Purge the external LSA %s.",
					   __func__, lsa->name);
			ospf6_external_lsa_purge(ospf6, lsa);
			info->id = 0;
			rt->path.origin.id = 0;
		}

		if (aggr_lsa) {
			if (IS_OSPF6_DEBUG_AGGR)
				zlog_debug("%s: Purge the aggr external LSA %s.",
					   __func__, lsa->name);
			ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr);
		}

		UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED);

		if (IS_OSPF6_DEBUG_AGGR)
			zlog_debug("%s: Don't originate the summary address,It is configured to not-advertise.",
				__func__);
		return;
	}

	/* Summary route already originated,
	 * So, Do nothing.
	 */
	if (CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED)) {
		if (!aggr_lsa) {
			zlog_warn(
				"%s: Could not refresh/originate %pFX",
						__func__,
						&aggr->p);
			/* Remove the assert later */
			assert(aggr_lsa);
			return;
		}

		external = lsa_after_header(aggr_lsa->header);
		metric = (unsigned long)OSPF6_ASBR_METRIC(external);
		tag = ospf6_as_external_lsa_get_tag(aggr_lsa);
		mtype = CHECK_FLAG(external->bits_metric,
				   OSPF6_ASBR_BIT_E) ? 2 : 1;

		/* Prepare the external_info for aggregator */
		ospf6_fill_aggr_route_details(ospf6, aggr);
		rt_aggr = aggr->route;
		/* If tag/metric/metric-type modified , then re-originate the
		 * route with modified tag/metric/metric-type details.
		 */
		if ((tag != aggr->tag)
		    || (metric != (unsigned int)rt_aggr->path.cost)
		    || (mtype != aggr->mtype)) {

			if (IS_OSPF6_DEBUG_AGGR)
				zlog_debug(
					"%s: Routetag(old:%d new:%d)/Metric(o:%u,n:%u)/mtype(o:%d n:%d) modified,So refresh the summary route.(%pFX)",
					__func__, tag, aggr->tag,
					metric,
					aggr->metric,
					mtype, aggr->mtype,
					&aggr->p);

			aggr_lsa = ospf6_originate_type5_type7_lsas(aggr->route,
								    ospf6);
			if (aggr_lsa)
				SET_FLAG(aggr->aggrflags,
					OSPF6_EXTERNAL_AGGRT_ORIGINATED);
		}

		return;
	}

	/* If the external route prefix same as aggregate route
	 * and if external route is already originated as TYPE-5
	 * then just update the aggr info and remove the route info
	 */
	if (lsa && prefix_same(&aggr->p, &rt->prefix)) {
		if (IS_OSPF6_DEBUG_AGGR)
			zlog_debug(
				"%s: Route prefix is same as aggr so no need to re-originate LSA(%pFX)",
				__PRETTY_FUNCTION__, &aggr->p);

		aggr->id = info->id;
		info->id = 0;
		rt->path.origin.id = 0;

		ospf6_summary_add_aggr_route_and_blackhole(ospf6, aggr);

		SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED);

		return;
	}

	ospf6_originate_new_aggr_lsa(ospf6, aggr);
}

static void ospf6_aggr_handle_external_info(void *data)
{
	struct ospf6_route *rt = (struct ospf6_route *)data;
	struct ospf6_external_aggr_rt *aggr = NULL;
	struct ospf6_lsa *lsa = NULL;
	struct ospf6_external_info *info;
	struct ospf6 *ospf6 = NULL;

	rt->aggr_route = NULL;

	rt->to_be_processed = true;

	if (IS_OSPF6_DEBUG_ASBR || IS_OSPF6_DEBUG_ORIGINATE(AS_EXTERNAL))
		zlog_debug("%s: Handle external route for origination/refresh (%pFX)",
					__func__,
					&rt->prefix);

	ospf6 = rt->ospf6;
	assert(ospf6);

	aggr = ospf6_external_aggr_match(ospf6,
					&rt->prefix);
	if (aggr) {
		ospf6_originate_summary_lsa(ospf6, aggr, rt);
		return;
	}

	info = rt->route_option;
	if (info->id) {
		lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL),
					htonl(info->id), ospf6->router_id,
					ospf6->lsdb);
		if (lsa) {
			if (IS_OSPF6_DEBUG_AGGR)
				zlog_debug("%s: LSA found, refresh it",
					   __func__);
			EVENT_OFF(lsa->refresh);
			event_add_event(master, ospf6_lsa_refresh, lsa, 0,
					&lsa->refresh);
			return;
		}
	}

	info->id  = ospf6->external_id++;
	rt->path.origin.id = htonl(info->id);

	(void)ospf6_originate_type5_type7_lsas(rt, ospf6);
}

void ospf6_asbr_summary_config_delete(struct ospf6 *ospf6,
				      struct route_node *rn)
{
	struct ospf6_external_aggr_rt *aggr = rn->info;

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("%s: Deleting Aggregate route (%pFX)",
						__func__,
						&aggr->p);

	ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr);

	rn->info = NULL;
	route_unlock_node(rn);
}

static int
ospf6_handle_external_aggr_modify(struct ospf6 *ospf6,
				  struct ospf6_external_aggr_rt *aggr)
{
	struct ospf6_lsa *lsa = NULL;
	struct ospf6_as_external_lsa *asel = NULL;
	struct ospf6_route *rt_aggr;
	unsigned int metric = 0;
	route_tag_t tag = 0;
	int mtype;

	lsa = ospf6_lsdb_lookup(
		htons(OSPF6_LSTYPE_AS_EXTERNAL),
		htonl(aggr->id), ospf6->router_id,
		ospf6->lsdb);
	if (!lsa) {
		zlog_warn(
			"%s: Could not refresh/originate %pFX",
			__func__,
			&aggr->p);

		return OSPF6_FAILURE;
	}

	asel = lsa_after_header(lsa->header);
	metric = (unsigned long)OSPF6_ASBR_METRIC(asel);
	tag = ospf6_as_external_lsa_get_tag(lsa);
	mtype = CHECK_FLAG(asel->bits_metric,
			   OSPF6_ASBR_BIT_E) ? 2 : 1;

	/* Fill all the details for advertisement */
	ospf6_fill_aggr_route_details(ospf6, aggr);
	rt_aggr = aggr->route;
	/* If tag/metric/metric-type modified , then
	 * re-originate the route with modified
	 * tag/metric/metric-type details.
	 */
	if ((tag != aggr->tag)
	    || (metric
		!= (unsigned int)rt_aggr->path.cost)
	    || (mtype
		!= aggr->mtype)) {
		if (IS_OSPF6_DEBUG_AGGR)
			zlog_debug(
			"%s: Changed tag(old:%d new:%d)/metric(o:%u n:%d)/mtype(o:%d n:%d),So refresh the summary route.(%pFX)",
			__func__, tag,
			aggr->tag,
			metric,
			(unsigned int)rt_aggr->path.cost,
			mtype, aggr->mtype,
			&aggr->p);

		(void)ospf6_originate_type5_type7_lsas(
					aggr->route,
					ospf6);
	}

	return OSPF6_SUCCESS;
}

static void ospf6_handle_external_aggr_update(struct ospf6 *ospf6)
{
	struct route_node *rn = NULL;
	int ret;

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("%s: Process modified aggregators.", __func__);

	for (rn = route_top(ospf6->rt_aggr_tbl); rn; rn = route_next(rn)) {
		struct ospf6_external_aggr_rt *aggr;

		if (!rn->info)
			continue;

		aggr = rn->info;

		if (aggr->action == OSPF6_ROUTE_AGGR_DEL) {
			aggr->action = OSPF6_ROUTE_AGGR_NONE;
			ospf6_asbr_summary_config_delete(ospf6, rn);

			hash_clean_and_free(&aggr->match_extnl_hash,
					    ospf6_aggr_handle_external_info);

			XFREE(MTYPE_OSPF6_EXTERNAL_RT_AGGR, aggr);

		} else if (aggr->action == OSPF6_ROUTE_AGGR_MODIFY) {

			aggr->action = OSPF6_ROUTE_AGGR_NONE;

			/* Check if tag/metric/metric-type modified */
			if (CHECK_FLAG(aggr->aggrflags,
				OSPF6_EXTERNAL_AGGRT_ORIGINATED)
			    && !CHECK_FLAG(aggr->aggrflags,
				OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) {

				ret = ospf6_handle_external_aggr_modify(ospf6,
									aggr);
				if (ret == OSPF6_FAILURE)
					continue;
			}

			/* Advertise option modified ?
			 * If so, handled it here.
			 */
			ospf6_aggr_handle_advertise_change(ospf6, aggr);
		}
	}
}

static void ospf6_aggr_unlink_external_info(void *data)
{
	struct ospf6_route *rt = (struct ospf6_route *)data;

	rt->aggr_route = NULL;

	rt->to_be_processed = true;
}

void ospf6_external_aggregator_free(struct ospf6_external_aggr_rt *aggr)
{
	hash_clean_and_free(&aggr->match_extnl_hash,
			    ospf6_aggr_unlink_external_info);

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("%s: Release the aggregator Address(%pFX)",
						__func__,
						&aggr->p);

	XFREE(MTYPE_OSPF6_EXTERNAL_RT_AGGR, aggr);
}

static void
ospf6_delete_all_marked_aggregators(struct ospf6 *ospf6)
{
	struct route_node *rn = NULL;
	struct ospf6_external_aggr_rt *aggr;

	/* Loop through all the aggregators, Delete all aggregators
	 * which are marked as DELETE. Set action to NONE for remaining
	 * aggregators
	 */
	for (rn = route_top(ospf6->rt_aggr_tbl); rn; rn = route_next(rn)) {
		if (!rn->info)
			continue;

		aggr = rn->info;

		if (aggr->action != OSPF6_ROUTE_AGGR_DEL) {
			aggr->action = OSPF6_ROUTE_AGGR_NONE;
			continue;
		}
		ospf6_asbr_summary_config_delete(ospf6, rn);
		ospf6_external_aggregator_free(aggr);
	}
}

static void ospf6_handle_exnl_rt_after_aggr_del(struct ospf6 *ospf6,
					       struct ospf6_route *rt)
{
	struct ospf6_lsa *lsa;

	/* Process only marked external routes.
	 * These routes were part of a deleted
	 * aggregator.So, originate now.
	 */
	if (!rt->to_be_processed)
		return;

	rt->to_be_processed = false;

	lsa = ospf6_find_external_lsa(ospf6, &rt->prefix);

	if (lsa) {
		EVENT_OFF(lsa->refresh);
		event_add_event(master, ospf6_lsa_refresh, lsa, 0,
				&lsa->refresh);
	} else {
		if (IS_OSPF6_DEBUG_AGGR)
			zlog_debug("%s: Originate external route(%pFX)",
				__func__,
				&rt->prefix);

		(void)ospf6_originate_type5_type7_lsas(rt, ospf6);
	}
}

static void ospf6_handle_aggregated_exnl_rt(struct ospf6 *ospf6,
					   struct ospf6_external_aggr_rt *aggr,
					   struct ospf6_route *rt)
{
	struct ospf6_lsa *lsa;
	struct ospf6_as_external_lsa *ext_lsa;
	struct ospf6_external_info *info;

	/* Handling the case where the external route prefix
	 * and aggegate prefix is same
	 * If same don't flush the originated external LSA.
	 */
	if (prefix_same(&aggr->p, &rt->prefix)) {
		if (IS_OSPF6_DEBUG_AGGR)
			zlog_debug("%s: External Route prefix same as Aggregator(%pFX), so don't flush.",
				__func__,
				&rt->prefix);

		return;
	}

	info = rt->route_option;
	assert(info);

	lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL),
				htonl(info->id), ospf6->router_id, ospf6->lsdb);
	if (lsa) {
		ext_lsa = lsa_after_header(lsa->header);

		if (rt->prefix.prefixlen != ext_lsa->prefix.prefix_length)
			return;

		ospf6_external_lsa_purge(ospf6, lsa);

		/* Resetting the ID of route */
		rt->path.origin.id = 0;
		info->id = 0;
	}
}

static void
ospf6_handle_external_aggr_add(struct ospf6 *ospf6)
{
	struct ospf6_route *rt = NULL;
	struct ospf6_external_info *ei = NULL;
	struct ospf6_external_aggr_rt *aggr;

	/* Delete all the aggregators which are marked as
	 * OSPF6_ROUTE_AGGR_DEL.
	 */
	ospf6_delete_all_marked_aggregators(ospf6);

	for (rt = ospf6_route_head(ospf6->external_table); rt;
		rt = ospf6_route_next(rt)) {
		ei = rt->route_option;
		if (ei == NULL)
			continue;

		if (is_default_prefix(&rt->prefix))
			continue;

		aggr = ospf6_external_aggr_match(ospf6,
					&rt->prefix);

		/* If matching aggregator found, Add
		 * the external route refrenace to the
		 * aggregator and originate the aggr
		 * route if it is advertisable.
		 * flush the external LSA if it is
		 * already originated for this external
		 * prefix.
		 */
		if (aggr) {
			ospf6_originate_summary_lsa(ospf6, aggr, rt);

			/* All aggregated external rts
			 * are handled here.
			 */
			ospf6_handle_aggregated_exnl_rt(
				ospf6, aggr, rt);
			continue;
		}

		/* External routes which are only out
		 * of aggregation will be handled here.
		 */
		ospf6_handle_exnl_rt_after_aggr_del(
					ospf6, rt);
	}
}

static void ospf6_asbr_summary_process(struct event *thread)
{
	struct ospf6 *ospf6 = EVENT_ARG(thread);
	int operation = 0;

	operation = ospf6->aggr_action;

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("%s: operation:%d",
				__func__,
				operation);

	switch (operation) {
	case OSPF6_ROUTE_AGGR_ADD:
		ospf6_handle_external_aggr_add(ospf6);
		break;
	case OSPF6_ROUTE_AGGR_DEL:
	case OSPF6_ROUTE_AGGR_MODIFY:
		ospf6_handle_external_aggr_update(ospf6);
		break;
	default:
		break;
	}
}

static void
ospf6_start_asbr_summary_delay_timer(struct ospf6 *ospf6,
			struct ospf6_external_aggr_rt *aggr,
			ospf6_aggr_action_t operation)
{
	aggr->action = operation;

	if (event_is_scheduled(ospf6->t_external_aggr)) {
		if (ospf6->aggr_action == OSPF6_ROUTE_AGGR_ADD) {

			if (IS_OSPF6_DEBUG_AGGR)
				zlog_debug("%s: Not required to restart timer,set is already added.",
					__func__);
			return;
		}

		if (operation == OSPF6_ROUTE_AGGR_ADD) {
			if (IS_OSPF6_DEBUG_AGGR)
				zlog_debug("%s, Restarting Aggregator delay timer.",
							__func__);
			EVENT_OFF(ospf6->t_external_aggr);
		}
	}

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("%s: Start Aggregator delay timer %u(in seconds).",
			   __func__, ospf6->aggr_delay_interval);

	ospf6->aggr_action = operation;
	event_add_timer(master, ospf6_asbr_summary_process, ospf6,
			ospf6->aggr_delay_interval, &ospf6->t_external_aggr);
}

int ospf6_asbr_external_rt_advertise(struct ospf6 *ospf6,
				struct prefix *p)
{
	struct route_node *rn;
	struct ospf6_external_aggr_rt *aggr;

	rn = route_node_lookup(ospf6->rt_aggr_tbl, p);
	if (!rn)
		return OSPF6_INVALID;

	aggr = rn->info;

	route_unlock_node(rn);

	if (!CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE))
		return OSPF6_INVALID;

	UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE);

	if (!OSPF6_EXTERNAL_RT_COUNT(aggr))
		return OSPF6_SUCCESS;

	ospf6_start_asbr_summary_delay_timer(ospf6, aggr,
					     OSPF6_ROUTE_AGGR_MODIFY);

	return OSPF6_SUCCESS;
}

int ospf6_external_aggr_delay_timer_set(struct ospf6 *ospf6, uint16_t interval)
{
	ospf6->aggr_delay_interval = interval;

	return OSPF6_SUCCESS;
}

static unsigned int ospf6_external_rt_hash_key(const void *data)
{
	const struct ospf6_route *rt = data;
	unsigned int key = 0;

	key = prefix_hash_key(&rt->prefix);
	return key;
}

static bool ospf6_external_rt_hash_cmp(const void *d1, const void *d2)
{
	const struct ospf6_route *rt1 = d1;
	const struct ospf6_route *rt2 = d2;

	return prefix_same(&rt1->prefix, &rt2->prefix);
}

static struct ospf6_external_aggr_rt *
ospf6_external_aggr_new(struct prefix *p)
{
	struct ospf6_external_aggr_rt *aggr;

	aggr = XCALLOC(MTYPE_OSPF6_EXTERNAL_RT_AGGR,
		       sizeof(struct ospf6_external_aggr_rt));

	prefix_copy(&aggr->p, p);
	aggr->metric = -1;
	aggr->mtype = DEFAULT_METRIC_TYPE;
	aggr->match_extnl_hash = hash_create(ospf6_external_rt_hash_key,
					     ospf6_external_rt_hash_cmp,
					     "Ospf6 external route hash");
	return aggr;
}

static void ospf6_external_aggr_add(struct ospf6 *ospf6,
		struct ospf6_external_aggr_rt *aggr)
{
	struct route_node *rn;

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("%s: Adding Aggregate route to Aggr table (%pFX)",
					__func__,
					&aggr->p);

	rn = route_node_get(ospf6->rt_aggr_tbl, &aggr->p);
	if (rn->info)
		route_unlock_node(rn);
	else
		rn->info = aggr;
}

int ospf6_asbr_external_rt_no_advertise(struct ospf6 *ospf6,
				struct prefix *p)
{
	struct ospf6_external_aggr_rt *aggr;
	route_tag_t tag = 0;

	aggr = ospf6_external_aggr_config_lookup(ospf6, p);
	if (aggr) {
		if (CHECK_FLAG(aggr->aggrflags,
			OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE))
			return OSPF6_SUCCESS;

		SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE);

		aggr->tag = tag;
		aggr->metric = -1;

		if (!OSPF6_EXTERNAL_RT_COUNT(aggr))
			return OSPF6_SUCCESS;

		ospf6_start_asbr_summary_delay_timer(ospf6, aggr,
			OSPF6_ROUTE_AGGR_MODIFY);
	} else {
		aggr = ospf6_external_aggr_new(p);

		if (!aggr)
			return OSPF6_FAILURE;

		SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE);
		ospf6_external_aggr_add(ospf6, aggr);
		ospf6_start_asbr_summary_delay_timer(ospf6, aggr,
					OSPF6_ROUTE_AGGR_ADD);
	}

	return OSPF6_SUCCESS;
}

struct ospf6_external_aggr_rt *
ospf6_external_aggr_config_lookup(struct ospf6 *ospf6, struct prefix *p)
{
	struct route_node *rn;

	rn = route_node_lookup(ospf6->rt_aggr_tbl, p);
	if (rn) {
		route_unlock_node(rn);
		return rn->info;
	}

	return NULL;
}


int ospf6_external_aggr_config_set(struct ospf6 *ospf6, struct prefix *p,
				      route_tag_t tag, int metric, int mtype)
{
	struct ospf6_external_aggr_rt *aggregator;

	aggregator = ospf6_external_aggr_config_lookup(ospf6, p);

	if (aggregator) {
		if (CHECK_FLAG(aggregator->aggrflags,
			       OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE))
			UNSET_FLAG(aggregator->aggrflags,
				   OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE);
		else if ((aggregator->tag == tag)
			 && (aggregator->metric == metric)
			 && (aggregator->mtype == mtype))
			return OSPF6_SUCCESS;

		aggregator->tag = tag;
		aggregator->metric = metric;
		aggregator->mtype = mtype;

		ospf6_start_asbr_summary_delay_timer(ospf6, aggregator,
					 OSPF6_ROUTE_AGGR_MODIFY);
	} else {
		aggregator = ospf6_external_aggr_new(p);
		if (!aggregator)
			return OSPF6_FAILURE;

		aggregator->tag = tag;
		aggregator->metric = metric;
		aggregator->mtype = mtype;

		ospf6_external_aggr_add(ospf6, aggregator);
		ospf6_start_asbr_summary_delay_timer(ospf6, aggregator,
						OSPF6_ROUTE_AGGR_ADD);
	}

	return OSPF6_SUCCESS;
}

int ospf6_external_aggr_config_unset(struct ospf6 *ospf6,
					struct prefix *p)
{
	struct route_node *rn;
	struct ospf6_external_aggr_rt *aggr;

	rn = route_node_lookup(ospf6->rt_aggr_tbl, p);
	if (!rn)
		return OSPF6_INVALID;

	aggr = rn->info;

	route_unlock_node(rn);

	if (!OSPF6_EXTERNAL_RT_COUNT(aggr)) {
		ospf6_asbr_summary_config_delete(ospf6, rn);
		ospf6_external_aggregator_free(aggr);
		return OSPF6_SUCCESS;
	}

	ospf6_start_asbr_summary_delay_timer(ospf6, aggr,
				OSPF6_ROUTE_AGGR_DEL);

	return OSPF6_SUCCESS;
}

void ospf6_handle_external_lsa_origination(struct ospf6 *ospf6,
					       struct ospf6_route *rt,
					       struct prefix *p)
{

	struct ospf6_external_aggr_rt *aggr;
	struct ospf6_external_info *info;
	struct prefix prefix_id;

	if (!is_default_prefix(p)) {
		aggr = ospf6_external_aggr_match(ospf6,
						p);

		if (aggr) {

			if (IS_OSPF6_DEBUG_AGGR)
				zlog_debug("%s: Send Aggregate LSA (%pFX)",
				__func__,
				&aggr->p);

			ospf6_originate_summary_lsa(
				ospf6, aggr, rt);

			/* Handling the case where the
			 * external route prefix
			 * and aggegate prefix is same
			 * If same don't flush the
			 * originated
			 * external LSA.
			 */
			ospf6_handle_aggregated_exnl_rt(
					ospf6, aggr, rt);
			return;
		}
	}

	info = rt->route_option;

	/* When the info->id = 0, it means it is being originated for the
	 * first time.
	 */
	if (!info->id) {
		info->id = ospf6->external_id++;
	} else {
		prefix_id.family = AF_INET;
		prefix_id.prefixlen = 32;
		prefix_id.u.prefix4.s_addr = htonl(info->id);
	}

	rt->path.origin.id = htonl(info->id);

	if (IS_OSPF6_DEBUG_ASBR) {
		zlog_debug("Advertise new AS-External Id:%pI4 prefix %pFX metric %u",
			   &prefix_id.u.prefix4, p, rt->path.metric_type);
	}

	ospf6_originate_type5_type7_lsas(rt, ospf6);

}

void ospf6_unset_all_aggr_flag(struct ospf6 *ospf6)
{
	struct route_node *rn = NULL;
	struct ospf6_external_aggr_rt *aggr;

	if (IS_OSPF6_DEBUG_AGGR)
		zlog_debug("Unset the origination bit for all aggregator");

	/* Resetting the running external ID counter so that the origination
	 * of external LSAs starts from the beginning 0.0.0.1
	 */
	ospf6->external_id = OSPF6_EXT_INIT_LS_ID;

	for (rn = route_top(ospf6->rt_aggr_tbl); rn; rn = route_next(rn)) {
		if (!rn->info)
			continue;

		aggr = rn->info;

		UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED);
	}
}
