// SPDX-License-Identifier: GPL-2.0-or-later
/* Zebra MPLS code
 * Copyright (C) 2013 Cumulus Networks, Inc.
 */

#include <zebra.h>

#include "prefix.h"
#include "table.h"
#include "memory.h"
#include "command.h"
#include "if.h"
#include "log.h"
#include "sockunion.h"
#include "linklist.h"
#include "frrevent.h"
#include "workqueue.h"
#include "prefix.h"
#include "routemap.h"
#include "stream.h"
#include "nexthop.h"
#include "termtable.h"
#include "lib/json.h"

#include "zebra/rib.h"
#include "zebra/rt.h"
#include "zebra/interface.h"
#include "zebra/zserv.h"
#include "zebra/zebra_router.h"
#include "zebra/redistribute.h"
#include "zebra/debug.h"
#include "zebra/zebra_vrf.h"
#include "zebra/zebra_mpls.h"
#include "zebra/zebra_srte.h"
#include "zebra/zebra_errors.h"

DEFINE_MTYPE_STATIC(ZEBRA, LSP, "MPLS LSP object");
DEFINE_MTYPE_STATIC(ZEBRA, FEC, "MPLS FEC object");
DEFINE_MTYPE_STATIC(ZEBRA, NHLFE, "MPLS nexthop object");
DEFINE_MTYPE_STATIC(ZEBRA, NH_LABEL, "Nexthop label");

bool mpls_enabled;
bool mpls_pw_reach_strict; /* Strict reachability checking */

/* static function declarations */

static void fec_evaluate(struct zebra_vrf *zvrf);
static uint32_t fec_derive_label_from_index(struct zebra_vrf *vrf,
					    struct zebra_fec *fec);
static int lsp_install(struct zebra_vrf *zvrf, mpls_label_t label,
		       struct route_node *rn, struct route_entry *re);
static int lsp_uninstall(struct zebra_vrf *zvrf, mpls_label_t label);
static int fec_change_update_lsp(struct zebra_vrf *zvrf, struct zebra_fec *fec,
				 mpls_label_t old_label, bool uninstall);
static int fec_send(struct zebra_fec *fec, struct zserv *client);
static void fec_update_clients(struct zebra_fec *fec);
static void fec_print(struct zebra_fec *fec, struct vty *vty);
static struct zebra_fec *fec_find(struct route_table *table, struct prefix *p);
static struct zebra_fec *fec_add(struct route_table *table, struct prefix *p,
				 mpls_label_t label, uint32_t flags,
				 uint32_t label_index);
static int fec_del(struct zebra_fec *fec);

static unsigned int label_hash(const void *p);
static bool label_cmp(const void *p1, const void *p2);
static int nhlfe_nexthop_active_ipv4(struct zebra_nhlfe *nhlfe,
				     struct nexthop *nexthop);
static int nhlfe_nexthop_active_ipv6(struct zebra_nhlfe *nhlfe,
				     struct nexthop *nexthop);
static int nhlfe_nexthop_active(struct zebra_nhlfe *nhlfe);

static void lsp_select_best_nhlfe(struct zebra_lsp *lsp);
static void lsp_uninstall_from_kernel(struct hash_bucket *bucket, void *ctxt);
static void lsp_schedule(struct hash_bucket *bucket, void *ctxt);
static wq_item_status lsp_process(struct work_queue *wq, void *data);
static void lsp_processq_del(struct work_queue *wq, void *data);
static void lsp_processq_complete(struct work_queue *wq);
static int lsp_processq_add(struct zebra_lsp *lsp);
static void *lsp_alloc(void *p);

/* Check whether lsp can be freed - no nhlfes, e.g., and call free api */
static void lsp_check_free(struct hash *lsp_table, struct zebra_lsp **plsp);

/* Free lsp; sets caller's pointer to NULL */
static void lsp_free(struct hash *lsp_table, struct zebra_lsp **plsp);

static char *nhlfe2str(const struct zebra_nhlfe *nhlfe, char *buf, int size);
static char *nhlfe_config_str(const struct zebra_nhlfe *nhlfe, char *buf,
			      int size);
static int nhlfe_nhop_match(struct zebra_nhlfe *nhlfe,
			    enum nexthop_types_t gtype,
			    const union g_addr *gate, ifindex_t ifindex);
static struct zebra_nhlfe *nhlfe_find(struct nhlfe_list_head *list,
				      enum lsp_types_t lsp_type,
				      enum nexthop_types_t gtype,
				      const union g_addr *gate,
				      ifindex_t ifindex);
static struct zebra_nhlfe *
nhlfe_add(struct zebra_lsp *lsp, enum lsp_types_t lsp_type,
	  enum nexthop_types_t gtype, const union g_addr *gate,
	  ifindex_t ifindex, vrf_id_t vrf_id, uint8_t num_labels,
	  const mpls_label_t *labels, bool is_backup);
static int nhlfe_del(struct zebra_nhlfe *nhlfe);
static void nhlfe_free(struct zebra_nhlfe *nhlfe);
static void nhlfe_out_label_update(struct zebra_nhlfe *nhlfe,
				   struct mpls_label_stack *nh_label);
static int mpls_lsp_uninstall_all(struct hash *lsp_table, struct zebra_lsp *lsp,
				  enum lsp_types_t type);
static int mpls_static_lsp_uninstall_all(struct zebra_vrf *zvrf,
					 mpls_label_t in_label);
static void nhlfe_print(struct zebra_nhlfe *nhlfe, struct vty *vty,
			const char *indent);
static void lsp_print(struct vty *vty, struct zebra_lsp *lsp);
static void mpls_lsp_uninstall_all_type(struct hash_bucket *bucket, void *ctxt);
static void mpls_ftn_uninstall_all(struct zebra_vrf *zvrf,
				   int afi, enum lsp_types_t lsp_type);
static int lsp_znh_install(struct zebra_lsp *lsp, enum lsp_types_t type,
			   const struct zapi_nexthop *znh);
static int lsp_backup_znh_install(struct zebra_lsp *lsp, enum lsp_types_t type,
				  const struct zapi_nexthop *znh);

/* Static functions */

/*
 * Handle failure in LSP install, clear flags for NHLFE.
 */
static void clear_nhlfe_installed(struct zebra_lsp *lsp)
{
	struct zebra_nhlfe *nhlfe;
	struct nexthop *nexthop;

	frr_each_safe(nhlfe_list, &lsp->nhlfe_list, nhlfe) {
		nexthop = nhlfe->nexthop;
		if (!nexthop)
			continue;

		UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED);
		UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB);
	}

	frr_each_safe(nhlfe_list, &lsp->backup_nhlfe_list, nhlfe) {
		nexthop = nhlfe->nexthop;
		if (!nexthop)
			continue;

		UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED);
		UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB);
	}
}

/*
 * Install label forwarding entry based on labeled-route entry.
 */
static int lsp_install(struct zebra_vrf *zvrf, mpls_label_t label,
		       struct route_node *rn, struct route_entry *re)
{
	struct hash *lsp_table;
	struct zebra_ile tmp_ile;
	struct zebra_lsp *lsp;
	struct zebra_nhlfe *nhlfe;
	struct nexthop *nexthop;
	enum lsp_types_t lsp_type;
	char buf[BUFSIZ];
	int added, changed;
	bool zvrf_nexthop_resolution;

	/* Lookup table. */
	lsp_table = zvrf->lsp_table;
	if (!lsp_table)
		return -1;

	zvrf_nexthop_resolution = zvrf->zebra_mpls_fec_nexthop_resolution;
	lsp_type = lsp_type_from_re_type(re->type);
	added = changed = 0;

	/* Locate or allocate LSP entry. */
	tmp_ile.in_label = label;
	lsp = hash_get(lsp_table, &tmp_ile, lsp_alloc);

	/* For each active nexthop, create NHLFE. Note that we deliberately skip
	 * recursive nexthops right now, because intermediate hops won't
	 * understand
	 * the label advertised by the recursive nexthop (plus we don't have the
	 * logic yet to push multiple labels).
	 */
	nexthop = re->nhe->nhg.nexthop;
	while (nexthop) {
		if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) {
			nexthop =
				nexthop_next_resolution(nexthop,
							zvrf_nexthop_resolution);
			continue;
		}
		if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) {
			nexthop =
				nexthop_next_resolution(nexthop,
							zvrf_nexthop_resolution);
			continue;
		}

		nhlfe = nhlfe_find(&lsp->nhlfe_list, lsp_type,
				   nexthop->type, &nexthop->gate,
				   nexthop->ifindex);
		if (nhlfe) {
			/* Clear deleted flag (in case it was set) */
			UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED);
			if (nexthop_labels_match(nhlfe->nexthop, nexthop)) {
				/* No change */
				nexthop =
					nexthop_next_resolution(nexthop,
								zvrf_nexthop_resolution);
				continue;
			}


			if (IS_ZEBRA_DEBUG_MPLS) {
				nhlfe2str(nhlfe, buf, BUFSIZ);
				zlog_debug(
					"LSP in-label %u type %d nexthop %s out-label changed",
					lsp->ile.in_label, lsp_type, buf);
			}

			/* Update out label, trigger processing. */
			nhlfe_out_label_update(nhlfe, nexthop->nh_label);
			SET_FLAG(nhlfe->flags, NHLFE_FLAG_CHANGED);
			changed++;
		} else {
			/* Add LSP entry to this nexthop */
			nhlfe = nhlfe_add(
				lsp, lsp_type, nexthop->type, &nexthop->gate,
				nexthop->ifindex, nexthop->vrf_id,
				nexthop->nh_label->num_labels,
				nexthop->nh_label->label, false /*backup*/);
			if (!nhlfe)
				return -1;

			if (IS_ZEBRA_DEBUG_MPLS) {
				char label_str[MPLS_LABEL_STRLEN];

				nhlfe2str(nhlfe, buf, BUFSIZ);
				zlog_debug("Add LSP in-label %u type %d nexthop %s out-label %s",
					   lsp->ile.in_label, lsp_type, buf,
					   mpls_label2str(nexthop->nh_label
								  ->num_labels,
							  nexthop->nh_label->label,
							  label_str,
							  sizeof(label_str),
							  nexthop->nh_label_type,
							  0));
			}

			lsp->addr_family = NHLFE_FAMILY(nhlfe);

			/* Mark NHLFE as changed. */
			SET_FLAG(nhlfe->flags, NHLFE_FLAG_CHANGED);
			added++;
		}
		nexthop = nexthop_next_resolution(nexthop,
						  zvrf_nexthop_resolution);
	}

	/* Queue LSP for processing if necessary. If no NHLFE got added (special
	 * case), delete the LSP entry; this case results in somewhat ugly
	 * logging.
	 */
	if (added || changed) {
		if (lsp_processq_add(lsp))
			return -1;
	} else {
		lsp_check_free(lsp_table, &lsp);
		/* failed to install a new LSP */
		return -1;
	}

	return 0;
}

/*
 * Uninstall all non-static NHLFEs of a label forwarding entry. If all
 * NHLFEs are removed, the entire entry is deleted.
 */
static int lsp_uninstall(struct zebra_vrf *zvrf, mpls_label_t label)
{
	struct hash *lsp_table;
	struct zebra_ile tmp_ile;
	struct zebra_lsp *lsp;
	struct zebra_nhlfe *nhlfe;
	char buf[BUFSIZ];

	/* Lookup table. */
	lsp_table = zvrf->lsp_table;
	if (!lsp_table)
		return -1;

	/* If entry is not present, exit. */
	tmp_ile.in_label = label;
	lsp = hash_lookup(lsp_table, &tmp_ile);
	if (!lsp || (nhlfe_list_first(&lsp->nhlfe_list) == NULL))
		return 0;

	/* Mark NHLFEs for delete or directly delete, as appropriate. */
	frr_each_safe(nhlfe_list, &lsp->nhlfe_list, nhlfe) {

		/* Skip static NHLFEs */
		if (nhlfe->type == ZEBRA_LSP_STATIC)
			continue;

		if (IS_ZEBRA_DEBUG_MPLS) {
			nhlfe2str(nhlfe, buf, BUFSIZ);
			zlog_debug(
				"Del LSP in-label %u type %d nexthop %s flags 0x%x",
				label, nhlfe->type, buf, nhlfe->flags);
		}

		if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_SELECTED)) {
			UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_CHANGED);
			SET_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED);
		} else {
			nhlfe_del(nhlfe);
		}
	}

	/* Queue LSP for processing, if needed, else delete. */
	if (CHECK_FLAG(lsp->flags, LSP_FLAG_INSTALLED)) {
		if (lsp_processq_add(lsp))
			return -1;
	} else {
		lsp_check_free(lsp_table, &lsp);
	}

	return 0;
}

/*
 * This function is invoked upon change to label block configuration; it
 * will walk all registered FECs with label-index and appropriately update
 * their local labels and trigger client updates.
 */
static void fec_evaluate(struct zebra_vrf *zvrf)
{
	struct route_node *rn;
	struct zebra_fec *fec;
	uint32_t old_label, new_label;
	int af;

	for (af = AFI_IP; af < AFI_MAX; af++) {
		if (zvrf->fec_table[af] == NULL)
			continue;

		for (rn = route_top(zvrf->fec_table[af]); rn;
		     rn = route_next(rn)) {
			if ((fec = rn->info) == NULL)
				continue;

			/* Skip configured FECs and those without a label index.
			 */
			if (CHECK_FLAG(fec->flags, FEC_FLAG_CONFIGURED)
			    || fec->label_index == MPLS_INVALID_LABEL_INDEX)
				continue;

			/* Save old label, determine new label. */
			old_label = fec->label;
			new_label =
				zvrf->mpls_srgb.start_label + fec->label_index;
			if (new_label >= zvrf->mpls_srgb.end_label)
				new_label = MPLS_INVALID_LABEL;

			/* If label has changed, update FEC and clients. */
			if (new_label == old_label)
				continue;

			if (IS_ZEBRA_DEBUG_MPLS)
				zlog_debug(
					"Update fec %pRN new label %u upon label block",
					rn, new_label);

			fec->label = new_label;
			fec_update_clients(fec);

			/* Update label forwarding entries appropriately */
			fec_change_update_lsp(zvrf, fec, old_label, false);
		}
	}
}

/*
 * Derive (if possible) and update the local label for the FEC based on
 * its label index. The index is "acceptable" if it falls within the
 * globally configured label block (SRGB).
 */
static uint32_t fec_derive_label_from_index(struct zebra_vrf *zvrf,
					    struct zebra_fec *fec)
{
	uint32_t label;

	if (fec->label_index != MPLS_INVALID_LABEL_INDEX
	    && zvrf->mpls_srgb.start_label
	    && ((label = zvrf->mpls_srgb.start_label + fec->label_index)
		< zvrf->mpls_srgb.end_label))
		fec->label = label;
	else
		fec->label = MPLS_INVALID_LABEL;

	return fec->label;
}

/*
 * There is a change for this FEC. Install or uninstall label forwarding
 * entries, as appropriate.
 */
static int fec_change_update_lsp(struct zebra_vrf *zvrf, struct zebra_fec *fec,
				 mpls_label_t old_label, bool uninstall)
{
	struct route_table *table;
	struct route_node *rn;
	struct route_entry *re;
	afi_t afi;

	/* Uninstall label forwarding entry, if previously installed. */
	if (old_label != MPLS_INVALID_LABEL
	    && old_label != MPLS_LABEL_IMPLICIT_NULL)
		lsp_uninstall(zvrf, old_label);

	/* Install label forwarding entry corr. to new label, if needed. */
	if (fec->label == MPLS_INVALID_LABEL
	    || fec->label == MPLS_LABEL_IMPLICIT_NULL)
		return 0;

	afi = family2afi(PREFIX_FAMILY(&fec->rn->p));
	table = zebra_vrf_table(afi, SAFI_UNICAST, zvrf_id(zvrf));
	if (!table)
		return 0;

	/* See if labeled route exists. */
	rn = route_node_lookup(table, &fec->rn->p);
	if (!rn)
		return 0;

	RNODE_FOREACH_RE (rn, re) {
		if (CHECK_FLAG(re->flags, ZEBRA_FLAG_SELECTED))
			break;
	}

	if (!re || !zebra_rib_labeled_unicast(re)) {
		if (uninstall)
			lsp_uninstall(zvrf, fec->label);
		return 0;
	}

	if (lsp_install(zvrf, fec->label, rn, re)) {
		if (uninstall)
			lsp_uninstall(zvrf, fec->label);
		return -1;
	}

	return 0;
}

/*
 * Inform about FEC to a registered client.
 */
static int fec_send(struct zebra_fec *fec, struct zserv *client)
{
	struct stream *s;
	struct route_node *rn;

	rn = fec->rn;

	/* Get output stream. */
	s = stream_new(ZEBRA_MAX_PACKET_SIZ);

	zclient_create_header(s, ZEBRA_FEC_UPDATE, VRF_DEFAULT);

	stream_putw(s, rn->p.family);
	stream_put_prefix(s, &rn->p);
	stream_putl(s, fec->label);
	stream_putw_at(s, 0, stream_get_endp(s));
	return zserv_send_message(client, s);
}

/*
 * Upon reconfiguring nexthop-resolution updates, update the
 * lsp entries accordingly.
 */
void zebra_mpls_fec_nexthop_resolution_update(struct zebra_vrf *zvrf)
{
	int af;
	struct route_node *rn;
	struct zebra_fec *fec;

	for (af = AFI_IP; af < AFI_MAX; af++) {
		if (zvrf->fec_table[af] == NULL)
			continue;
		for (rn = route_top(zvrf->fec_table[af]); rn;
		     rn = route_next(rn)) {
			if (!rn->info)
				continue;
			fec = rn->info;
			fec_change_update_lsp(zvrf, fec, MPLS_INVALID_LABEL,
					      true);
		}
	}
}

/*
 * Update all registered clients about this FEC. Caller should've updated
 * FEC and ensure no duplicate updates.
 */
static void fec_update_clients(struct zebra_fec *fec)
{
	struct listnode *node;
	struct zserv *client;

	for (ALL_LIST_ELEMENTS_RO(fec->client_list, node, client)) {
		if (IS_ZEBRA_DEBUG_MPLS)
			zlog_debug("Update client %s",
				   zebra_route_string(client->proto));
		fec_send(fec, client);
	}
}


/*
 * Print a FEC-label binding entry.
 */
static void fec_print(struct zebra_fec *fec, struct vty *vty)
{
	struct route_node *rn;
	struct listnode *node;
	struct zserv *client;
	char buf[BUFSIZ];

	rn = fec->rn;
	vty_out(vty, "%pRN\n", rn);
	vty_out(vty, "  Label: %s", label2str(fec->label, 0, buf, BUFSIZ));
	if (fec->label_index != MPLS_INVALID_LABEL_INDEX)
		vty_out(vty, ", Label Index: %u", fec->label_index);
	vty_out(vty, "\n");
	if (!list_isempty(fec->client_list)) {
		vty_out(vty, "  Client list:");
		for (ALL_LIST_ELEMENTS_RO(fec->client_list, node, client))
			vty_out(vty, " %s(fd %d)",
				zebra_route_string(client->proto),
				client->sock);
		vty_out(vty, "\n");
	}
}

/*
 * Locate FEC-label binding that matches with passed info.
 */
static struct zebra_fec *fec_find(struct route_table *table, struct prefix *p)
{
	struct route_node *rn;

	apply_mask(p);
	rn = route_node_lookup(table, p);
	if (!rn)
		return NULL;

	route_unlock_node(rn);
	return (rn->info);
}

/*
 * Add a FEC. This may be upon a client registering for a binding
 * or when a binding is configured.
 */
static struct zebra_fec *fec_add(struct route_table *table, struct prefix *p,
				 mpls_label_t label, uint32_t flags,
				 uint32_t label_index)
{
	struct route_node *rn;
	struct zebra_fec *fec;

	apply_mask(p);

	/* Lookup (or add) route node.*/
	rn = route_node_get(table, p);
	if (!rn)
		return NULL;

	fec = rn->info;

	if (!fec) {
		fec = XCALLOC(MTYPE_FEC, sizeof(struct zebra_fec));

		rn->info = fec;
		fec->rn = rn;
		fec->label = label;
		fec->client_list = list_new();
	} else
		route_unlock_node(rn); /* for the route_node_get */

	fec->label_index = label_index;
	fec->flags = flags;

	return fec;
}

/*
 * Delete a FEC. This may be upon the last client deregistering for
 * a FEC and no binding exists or when the binding is deleted and there
 * are no registered clients.
 */
static int fec_del(struct zebra_fec *fec)
{
	list_delete(&fec->client_list);
	fec->rn->info = NULL;
	route_unlock_node(fec->rn);
	XFREE(MTYPE_FEC, fec);
	return 0;
}

/*
 * Hash function for label.
 */
static unsigned int label_hash(const void *p)
{
	const struct zebra_ile *ile = p;

	return (jhash_1word(ile->in_label, 0));
}

/*
 * Compare 2 LSP hash entries based on in-label.
 */
static bool label_cmp(const void *p1, const void *p2)
{
	const struct zebra_ile *ile1 = p1;
	const struct zebra_ile *ile2 = p2;

	return (ile1->in_label == ile2->in_label);
}

/*
 * Check if an IPv4 nexthop for a NHLFE is active. Update nexthop based on
 * the passed flag.
 * NOTE: Looking only for connected routes right now.
 */
static int nhlfe_nexthop_active_ipv4(struct zebra_nhlfe *nhlfe,
				     struct nexthop *nexthop)
{
	struct route_table *table;
	struct prefix_ipv4 p;
	struct route_node *rn;
	struct route_entry *match;
	struct nexthop *match_nh;

	table = zebra_vrf_table(AFI_IP, SAFI_UNICAST, nexthop->vrf_id);
	if (!table)
		return 0;

	/* Lookup nexthop in IPv4 routing table. */
	memset(&p, 0, sizeof(p));
	p.family = AF_INET;
	p.prefixlen = IPV4_MAX_BITLEN;
	p.prefix = nexthop->gate.ipv4;

	rn = route_node_match(table, (struct prefix *)&p);
	if (!rn)
		return 0;

	route_unlock_node(rn);

	/* Locate a valid connected route. */
	RNODE_FOREACH_RE (rn, match) {
		if (CHECK_FLAG(match->status, ROUTE_ENTRY_REMOVED)
		    || !CHECK_FLAG(match->flags, ZEBRA_FLAG_SELECTED))
			continue;

		for (match_nh = match->nhe->nhg.nexthop; match_nh;
		     match_nh = match_nh->next) {
			if ((match->type == ZEBRA_ROUTE_CONNECT ||
			     match->type == ZEBRA_ROUTE_LOCAL) ||
			    nexthop->ifindex == match_nh->ifindex) {
				nexthop->ifindex = match_nh->ifindex;
				return 1;
			}
		}
	}

	return 0;
}


/*
 * Check if an IPv6 nexthop for a NHLFE is active. Update nexthop based on
 * the passed flag.
 * NOTE: Looking only for connected routes right now.
 */
static int nhlfe_nexthop_active_ipv6(struct zebra_nhlfe *nhlfe,
				     struct nexthop *nexthop)
{
	struct route_table *table;
	struct prefix_ipv6 p;
	struct route_node *rn;
	struct route_entry *match;

	table = zebra_vrf_table(AFI_IP6, SAFI_UNICAST, nexthop->vrf_id);
	if (!table)
		return 0;

	/* Lookup nexthop in IPv6 routing table. */
	memset(&p, 0, sizeof(p));
	p.family = AF_INET6;
	p.prefixlen = IPV6_MAX_BITLEN;
	p.prefix = nexthop->gate.ipv6;

	rn = route_node_match(table, (struct prefix *)&p);
	if (!rn)
		return 0;

	route_unlock_node(rn);

	/* Locate a valid connected route. */
	RNODE_FOREACH_RE (rn, match) {
		if (((match->type == ZEBRA_ROUTE_CONNECT ||
		      match->type == ZEBRA_ROUTE_LOCAL)) &&
		    !CHECK_FLAG(match->status, ROUTE_ENTRY_REMOVED) &&
		    CHECK_FLAG(match->flags, ZEBRA_FLAG_SELECTED))
			break;
	}

	if (!match || !match->nhe->nhg.nexthop)
		return 0;

	nexthop->ifindex = match->nhe->nhg.nexthop->ifindex;
	return 1;
}


/*
 * Check the nexthop reachability for a NHLFE and return if valid (reachable)
 * or not.
 * NOTE: Each NHLFE points to only 1 nexthop.
 */
static int nhlfe_nexthop_active(struct zebra_nhlfe *nhlfe)
{
	struct nexthop *nexthop;
	struct interface *ifp;
	struct zebra_ns *zns;

	nexthop = nhlfe->nexthop;
	if (!nexthop) // unexpected
		return 0;

	/* Check on nexthop based on type. */
	switch (nexthop->type) {
	case NEXTHOP_TYPE_IFINDEX:
		/*
		 * Lookup if this type is special.  The
		 * NEXTHOP_TYPE_IFINDEX is a pop and
		 * forward into a different table for
		 * processing.  As such this ifindex
		 * passed to us may be a VRF device
		 * which will not be in the default
		 * VRF.  So let's look in all of them
		 */
		zns = zebra_ns_lookup(NS_DEFAULT);
		ifp = if_lookup_by_index_per_ns(zns, nexthop->ifindex);
		if (ifp && if_is_operative(ifp))
			SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
		else
			UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
		break;
	case NEXTHOP_TYPE_IPV4:
	case NEXTHOP_TYPE_IPV4_IFINDEX:
		if (nhlfe_nexthop_active_ipv4(nhlfe, nexthop))
			SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
		else
			UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
		break;

	case NEXTHOP_TYPE_IPV6:
		if (nhlfe_nexthop_active_ipv6(nhlfe, nexthop))
			SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
		else
			UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
		break;

	case NEXTHOP_TYPE_IPV6_IFINDEX:
		if (IN6_IS_ADDR_LINKLOCAL(&nexthop->gate.ipv6)) {
			ifp = if_lookup_by_index(nexthop->ifindex,
						 nexthop->vrf_id);
			if (ifp && if_is_operative(ifp))
				SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
			else
				UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
		} else {
			if (nhlfe_nexthop_active_ipv6(nhlfe, nexthop))
				SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
			else
				UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
		}
		break;

	case NEXTHOP_TYPE_BLACKHOLE:
		break;
	}

	return CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
}

/*
 * Walk through NHLFEs for a LSP forwarding entry, verify nexthop
 * reachability and select the best. Multipath entries are also
 * marked. This is invoked when an LSP scheduled for processing (due
 * to some change) is examined.
 */
static void lsp_select_best_nhlfe(struct zebra_lsp *lsp)
{
	struct zebra_nhlfe *nhlfe;
	struct zebra_nhlfe *best;
	struct nexthop *nexthop;
	int changed = 0;

	if (!lsp)
		return;

	best = NULL;
	lsp->num_ecmp = 0;
	UNSET_FLAG(lsp->flags, LSP_FLAG_CHANGED);

	/*
	 * First compute the best path, after checking nexthop status. We are
	 * only concerned with non-deleted NHLFEs.
	 */
	frr_each_safe(nhlfe_list, &lsp->nhlfe_list, nhlfe) {
		/* Clear selection flags. */
		UNSET_FLAG(nhlfe->flags,
			   (NHLFE_FLAG_SELECTED | NHLFE_FLAG_MULTIPATH));

		if (!CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED)
		    && nhlfe_nexthop_active(nhlfe)) {
			if (!best || (nhlfe->distance < best->distance))
				best = nhlfe;
		}
	}

	lsp->best_nhlfe = best;
	if (!lsp->best_nhlfe)
		return;

	/*
	 * Check the active status of backup nhlfes also
	 */
	frr_each_safe(nhlfe_list, &lsp->backup_nhlfe_list, nhlfe) {
		if (!CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED))
			(void)nhlfe_nexthop_active(nhlfe);
	}

	/* Mark best NHLFE as selected. */
	SET_FLAG(lsp->best_nhlfe->flags, NHLFE_FLAG_SELECTED);

	/*
	 * If best path exists, see if there is ECMP. While doing this, note if
	 * a
	 * new (uninstalled) NHLFE has been selected, an installed entry that is
	 * still selected has a change or an installed entry is to be removed.
	 */
	frr_each(nhlfe_list, &lsp->nhlfe_list, nhlfe) {
		int nh_chg, nh_sel, nh_inst;

		nexthop = nhlfe->nexthop;
		if (!nexthop) // unexpected
			continue;

		if (!CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED)
		    && CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)
		    && (nhlfe->distance == lsp->best_nhlfe->distance)) {
			SET_FLAG(nhlfe->flags, NHLFE_FLAG_SELECTED);
			SET_FLAG(nhlfe->flags, NHLFE_FLAG_MULTIPATH);
			lsp->num_ecmp++;
		}

		if (CHECK_FLAG(lsp->flags, LSP_FLAG_INSTALLED) && !changed) {
			nh_chg = CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_CHANGED);
			nh_sel = CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_SELECTED);
			nh_inst =
				CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED);

			if ((nh_sel && !nh_inst)
			    || (nh_sel && nh_inst && nh_chg)
			    || (nh_inst && !nh_sel))
				changed = 1;
		}

		/* We have finished examining, clear changed flag. */
		UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_CHANGED);
	}

	if (changed)
		SET_FLAG(lsp->flags, LSP_FLAG_CHANGED);
}

/*
 * Delete LSP forwarding entry from kernel, if installed. Called upon
 * process exit.
 */
static void lsp_uninstall_from_kernel(struct hash_bucket *bucket, void *ctxt)
{
	struct zebra_lsp *lsp;

	lsp = (struct zebra_lsp *)bucket->data;
	if (CHECK_FLAG(lsp->flags, LSP_FLAG_INSTALLED))
		(void)dplane_lsp_delete(lsp);
}

/*
 * Schedule LSP forwarding entry for processing. Called upon changes
 * that may impact LSPs such as nexthop / connected route changes.
 */
static void lsp_schedule(struct hash_bucket *bucket, void *ctxt)
{
	struct zebra_lsp *lsp;

	lsp = (struct zebra_lsp *)bucket->data;

	/* In the common flow, this is used when external events occur. For
	 * LSPs with backup nhlfes, we'll assume that the forwarding
	 * plane will use the backups to handle these events, until the
	 * owning protocol can react.
	 */
	if (ctxt == NULL) {
		/* Skip LSPs with backups */
		if (nhlfe_list_first(&lsp->backup_nhlfe_list) != NULL) {
			if (IS_ZEBRA_DEBUG_MPLS_DETAIL)
				zlog_debug("%s: skip LSP in-label %u",
					   __func__, lsp->ile.in_label);
			return;
		}
	}

	(void)lsp_processq_add(lsp);
}

/*
 * Process a LSP entry that is in the queue. Recalculate best NHLFE and
 * any multipaths and update or delete from the kernel, as needed.
 */
static wq_item_status lsp_process(struct work_queue *wq, void *data)
{
	struct zebra_lsp *lsp;
	struct zebra_nhlfe *oldbest, *newbest;
	char buf[BUFSIZ], buf2[BUFSIZ];
	struct zebra_vrf *zvrf = zebra_vrf_lookup_by_id(VRF_DEFAULT);
	enum zebra_dplane_result res;

	lsp = (struct zebra_lsp *)data;
	if (!lsp) // unexpected
		return WQ_SUCCESS;

	oldbest = lsp->best_nhlfe;

	/* Select best NHLFE(s) */
	lsp_select_best_nhlfe(lsp);

	newbest = lsp->best_nhlfe;

	if (IS_ZEBRA_DEBUG_MPLS) {
		if (oldbest)
			nhlfe2str(oldbest, buf, sizeof(buf));
		if (newbest)
			nhlfe2str(newbest, buf2, sizeof(buf2));
		zlog_debug(
			"Process LSP in-label %u oldbest %s newbest %s flags 0x%x ecmp# %d",
			lsp->ile.in_label, oldbest ? buf : "NULL",
			newbest ? buf2 : "NULL", lsp->flags, lsp->num_ecmp);
	}

	if (!CHECK_FLAG(lsp->flags, LSP_FLAG_INSTALLED)) {
		/* Not already installed */
		if (newbest) {

			UNSET_FLAG(lsp->flags, LSP_FLAG_CHANGED);

			switch (dplane_lsp_add(lsp)) {
			case ZEBRA_DPLANE_REQUEST_QUEUED:
				/* Set 'installed' flag so we will know
				 * that an install is in-flight.
				 */
				SET_FLAG(lsp->flags, LSP_FLAG_INSTALLED);

				zvrf->lsp_installs_queued++;
				break;
			case ZEBRA_DPLANE_REQUEST_FAILURE:
				flog_warn(EC_ZEBRA_LSP_INSTALL_FAILURE,
					  "LSP Install Failure: %u",
					  lsp->ile.in_label);
				break;
			case ZEBRA_DPLANE_REQUEST_SUCCESS:
				zvrf->lsp_installs++;
				break;
			}
		}
	} else {
		/* Installed, may need an update and/or delete. */
		if (!newbest) {
			res = dplane_lsp_delete(lsp);

			/* We do some of the lsp cleanup immediately for
			 * deletes.
			 */
			UNSET_FLAG(lsp->flags, LSP_FLAG_INSTALLED);
			clear_nhlfe_installed(lsp);

			switch (res) {
			case ZEBRA_DPLANE_REQUEST_QUEUED:
				zvrf->lsp_removals_queued++;
				break;
			case ZEBRA_DPLANE_REQUEST_FAILURE:
				flog_warn(EC_ZEBRA_LSP_DELETE_FAILURE,
					  "LSP Deletion Failure: %u",
					  lsp->ile.in_label);
				break;
			case ZEBRA_DPLANE_REQUEST_SUCCESS:
				zvrf->lsp_removals++;
				break;
			}
		} else if (CHECK_FLAG(lsp->flags, LSP_FLAG_CHANGED)) {
			struct zebra_nhlfe *nhlfe;
			struct nexthop *nexthop;

			UNSET_FLAG(lsp->flags, LSP_FLAG_CHANGED);

			/* We leave the INSTALLED flag set here
			 * so we know an update is in-flight.
			 */

			/*
			 * Any NHLFE that was installed but is not
			 * selected now needs to have its flags updated.
			 */
			frr_each_safe(nhlfe_list, &lsp->nhlfe_list, nhlfe) {
				nexthop = nhlfe->nexthop;
				if (!nexthop)
					continue;

				if (CHECK_FLAG(nhlfe->flags,
					       NHLFE_FLAG_INSTALLED)
				    && !CHECK_FLAG(nhlfe->flags,
						   NHLFE_FLAG_SELECTED)) {
					UNSET_FLAG(nhlfe->flags,
						   NHLFE_FLAG_INSTALLED);
					UNSET_FLAG(nexthop->flags,
						   NEXTHOP_FLAG_FIB);
				}
			}

			switch (dplane_lsp_update(lsp)) {
			case ZEBRA_DPLANE_REQUEST_QUEUED:
				zvrf->lsp_installs_queued++;
				break;
			case ZEBRA_DPLANE_REQUEST_FAILURE:
				flog_warn(EC_ZEBRA_LSP_INSTALL_FAILURE,
					  "LSP Update Failure: %u",
					  lsp->ile.in_label);
				break;
			case ZEBRA_DPLANE_REQUEST_SUCCESS:
				zvrf->lsp_installs++;
				break;
			}
		}
	}

	return WQ_SUCCESS;
}


/*
 * Callback upon processing completion of a LSP forwarding entry.
 */
static void lsp_processq_del(struct work_queue *wq, void *data)
{
	struct zebra_vrf *zvrf;
	struct zebra_lsp *lsp;
	struct hash *lsp_table;
	struct zebra_nhlfe *nhlfe;

	/* If zebra is shutting down, don't delete any structs,
	 * just ignore this callback. The LSPs will be cleaned up
	 * during the shutdown processing.
	 */
	if (zebra_router_in_shutdown())
		return;

	zvrf = zebra_vrf_lookup_by_id(VRF_DEFAULT);
	lsp_table = zvrf->lsp_table;
	if (!lsp_table) // unexpected
		return;

	lsp = (struct zebra_lsp *)data;
	if (!lsp) // unexpected
		return;

	/* Clear flag, remove any NHLFEs marked for deletion. If no NHLFEs
	 * exist,
	 * delete LSP entry also.
	 */
	UNSET_FLAG(lsp->flags, LSP_FLAG_SCHEDULED);

	frr_each_safe(nhlfe_list, &lsp->nhlfe_list, nhlfe) {
		if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED))
			nhlfe_del(nhlfe);
	}

	frr_each_safe(nhlfe_list, &lsp->backup_nhlfe_list, nhlfe) {
		if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED))
			nhlfe_del(nhlfe);
	}

	lsp_check_free(lsp_table, &lsp);
}

/*
 * Callback upon finishing the processing of all scheduled
 * LSP forwarding entries.
 */
static void lsp_processq_complete(struct work_queue *wq)
{
	/* Nothing to do for now. */
}

/*
 * Add LSP forwarding entry to queue for subsequent processing.
 */
static int lsp_processq_add(struct zebra_lsp *lsp)
{
	/* If already scheduled, exit. */
	if (CHECK_FLAG(lsp->flags, LSP_FLAG_SCHEDULED))
		return 0;

	if (zrouter.lsp_process_q == NULL) {
		flog_err(EC_ZEBRA_WQ_NONEXISTENT,
			 "%s: work_queue does not exist!", __func__);
		return -1;
	}

	work_queue_add(zrouter.lsp_process_q, lsp);
	SET_FLAG(lsp->flags, LSP_FLAG_SCHEDULED);
	return 0;
}

/*
 * Callback to allocate LSP forwarding table entry.
 */
static void *lsp_alloc(void *p)
{
	const struct zebra_ile *ile = p;
	struct zebra_lsp *lsp;

	lsp = XCALLOC(MTYPE_LSP, sizeof(struct zebra_lsp));
	lsp->ile = *ile;
	nhlfe_list_init(&lsp->nhlfe_list);
	nhlfe_list_init(&lsp->backup_nhlfe_list);

	if (IS_ZEBRA_DEBUG_MPLS)
		zlog_debug("Alloc LSP in-label %u", lsp->ile.in_label);

	return ((void *)lsp);
}

/*
 * Check whether lsp can be freed - no nhlfes, e.g., and call free api
 */
static void lsp_check_free(struct hash *lsp_table, struct zebra_lsp **plsp)
{
	struct zebra_lsp *lsp;

	if (plsp == NULL || *plsp == NULL)
		return;

	lsp = *plsp;

	if ((nhlfe_list_first(&lsp->nhlfe_list) == NULL) &&
	    (nhlfe_list_first(&lsp->backup_nhlfe_list) == NULL) &&
	    !CHECK_FLAG(lsp->flags, LSP_FLAG_SCHEDULED))
		lsp_free(lsp_table, plsp);
}

static void lsp_free_nhlfe(struct zebra_lsp *lsp)
{
	struct zebra_nhlfe *nhlfe;

	while ((nhlfe = nhlfe_list_first(&lsp->nhlfe_list))) {
		nhlfe_list_del(&lsp->nhlfe_list, nhlfe);
		nhlfe_free(nhlfe);
	}

	while ((nhlfe = nhlfe_list_first(&lsp->backup_nhlfe_list))) {
		nhlfe_list_del(&lsp->backup_nhlfe_list, nhlfe);
		nhlfe_free(nhlfe);
	}
}

/*
 * Dtor for an LSP: remove from ile hash, release any internal allocations,
 * free LSP object.
 */
static void lsp_free(struct hash *lsp_table, struct zebra_lsp **plsp)
{
	struct zebra_lsp *lsp;

	if (plsp == NULL || *plsp == NULL)
		return;

	lsp = *plsp;

	if (IS_ZEBRA_DEBUG_MPLS)
		zlog_debug("Free LSP in-label %u flags 0x%x",
			   lsp->ile.in_label, lsp->flags);

	lsp_free_nhlfe(lsp);

	hash_release(lsp_table, &lsp->ile);
	XFREE(MTYPE_LSP, lsp);

	*plsp = NULL;
}

/*
 * Create printable string for NHLFE entry.
 */
static char *nhlfe2str(const struct zebra_nhlfe *nhlfe, char *buf, int size)
{
	const struct nexthop *nexthop;

	buf[0] = '\0';
	nexthop = nhlfe->nexthop;
	switch (nexthop->type) {
	case NEXTHOP_TYPE_IPV4:
	case NEXTHOP_TYPE_IPV4_IFINDEX:
		inet_ntop(AF_INET, &nexthop->gate.ipv4, buf, size);
		break;
	case NEXTHOP_TYPE_IPV6:
	case NEXTHOP_TYPE_IPV6_IFINDEX:
		inet_ntop(AF_INET6, &nexthop->gate.ipv6, buf, size);
		break;
	case NEXTHOP_TYPE_IFINDEX:
		snprintf(buf, size, "Ifindex: %u", nexthop->ifindex);
		break;
	case NEXTHOP_TYPE_BLACKHOLE:
		break;
	}

	return buf;
}

/*
 * Check if NHLFE matches with search info passed.
 */
static int nhlfe_nhop_match(struct zebra_nhlfe *nhlfe,
			    enum nexthop_types_t gtype,
			    const union g_addr *gate, ifindex_t ifindex)
{
	struct nexthop *nhop;
	int cmp = 1;

	nhop = nhlfe->nexthop;
	if (!nhop)
		return 1;

	if (nhop->type != gtype)
		return 1;

	switch (nhop->type) {
	case NEXTHOP_TYPE_IPV4:
	case NEXTHOP_TYPE_IPV4_IFINDEX:
		cmp = memcmp(&(nhop->gate.ipv4), &(gate->ipv4),
			     sizeof(struct in_addr));
		if (!cmp && nhop->type == NEXTHOP_TYPE_IPV4_IFINDEX)
			cmp = !(nhop->ifindex == ifindex);
		break;
	case NEXTHOP_TYPE_IPV6:
	case NEXTHOP_TYPE_IPV6_IFINDEX:
		cmp = memcmp(&(nhop->gate.ipv6), &(gate->ipv6),
			     sizeof(struct in6_addr));
		if (!cmp && nhop->type == NEXTHOP_TYPE_IPV6_IFINDEX)
			cmp = !(nhop->ifindex == ifindex);
		break;
	case NEXTHOP_TYPE_IFINDEX:
		cmp = !(nhop->ifindex == ifindex);
		break;
	case NEXTHOP_TYPE_BLACKHOLE:
		break;
	}

	return cmp;
}


/*
 * Locate NHLFE that matches with passed info.
 * TODO: handle vrf_id if vrf backend is netns based
 */
static struct zebra_nhlfe *nhlfe_find(struct nhlfe_list_head *list,
				      enum lsp_types_t lsp_type,
				      enum nexthop_types_t gtype,
				      const union g_addr *gate,
				      ifindex_t ifindex)
{
	struct zebra_nhlfe *nhlfe;

	frr_each_safe(nhlfe_list, list, nhlfe) {
		if (nhlfe->type != lsp_type)
			continue;
		if (!nhlfe_nhop_match(nhlfe, gtype, gate, ifindex))
			break;
	}

	return nhlfe;
}

/*
 * Allocate and init new NHLFE.
 */
static struct zebra_nhlfe *
nhlfe_alloc(struct zebra_lsp *lsp, enum lsp_types_t lsp_type,
	    enum nexthop_types_t gtype, const union g_addr *gate,
	    ifindex_t ifindex, vrf_id_t vrf_id, uint8_t num_labels,
	    const mpls_label_t *labels)
{
	struct zebra_nhlfe *nhlfe;
	struct nexthop *nexthop;

	assert(lsp);

	nhlfe = XCALLOC(MTYPE_NHLFE, sizeof(struct zebra_nhlfe));

	nhlfe->lsp = lsp;
	nhlfe->type = lsp_type;
	nhlfe->distance = lsp_distance(lsp_type);

	nexthop = nexthop_new();

	nexthop_add_labels(nexthop, lsp_type, num_labels, labels);

	nexthop->vrf_id = vrf_id;
	nexthop->type = gtype;
	switch (nexthop->type) {
	case NEXTHOP_TYPE_IPV4:
	case NEXTHOP_TYPE_IPV4_IFINDEX:
		nexthop->gate.ipv4 = gate->ipv4;
		if (ifindex)
			nexthop->ifindex = ifindex;
		break;
	case NEXTHOP_TYPE_IPV6:
	case NEXTHOP_TYPE_IPV6_IFINDEX:
		nexthop->gate.ipv6 = gate->ipv6;
		if (ifindex)
			nexthop->ifindex = ifindex;
		break;
	case NEXTHOP_TYPE_IFINDEX:
		nexthop->ifindex = ifindex;
		break;
	case NEXTHOP_TYPE_BLACKHOLE:
		if (IS_ZEBRA_DEBUG_MPLS)
			zlog_debug("%s: invalid: blackhole nexthop", __func__);

		nexthop_free(nexthop);
		XFREE(MTYPE_NHLFE, nhlfe);
		return NULL;
	}
	nhlfe->nexthop = nexthop;

	return nhlfe;
}

/*
 * Add primary or backup NHLFE. Base entry must have been created and
 * duplicate check done.
 */
static struct zebra_nhlfe *
nhlfe_add(struct zebra_lsp *lsp, enum lsp_types_t lsp_type,
	  enum nexthop_types_t gtype, const union g_addr *gate,
	  ifindex_t ifindex, vrf_id_t vrf_id, uint8_t num_labels,
	  const mpls_label_t *labels, bool is_backup)
{
	struct zebra_nhlfe *nhlfe;

	if (!lsp)
		return NULL;

	/* Allocate new object */
	nhlfe = nhlfe_alloc(lsp, lsp_type, gtype, gate, ifindex, vrf_id,
			    num_labels, labels);

	if (!nhlfe)
		return NULL;

	/* Enqueue to LSP: primaries at head of list, backups at tail */
	if (is_backup) {
		SET_FLAG(nhlfe->flags, NHLFE_FLAG_IS_BACKUP);
		nhlfe_list_add_tail(&lsp->backup_nhlfe_list, nhlfe);
	} else
		nhlfe_list_add_head(&lsp->nhlfe_list, nhlfe);

	return nhlfe;
}

/*
 * Common delete for NHLFEs.
 */
static void nhlfe_free(struct zebra_nhlfe *nhlfe)
{
	if (!nhlfe)
		return;

	/* Free nexthop. */
	if (nhlfe->nexthop)
		nexthop_free(nhlfe->nexthop);

	nhlfe->nexthop = NULL;

	XFREE(MTYPE_NHLFE, nhlfe);
}


/*
 * Disconnect NHLFE from LSP, and free. Entry must be present on LSP's list.
 */
static int nhlfe_del(struct zebra_nhlfe *nhlfe)
{
	struct zebra_lsp *lsp;

	if (!nhlfe)
		return -1;

	lsp = nhlfe->lsp;
	if (!lsp)
		return -1;

	if (nhlfe == lsp->best_nhlfe)
		lsp->best_nhlfe = NULL;

	/* Unlink from LSP */
	if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_IS_BACKUP))
		nhlfe_list_del(&lsp->backup_nhlfe_list, nhlfe);
	else
		nhlfe_list_del(&lsp->nhlfe_list, nhlfe);

	nhlfe->lsp = NULL;

	nhlfe_free(nhlfe);

	return 0;
}

/*
 * Update label for NHLFE entry.
 */
static void nhlfe_out_label_update(struct zebra_nhlfe *nhlfe,
				   struct mpls_label_stack *nh_label)
{
	struct mpls_label_stack *nh_label_tmp;
	int i;

	/* Enforce limit on label stack size */
	if (nh_label->num_labels > MPLS_MAX_LABELS)
		nh_label->num_labels = MPLS_MAX_LABELS;

	/* Resize the array to accommodate the new label stack */
	if (nh_label->num_labels > nhlfe->nexthop->nh_label->num_labels) {
		nh_label_tmp = XREALLOC(MTYPE_NH_LABEL, nhlfe->nexthop->nh_label,
					sizeof(struct mpls_label_stack) +
						nh_label->num_labels *
							sizeof(mpls_label_t));
		if (nh_label_tmp) {
			nhlfe->nexthop->nh_label = nh_label_tmp;
			nhlfe->nexthop->nh_label->num_labels =
				nh_label->num_labels;
		} else
			nh_label->num_labels =
				nhlfe->nexthop->nh_label->num_labels;
	}

	/* Copy the label stack into the array */
	for (i = 0; i < nh_label->num_labels; i++)
		nhlfe->nexthop->nh_label->label[i] = nh_label->label[i];
}

static int mpls_lsp_uninstall_all(struct hash *lsp_table, struct zebra_lsp *lsp,
				  enum lsp_types_t type)
{
	struct zebra_nhlfe *nhlfe;
	int schedule_lsp = 0;
	char buf[BUFSIZ];

	if (CHECK_FLAG(lsp->flags, LSP_FLAG_INSTALLED))
		schedule_lsp = 1;

	/* Mark NHLFEs for delete or directly delete, as appropriate. */
	frr_each_safe(nhlfe_list, &lsp->nhlfe_list, nhlfe) {
		/* Skip non-static NHLFEs */
		if (nhlfe->type != type)
			continue;

		if (IS_ZEBRA_DEBUG_MPLS) {
			nhlfe2str(nhlfe, buf, sizeof(buf));
			zlog_debug(
				"Del LSP in-label %u type %d nexthop %s flags 0x%x",
				lsp->ile.in_label, type, buf, nhlfe->flags);
		}

		if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED)) {
			UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_CHANGED);
			SET_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED);
			schedule_lsp = 1;
		} else {
			nhlfe_del(nhlfe);
		}
	}

	frr_each_safe(nhlfe_list, &lsp->backup_nhlfe_list, nhlfe) {
		/* Skip non-static NHLFEs */
		if (nhlfe->type != type)
			continue;

		if (IS_ZEBRA_DEBUG_MPLS) {
			nhlfe2str(nhlfe, buf, sizeof(buf));
			zlog_debug(
				"Del backup LSP in-label %u type %d nexthop %s flags 0x%x",
				lsp->ile.in_label, type, buf, nhlfe->flags);
		}

		if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED)) {
			UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_CHANGED);
			SET_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED);
			schedule_lsp = 1;
		} else {
			nhlfe_del(nhlfe);
		}
	}

	/* Queue LSP for processing, if needed, else delete. */
	if (schedule_lsp) {
		if (IS_ZEBRA_DEBUG_MPLS) {
			zlog_debug("Schedule LSP in-label %u flags 0x%x",
				   lsp->ile.in_label, lsp->flags);
		}
		if (lsp_processq_add(lsp))
			return -1;
	} else {
		lsp_check_free(lsp_table, &lsp);
	}

	return 0;
}

/*
 * Uninstall all static NHLFEs for a particular LSP forwarding entry.
 * If no other NHLFEs exist, the entry would be deleted.
 */
static int mpls_static_lsp_uninstall_all(struct zebra_vrf *zvrf,
					 mpls_label_t in_label)
{
	struct hash *lsp_table;
	struct zebra_ile tmp_ile;
	struct zebra_lsp *lsp;

	/* Lookup table. */
	lsp_table = zvrf->lsp_table;
	if (!lsp_table)
		return -1;

	/* If entry is not present, exit. */
	tmp_ile.in_label = in_label;
	lsp = hash_lookup(lsp_table, &tmp_ile);
	if (!lsp || (nhlfe_list_first(&lsp->nhlfe_list) == NULL))
		return 0;

	return mpls_lsp_uninstall_all(lsp_table, lsp, ZEBRA_LSP_STATIC);
}

static json_object *nhlfe_json(struct zebra_nhlfe *nhlfe)
{
	json_object *json_nhlfe = NULL;
	json_object *json_backups = NULL;
	json_object *json_label_stack;
	struct nexthop *nexthop = nhlfe->nexthop;
	int i;

	json_nhlfe = json_object_new_object();
	json_object_string_add(json_nhlfe, "type", nhlfe_type2str(nhlfe->type));
	if (nexthop->nh_label) {
		json_object_int_add(json_nhlfe, "outLabel",
				    nexthop->nh_label->label[0]);
		json_label_stack = json_object_new_array();
		json_object_object_add(json_nhlfe, "outLabelStack",
				       json_label_stack);
		for (i = 0; i < nexthop->nh_label->num_labels; i++)
			json_object_array_add(
				json_label_stack,
				json_object_new_int(
					nexthop->nh_label->label[i]));
	}
	json_object_int_add(json_nhlfe, "distance", nhlfe->distance);

	if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED))
		json_object_boolean_true_add(json_nhlfe, "installed");

	switch (nexthop->type) {
	case NEXTHOP_TYPE_IPV4:
	case NEXTHOP_TYPE_IPV4_IFINDEX:
		json_object_string_addf(json_nhlfe, "nexthop", "%pI4",
					&nexthop->gate.ipv4);
		if (nexthop->ifindex)
			json_object_string_add(json_nhlfe, "interface",
					       ifindex2ifname(nexthop->ifindex,
							      nexthop->vrf_id));
		break;
	case NEXTHOP_TYPE_IPV6:
	case NEXTHOP_TYPE_IPV6_IFINDEX:
		json_object_string_addf(json_nhlfe, "nexthop", "%pI6",
					&nexthop->gate.ipv6);

		if (nexthop->ifindex)
			json_object_string_add(json_nhlfe, "interface",
					       ifindex2ifname(nexthop->ifindex,
							      nexthop->vrf_id));
		break;
	case NEXTHOP_TYPE_IFINDEX:
		if (nexthop->ifindex)
			json_object_string_add(json_nhlfe, "interface",
					       ifindex2ifname(nexthop->ifindex,
							      nexthop->vrf_id));
		break;
	case NEXTHOP_TYPE_BLACKHOLE:
		break;
	}

	if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_HAS_BACKUP)) {
		json_backups = json_object_new_array();
		for (i = 0; i < nexthop->backup_num; i++) {
			json_object_array_add(
				json_backups,
				json_object_new_int(nexthop->backup_idx[i]));
		}

		json_object_object_add(json_nhlfe, "backupIndex",
				       json_backups);
	}

	return json_nhlfe;
}

/*
 * Print the NHLFE for a LSP forwarding entry.
 */
static void nhlfe_print(struct zebra_nhlfe *nhlfe, struct vty *vty,
			const char *indent)
{
	struct nexthop *nexthop;
	char buf[MPLS_LABEL_STRLEN];

	nexthop = nhlfe->nexthop;
	if (!nexthop || !nexthop->nh_label) // unexpected
		return;

	vty_out(vty, " type: %s remote label: %s distance: %d\n",
		nhlfe_type2str(nhlfe->type),
		mpls_label2str(nexthop->nh_label->num_labels,
			       nexthop->nh_label->label, buf, sizeof(buf),
			       nexthop->nh_label_type, 0),
		nhlfe->distance);

	if (indent)
		vty_out(vty, "%s", indent);

	switch (nexthop->type) {
	case NEXTHOP_TYPE_IPV4:
	case NEXTHOP_TYPE_IPV4_IFINDEX:
		vty_out(vty, "  via %pI4", &nexthop->gate.ipv4);
		if (nexthop->ifindex)
			vty_out(vty, " dev %s",
				ifindex2ifname(nexthop->ifindex,
					       nexthop->vrf_id));
		break;
	case NEXTHOP_TYPE_IPV6:
	case NEXTHOP_TYPE_IPV6_IFINDEX:
		vty_out(vty, "  via %s",
			inet_ntop(AF_INET6, &nexthop->gate.ipv6, buf,
				  sizeof(buf)));
		if (nexthop->ifindex)
			vty_out(vty, " dev %s",
				ifindex2ifname(nexthop->ifindex,
					       nexthop->vrf_id));
		break;
	case NEXTHOP_TYPE_IFINDEX:
		if (nexthop->ifindex)
			vty_out(vty, "  dev %s",
				ifindex2ifname(nexthop->ifindex,
					       nexthop->vrf_id));
		break;
	case NEXTHOP_TYPE_BLACKHOLE:
		break;
	}
	vty_out(vty, "%s",
		CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_IS_BACKUP) ? " (backup)"
							       : "");
	vty_out(vty, "%s",
		CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED) ? " (installed)"
							       : "");
	vty_out(vty, "\n");
}

/*
 * Print an LSP forwarding entry.
 */
static void lsp_print(struct vty *vty, struct zebra_lsp *lsp)
{
	struct zebra_nhlfe *nhlfe, *backup;
	int i, j;

	vty_out(vty, "Local label: %u%s\n", lsp->ile.in_label,
		CHECK_FLAG(lsp->flags, LSP_FLAG_INSTALLED) ? " (installed)"
							   : "");

	frr_each(nhlfe_list, &lsp->nhlfe_list, nhlfe) {
		nhlfe_print(nhlfe, vty, NULL);

		if (nhlfe->nexthop == NULL ||
		    !CHECK_FLAG(nhlfe->nexthop->flags,
				NEXTHOP_FLAG_HAS_BACKUP))
			continue;

		/* Backup nhlfes: find backups in backup list */

		for (j = 0; j < nhlfe->nexthop->backup_num; j++) {
			i = 0;
			backup = NULL;
			frr_each(nhlfe_list, &lsp->backup_nhlfe_list, backup) {
				if (i == nhlfe->nexthop->backup_idx[j])
					break;
				i++;
			}

			if (backup) {
				vty_out(vty, "   [backup %d]", i);
				nhlfe_print(backup, vty, "   ");
			}
		}
	}
}

/*
 * JSON objects for an LSP forwarding entry.
 */
static json_object *lsp_json(struct zebra_lsp *lsp)
{
	struct zebra_nhlfe *nhlfe = NULL;
	json_object *json = json_object_new_object();
	json_object *json_nhlfe_list = json_object_new_array();

	json_object_int_add(json, "inLabel", lsp->ile.in_label);

	if (CHECK_FLAG(lsp->flags, LSP_FLAG_INSTALLED))
		json_object_boolean_true_add(json, "installed");

	frr_each(nhlfe_list, &lsp->nhlfe_list, nhlfe)
		json_object_array_add(json_nhlfe_list, nhlfe_json(nhlfe));

	json_object_object_add(json, "nexthops", json_nhlfe_list);
	json_nhlfe_list = NULL;


	frr_each(nhlfe_list, &lsp->backup_nhlfe_list, nhlfe) {
		if (json_nhlfe_list == NULL)
			json_nhlfe_list = json_object_new_array();

		json_object_array_add(json_nhlfe_list, nhlfe_json(nhlfe));
	}

	if (json_nhlfe_list)
		json_object_object_add(json, "backupNexthops", json_nhlfe_list);

	return json;
}


/* Return a sorted linked list of the hash contents */
static struct list *hash_get_sorted_list(struct hash *hash, void *cmp)
{
	unsigned int i;
	struct hash_bucket *hb;
	struct list *sorted_list = list_new();

	sorted_list->cmp = (int (*)(void *, void *))cmp;

	for (i = 0; i < hash->size; i++)
		for (hb = hash->index[i]; hb; hb = hb->next)
			listnode_add_sort(sorted_list, hb->data);

	return sorted_list;
}

/*
 * Compare two LSPs based on their label values.
 */
static int lsp_cmp(const struct zebra_lsp *lsp1, const struct zebra_lsp *lsp2)
{
	if (lsp1->ile.in_label < lsp2->ile.in_label)
		return -1;

	if (lsp1->ile.in_label > lsp2->ile.in_label)
		return 1;

	return 0;
}

/*
 * Initialize work queue for processing changed LSPs.
 */
static void mpls_processq_init(void)
{
	zrouter.lsp_process_q = work_queue_new(zrouter.master, "LSP processing");

	zrouter.lsp_process_q->spec.workfunc = &lsp_process;
	zrouter.lsp_process_q->spec.del_item_data = &lsp_processq_del;
	zrouter.lsp_process_q->spec.completion_func = &lsp_processq_complete;
	zrouter.lsp_process_q->spec.max_retries = 0;
	zrouter.lsp_process_q->spec.hold = 10;
}


/*
 * Process LSP update results from zebra dataplane.
 */
void zebra_mpls_lsp_dplane_result(struct zebra_dplane_ctx *ctx)
{
	struct zebra_vrf *zvrf;
	mpls_label_t label;
	struct zebra_ile tmp_ile;
	struct hash *lsp_table;
	struct zebra_lsp *lsp;
	struct zebra_nhlfe *nhlfe;
	struct nexthop *nexthop;
	enum dplane_op_e op;
	enum zebra_dplane_result status;
	enum zebra_sr_policy_update_label_mode update_mode;

	op = dplane_ctx_get_op(ctx);
	status = dplane_ctx_get_status(ctx);

	if (IS_ZEBRA_DEBUG_DPLANE_DETAIL)
		zlog_debug("LSP dplane ctx %p, op %s, in-label %u, result %s",
			   ctx, dplane_op2str(op),
			   dplane_ctx_get_in_label(ctx),
			   dplane_res2str(status));

	label = dplane_ctx_get_in_label(ctx);

	if (op == DPLANE_OP_LSP_INSTALL || op == DPLANE_OP_LSP_UPDATE) {
		/* Look for zebra LSP object */
		zvrf = zebra_vrf_lookup_by_id(VRF_DEFAULT);
		lsp_table = zvrf->lsp_table;

		tmp_ile.in_label = label;
		lsp = hash_lookup(lsp_table, &tmp_ile);
		if (lsp == NULL) {
			if (IS_ZEBRA_DEBUG_DPLANE)
				zlog_debug("LSP ctx %p: in-label %u not found",
					   ctx, dplane_ctx_get_in_label(ctx));
			return;
		}

		/* TODO -- Confirm that this result is still 'current' */

		if (status != ZEBRA_DPLANE_REQUEST_SUCCESS) {
			UNSET_FLAG(lsp->flags, LSP_FLAG_INSTALLED);
			clear_nhlfe_installed(lsp);
			flog_warn(EC_ZEBRA_LSP_INSTALL_FAILURE,
				  "LSP Install Failure: in-label %u",
				  lsp->ile.in_label);
			return;
		}

		/* Update zebra object */
		SET_FLAG(lsp->flags, LSP_FLAG_INSTALLED);
		frr_each(nhlfe_list, &lsp->nhlfe_list, nhlfe) {
			nexthop = nhlfe->nexthop;
			if (!nexthop)
				continue;

			if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_SELECTED) &&
			    CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) {
				SET_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED);
				SET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB);
			}
		}

		update_mode = (op == DPLANE_OP_LSP_INSTALL)
				      ? ZEBRA_SR_POLICY_LABEL_CREATED
				      : ZEBRA_SR_POLICY_LABEL_UPDATED;
		zebra_sr_policy_label_update(label, update_mode);
	} else if (op == DPLANE_OP_LSP_DELETE) {
		if (status != ZEBRA_DPLANE_REQUEST_SUCCESS) {
			flog_warn(EC_ZEBRA_LSP_DELETE_FAILURE,
				  "LSP Deletion Failure: in-label %u",
				  dplane_ctx_get_in_label(ctx));
			return;
		}
		zebra_sr_policy_label_update(label,
					     ZEBRA_SR_POLICY_LABEL_REMOVED);
	}
}

/*
 * Process LSP installation info from two sets of nhlfes: a set from
 * a dplane notification, and a set from the zebra LSP object. Update
 * counters of installed nexthops, and return whether the LSP has changed.
 */
static bool compare_notif_nhlfes(const struct nhlfe_list_head *ctx_head,
				 struct nhlfe_list_head *nhlfe_head,
				 int *start_counter, int *end_counter)
{
	struct zebra_nhlfe *nhlfe;
	const struct zebra_nhlfe *ctx_nhlfe;
	struct nexthop *nexthop;
	const struct nexthop *ctx_nexthop;
	int start_count = 0, end_count = 0;
	bool changed_p = false;
	bool is_debug = (IS_ZEBRA_DEBUG_DPLANE | IS_ZEBRA_DEBUG_MPLS);

	frr_each_safe(nhlfe_list, nhlfe_head, nhlfe) {
		char buf[NEXTHOP_STRLEN];

		nexthop = nhlfe->nexthop;
		if (!nexthop)
			continue;

		if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB))
			start_count++;

		ctx_nhlfe = NULL;
		ctx_nexthop = NULL;
		frr_each(nhlfe_list_const, ctx_head, ctx_nhlfe) {
			ctx_nexthop = ctx_nhlfe->nexthop;
			if (!ctx_nexthop)
				continue;

			if ((ctx_nexthop->type == nexthop->type) &&
			    nexthop_same(ctx_nexthop, nexthop)) {
				/* Matched */
				break;
			}
		}

		if (is_debug)
			nexthop2str(nexthop, buf, sizeof(buf));

		if (ctx_nhlfe && ctx_nexthop) {
			if (is_debug) {
				const char *tstr = "";

				if (!CHECK_FLAG(ctx_nhlfe->flags,
						NHLFE_FLAG_INSTALLED))
					tstr = "not ";

				zlog_debug("LSP dplane notif: matched nh %s (%sinstalled)",
					   buf, tstr);
			}

			/* Test zebra nhlfe install state */
			if (CHECK_FLAG(ctx_nhlfe->flags,
				       NHLFE_FLAG_INSTALLED)) {

				if (!CHECK_FLAG(nhlfe->flags,
						NHLFE_FLAG_INSTALLED))
					changed_p = true;

				/* Update counter */
				end_count++;
			} else {

				if (CHECK_FLAG(nhlfe->flags,
					       NHLFE_FLAG_INSTALLED))
					changed_p = true;
			}

		} else {
			/* Not mentioned in lfib set -> uninstalled */
			if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED) ||
			    CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE) ||
			    CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) {
				changed_p = true;
			}

			if (is_debug)
				zlog_debug("LSP dplane notif: no match, nh %s",
					   buf);
		}
	}

	if (start_counter)
		*start_counter += start_count;
	if (end_counter)
		*end_counter += end_count;

	return changed_p;
}

/*
 * Update an lsp nhlfe list from a dplane context, typically an async
 * notification context. Update the LSP list to match the installed
 * status from the context's list.
 */
static int update_nhlfes_from_ctx(struct nhlfe_list_head *nhlfe_head,
				  const struct nhlfe_list_head *ctx_head)
{
	int ret = 0;
	struct zebra_nhlfe *nhlfe;
	const struct zebra_nhlfe *ctx_nhlfe;
	struct nexthop *nexthop;
	const struct nexthop *ctx_nexthop;
	bool is_debug = (IS_ZEBRA_DEBUG_DPLANE | IS_ZEBRA_DEBUG_MPLS);

	frr_each_safe(nhlfe_list, nhlfe_head, nhlfe) {
		char buf[NEXTHOP_STRLEN];

		nexthop = nhlfe->nexthop;
		if (!nexthop)
			continue;

		ctx_nhlfe = NULL;
		ctx_nexthop = NULL;
		frr_each(nhlfe_list_const, ctx_head, ctx_nhlfe) {
			ctx_nexthop = ctx_nhlfe->nexthop;
			if (!ctx_nexthop)
				continue;

			if ((ctx_nexthop->type == nexthop->type) &&
			    nexthop_same(ctx_nexthop, nexthop)) {
				/* Matched */
				break;
			}
		}

		if (is_debug)
			nexthop2str(nexthop, buf, sizeof(buf));

		if (ctx_nhlfe && ctx_nexthop) {

			/* Bring zebra nhlfe install state into sync */
			if (CHECK_FLAG(ctx_nhlfe->flags,
				       NHLFE_FLAG_INSTALLED)) {
				if (is_debug)
					zlog_debug("%s: matched lsp nhlfe %s (installed)",
						   __func__, buf);

				SET_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED);
				SET_FLAG(nhlfe->flags, NHLFE_FLAG_SELECTED);

			} else {
				if (is_debug)
					zlog_debug("%s: matched lsp nhlfe %s (not installed)",
						   __func__, buf);

				UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED);
				UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_SELECTED);
			}

			if (CHECK_FLAG(ctx_nhlfe->nexthop->flags,
				       NEXTHOP_FLAG_FIB)) {
				SET_FLAG(nhlfe->nexthop->flags,
					 NEXTHOP_FLAG_ACTIVE);
				SET_FLAG(nhlfe->nexthop->flags,
					 NEXTHOP_FLAG_FIB);
			} else {
				UNSET_FLAG(nhlfe->nexthop->flags,
					 NEXTHOP_FLAG_ACTIVE);
				UNSET_FLAG(nhlfe->nexthop->flags,
					   NEXTHOP_FLAG_FIB);
			}

		} else {
			/* Not mentioned in lfib set -> uninstalled */
			if (is_debug)
				zlog_debug("%s: no match for lsp nhlfe %s",
					   __func__, buf);
			UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED);
			UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_SELECTED);
			UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB);
			UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE);
		}
	}

	return ret;
}

/*
 * Process async dplane notifications.
 */
void zebra_mpls_process_dplane_notify(struct zebra_dplane_ctx *ctx)
{
	struct zebra_vrf *zvrf;
	struct zebra_ile tmp_ile;
	struct hash *lsp_table;
	struct zebra_lsp *lsp;
	const struct nhlfe_list_head *ctx_list;
	int start_count = 0, end_count = 0; /* Installed counts */
	bool changed_p = false;
	bool is_debug = (IS_ZEBRA_DEBUG_DPLANE | IS_ZEBRA_DEBUG_MPLS);
	enum zebra_sr_policy_update_label_mode update_mode;

	if (is_debug)
		zlog_debug("LSP dplane notif, in-label %u",
			   dplane_ctx_get_in_label(ctx));

	/* Look for zebra LSP object */
	zvrf = zebra_vrf_lookup_by_id(VRF_DEFAULT);
	lsp_table = zvrf->lsp_table;

	tmp_ile.in_label = dplane_ctx_get_in_label(ctx);
	lsp = hash_lookup(lsp_table, &tmp_ile);
	if (lsp == NULL) {
		if (is_debug)
			zlog_debug("dplane LSP notif: in-label %u not found",
				   dplane_ctx_get_in_label(ctx));
		return;
	}

	/*
	 * The dataplane/forwarding plane is notifying zebra about the state
	 * of the nexthops associated with this LSP. First, we take a
	 * pre-scan pass to determine whether the LSP has transitioned
	 * from installed -> uninstalled. In that case, we need to have
	 * the existing state of the LSP objects available before making
	 * any changes.
	 */
	ctx_list = dplane_ctx_get_nhlfe_list(ctx);

	changed_p = compare_notif_nhlfes(ctx_list, &lsp->nhlfe_list,
					 &start_count, &end_count);

	if (is_debug)
		zlog_debug("LSP dplane notif: lfib start_count %d, end_count %d%s",
			   start_count, end_count,
			   changed_p ? ", changed" : "");

	ctx_list = dplane_ctx_get_backup_nhlfe_list(ctx);

	if (compare_notif_nhlfes(ctx_list, &lsp->backup_nhlfe_list,
				 &start_count, &end_count))
		/* Avoid accidentally setting back to 'false' */
		changed_p = true;

	if (is_debug)
		zlog_debug("LSP dplane notif: lfib backups, start_count %d, end_count %d%s",
			   start_count, end_count,
			   changed_p ? ", changed" : "");

	/*
	 * Has the LSP become uninstalled? We need the existing state of the
	 * nexthops/nhlfes at this point so we know what to delete.
	 */
	if (start_count > 0 && end_count == 0) {
		/* Inform other lfibs */
		dplane_lsp_notif_update(lsp, DPLANE_OP_LSP_DELETE, ctx);
	}

	/*
	 * Now we take a second pass and bring the zebra
	 * nexthop state into sync with the forwarding-plane state.
	 */
	ctx_list = dplane_ctx_get_nhlfe_list(ctx);
	update_nhlfes_from_ctx(&lsp->nhlfe_list, ctx_list);

	ctx_list = dplane_ctx_get_backup_nhlfe_list(ctx);
	update_nhlfes_from_ctx(&lsp->backup_nhlfe_list, ctx_list);

	if (end_count > 0) {
		SET_FLAG(lsp->flags, LSP_FLAG_INSTALLED);

		/* SR-TE update too */
		if (start_count == 0)
			update_mode = ZEBRA_SR_POLICY_LABEL_CREATED;
		else
			update_mode = ZEBRA_SR_POLICY_LABEL_UPDATED;
		zebra_sr_policy_label_update(lsp->ile.in_label, update_mode);

		if (changed_p)
			dplane_lsp_notif_update(lsp, DPLANE_OP_LSP_UPDATE, ctx);

	} else {
		/* SR-TE update too */
		zebra_sr_policy_label_update(lsp->ile.in_label,
					     ZEBRA_SR_POLICY_LABEL_REMOVED);

		UNSET_FLAG(lsp->flags, LSP_FLAG_INSTALLED);
		clear_nhlfe_installed(lsp);
	}
}

/*
 * Install dynamic LSP entry.
 */
void zebra_mpls_lsp_install(struct zebra_vrf *zvrf, struct route_node *rn,
			   struct route_entry *re)
{
	struct route_table *table;
	struct zebra_fec *fec;

	table = zvrf->fec_table[family2afi(PREFIX_FAMILY(&rn->p))];
	if (!table)
		return;

	/* See if there is a configured label binding for this FEC. */
	fec = fec_find(table, &rn->p);
	if (!fec || fec->label == MPLS_INVALID_LABEL)
		return;

	/* We cannot install a label forwarding entry if local label is the
	 * implicit-null label.
	 */
	if (fec->label == MPLS_LABEL_IMPLICIT_NULL)
		return;

	lsp_install(zvrf, fec->label, rn, re);
}

/*
 * Uninstall dynamic LSP entry, if any.
 */
int zebra_mpls_lsp_uninstall(struct zebra_vrf *zvrf, struct route_node *rn,
			     struct route_entry *re)
{
	struct route_table *table;
	struct zebra_fec *fec;

	table = zvrf->fec_table[family2afi(PREFIX_FAMILY(&rn->p))];
	if (!table)
		return -1;

	/* See if there is a configured label binding for this FEC. */
	fec = fec_find(table, &rn->p);
	if (!fec || fec->label == MPLS_INVALID_LABEL)
		return 0;

	/* Uninstall always removes all dynamic NHLFEs. */
	return lsp_uninstall(zvrf, fec->label);
}

/*
 * Add an NHLFE to an LSP, return the newly-added object. This path only changes
 * the LSP object - nothing is scheduled for processing, for example.
 */
struct zebra_nhlfe *
zebra_mpls_lsp_add_nhlfe(struct zebra_lsp *lsp, enum lsp_types_t lsp_type,
			 enum nexthop_types_t gtype, const union g_addr *gate,
			 ifindex_t ifindex, uint8_t num_labels,
			 const mpls_label_t *out_labels)
{
	/* Just a public pass-through to the internal implementation */
	return nhlfe_add(lsp, lsp_type, gtype, gate, ifindex, VRF_DEFAULT,
			 num_labels, out_labels, false /*backup*/);
}

/*
 * Add a backup NHLFE to an LSP, return the newly-added object.
 * This path only changes the LSP object - nothing is scheduled for
 * processing, for example.
 */
struct zebra_nhlfe *zebra_mpls_lsp_add_backup_nhlfe(
	struct zebra_lsp *lsp, enum lsp_types_t lsp_type,
	enum nexthop_types_t gtype, const union g_addr *gate, ifindex_t ifindex,
	uint8_t num_labels, const mpls_label_t *out_labels)
{
	/* Just a public pass-through to the internal implementation */
	return nhlfe_add(lsp, lsp_type, gtype, gate, ifindex, VRF_DEFAULT,
			 num_labels, out_labels, true);
}

/*
 * Add an NHLFE to an LSP based on a nexthop; return the newly-added object
 */
struct zebra_nhlfe *zebra_mpls_lsp_add_nh(struct zebra_lsp *lsp,
					  enum lsp_types_t lsp_type,
					  const struct nexthop *nh)
{
	struct zebra_nhlfe *nhlfe;

	nhlfe = nhlfe_add(
		lsp, lsp_type, nh->type, &nh->gate, nh->ifindex, nh->vrf_id,
		nh->nh_label ? nh->nh_label->num_labels : 0,
		nh->nh_label ? nh->nh_label->label : NULL, false /*backup*/);

	return nhlfe;
}

/*
 * Add a backup NHLFE to an LSP based on a nexthop;
 * return the newly-added object.
 */
struct zebra_nhlfe *zebra_mpls_lsp_add_backup_nh(struct zebra_lsp *lsp,
						 enum lsp_types_t lsp_type,
						 const struct nexthop *nh)
{
	struct zebra_nhlfe *nhlfe;

	nhlfe = nhlfe_add(lsp, lsp_type, nh->type, &nh->gate, nh->ifindex,
			  nh->vrf_id,
			  nh->nh_label ? nh->nh_label->num_labels : 0,
			  nh->nh_label ? nh->nh_label->label : NULL, true);

	return nhlfe;
}

/*
 * Free an allocated NHLFE
 */
void zebra_mpls_nhlfe_free(struct zebra_nhlfe *nhlfe)
{
	/* Just a pass-through to the internal implementation */
	nhlfe_free(nhlfe);
}

/*
 * Registration from a client for the label binding for a FEC. If a binding
 * already exists, it is informed to the client.
 * NOTE: If there is a manually configured label binding, that is used.
 * Otherwise, if a label index is specified, it means we have to allocate the
 * label from a locally configured label block (SRGB), if one exists and index
 * is acceptable. If no label index then just register the specified label.
 * NOTE2: Either label or label_index is expected to be set to MPLS_INVALID_*
 * by the calling function. Register requests with both will be rejected.
 */
int zebra_mpls_fec_register(struct zebra_vrf *zvrf, struct prefix *p,
			    uint32_t label, uint32_t label_index,
			    struct zserv *client)
{
	struct route_table *table;
	struct zebra_fec *fec;
	bool new_client;
	bool label_change = false;
	uint32_t old_label;
	bool have_label_index = (label_index != MPLS_INVALID_LABEL_INDEX);
	bool is_configured_fec = false; /* indicate statically configured FEC */

	table = zvrf->fec_table[family2afi(PREFIX_FAMILY(p))];
	if (!table)
		return -1;

	if (label != MPLS_INVALID_LABEL && have_label_index) {
		flog_err(
			EC_ZEBRA_FEC_LABEL_INDEX_LABEL_CONFLICT,
			"Rejecting FEC register for %pFX with both label %u and Label Index %u specified, client %s",
			p, label, label_index,
			zebra_route_string(client->proto));
		return -1;
	}

	/* Locate FEC */
	fec = fec_find(table, p);
	if (!fec) {
		fec = fec_add(table, p, label, 0, label_index);
		if (!fec) {
			flog_err(
				EC_ZEBRA_FEC_ADD_FAILED,
				"Failed to add FEC %pFX upon register, client %s",
				p, zebra_route_string(client->proto));
			return -1;
		}

		old_label = MPLS_INVALID_LABEL;
		new_client = true;
	} else {
		/* Check if the FEC has been statically defined in the config */
		is_configured_fec = CHECK_FLAG(fec->flags, FEC_FLAG_CONFIGURED);
		/* Client may register same FEC with different label index. */
		new_client =
			(listnode_lookup(fec->client_list, client) == NULL);
		if (!new_client && fec->label_index == label_index
		    && fec->label == label)
			/* Duplicate register */
			return 0;

		/* Save current label, update the FEC */
		old_label = fec->label;
		fec->label_index = label_index;
	}

	if (new_client)
		listnode_add(fec->client_list, client);

	if (IS_ZEBRA_DEBUG_MPLS)
		zlog_debug("FEC %pFX label%s %u %s by client %s%s", p,
			   have_label_index ? " index" : "",
			   have_label_index ? label_index : label,
			   new_client ? "registered" : "updated",
			   zebra_route_string(client->proto),
			   is_configured_fec
				   ? ", but using statically configured label"
				   : "");

	/* If not a statically configured FEC, derive the local label
	 * from label index or use the provided label
	 */
	if (!is_configured_fec) {
		if (have_label_index)
			fec_derive_label_from_index(zvrf, fec);
		else
			fec->label = label;

		/* If no label change, exit. */
		if (fec->label == old_label)
			return 0;

		label_change = true;
	}

	/* If new client or label change, update client and install or uninstall
	 * label forwarding entry as needed.
	 */
	/* Inform client of label, if needed. */
	if ((new_client && fec->label != MPLS_INVALID_LABEL) || label_change) {
		if (IS_ZEBRA_DEBUG_MPLS)
			zlog_debug("Update client label %u", fec->label);
		fec_send(fec, client);
	}

	if (new_client || label_change)
		return fec_change_update_lsp(zvrf, fec, old_label, false);

	return 0;
}

/*
 * Deregistration from a client for the label binding for a FEC. The FEC
 * itself is deleted if no other registered clients exist and there is no
 * label bound to the FEC.
 */
int zebra_mpls_fec_unregister(struct zebra_vrf *zvrf, struct prefix *p,
			      struct zserv *client)
{
	struct route_table *table;
	struct zebra_fec *fec;

	table = zvrf->fec_table[family2afi(PREFIX_FAMILY(p))];
	if (!table)
		return -1;

	fec = fec_find(table, p);
	if (!fec) {
		flog_err(EC_ZEBRA_FEC_RM_FAILED,
			 "Failed to find FEC %pFX upon unregister, client %s",
			 p, zebra_route_string(client->proto));
		return -1;
	}

	listnode_delete(fec->client_list, client);

	if (IS_ZEBRA_DEBUG_MPLS)
		zlog_debug("FEC %pFX unregistered by client %s", p,
			   zebra_route_string(client->proto));

	/* If not a configured entry, delete the FEC if no other clients. Before
	 * deleting, see if any LSP needs to be uninstalled.
	 */
	if (!CHECK_FLAG(fec->flags, FEC_FLAG_CONFIGURED) &&
	    list_isempty(fec->client_list)) {
		mpls_label_t old_label = fec->label;
		fec->label = MPLS_INVALID_LABEL; /* reset */
		fec_change_update_lsp(zvrf, fec, old_label, false);
		fec_del(fec);
	}

	return 0;
}

/*
 * Cleanup any FECs registered by this client.
 */
static int zebra_mpls_cleanup_fecs_for_client(struct zserv *client)
{
	struct zebra_vrf *zvrf = zebra_vrf_lookup_by_id(VRF_DEFAULT);
	struct route_node *rn;
	struct zebra_fec *fec;
	struct listnode *node;
	struct zserv *fec_client;
	int af;

	for (af = AFI_IP; af < AFI_MAX; af++) {
		if (zvrf->fec_table[af] == NULL)
			continue;

		for (rn = route_top(zvrf->fec_table[af]); rn;
		     rn = route_next(rn)) {
			fec = rn->info;
			if (!fec || list_isempty(fec->client_list))
				continue;

			for (ALL_LIST_ELEMENTS_RO(fec->client_list, node,
						  fec_client)) {
				if (fec_client == client) {
					listnode_delete(fec->client_list,
							fec_client);
					if (!CHECK_FLAG(fec->flags, FEC_FLAG_CONFIGURED)
					    && list_isempty(fec->client_list))
						fec_del(fec);
					break;
				}
			}
		}
	}

	return 0;
}

struct lsp_uninstall_args {
	struct hash *lsp_table;
	enum lsp_types_t type;
};

/*
 * Cleanup MPLS labels registered by this client.
 */
static int zebra_mpls_cleanup_zclient_labels(struct zserv *client)
{
	struct vrf *vrf;
	struct zebra_vrf *zvrf;

	RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) {
		struct lsp_uninstall_args args;

		zvrf = vrf->info;
		if (!zvrf)
			continue;

		/* Cleanup LSPs. */
		args.lsp_table = zvrf->lsp_table;
		args.type = lsp_type_from_re_type(client->proto);
		hash_iterate(zvrf->lsp_table, mpls_lsp_uninstall_all_type,
			     &args);

		/* Cleanup FTNs. */
		mpls_ftn_uninstall_all(zvrf, AFI_IP,
				       lsp_type_from_re_type(client->proto));
		mpls_ftn_uninstall_all(zvrf, AFI_IP6,
				       lsp_type_from_re_type(client->proto));
	}

	return 0;
}

/*
 * Return FEC (if any) to which this label is bound.
 * Note: Only works for per-prefix binding and when the label is not
 * implicit-null.
 * TODO: Currently walks entire table, can optimize later with another
 * hash..
 */
struct zebra_fec *zebra_mpls_fec_for_label(struct zebra_vrf *zvrf,
					   struct prefix *p, mpls_label_t label)
{
	struct route_node *rn;
	struct zebra_fec *fec;
	int af;

	for (af = AFI_IP; af < AFI_MAX; af++) {
		if (zvrf->fec_table[af] == NULL)
			continue;

		for (rn = route_top(zvrf->fec_table[af]); rn;
		     rn = route_next(rn)) {
			if (!rn->info)
				continue;
			fec = rn->info;
			if (fec->label == label) {
				if (p && prefix_same(p, &rn->p))
					return NULL;
				return fec;
			}
		}
	}

	return NULL;
}

/*
 * Inform if specified label is currently bound to a FEC or not.
 */
int zebra_mpls_label_already_bound(struct zebra_vrf *zvrf, struct prefix *p,
				   mpls_label_t label)
{
	return (zebra_mpls_fec_for_label(zvrf, p, label) ? 1 : 0);
}

/*
 * Add static FEC to label binding. If there are clients registered for this
 * FEC, notify them. If there are labeled routes for this FEC, install the
 * label forwarding entry.
*/
int zebra_mpls_static_fec_add(struct zebra_vrf *zvrf, struct prefix *p,
			      mpls_label_t in_label)
{
	struct route_table *table;
	struct zebra_fec *fec;
	mpls_label_t old_label;
	int ret = 0;

	table = zvrf->fec_table[family2afi(PREFIX_FAMILY(p))];
	if (!table)
		return -1;

	/* Update existing FEC or create a new one. */
	fec = fec_find(table, p);
	if (!fec) {
		fec = fec_add(table, p, in_label, FEC_FLAG_CONFIGURED,
			      MPLS_INVALID_LABEL_INDEX);
		if (!fec) {
			flog_err(EC_ZEBRA_FEC_ADD_FAILED,
				 "Failed to add FEC %pFX upon config", p);
			return -1;
		}

		if (IS_ZEBRA_DEBUG_MPLS)
			zlog_debug("Add fec %pFX label %u", p, in_label);
	} else {
		SET_FLAG(fec->flags, FEC_FLAG_CONFIGURED);
		if (fec->label == in_label)
			/* Duplicate config */
			return 0;

		/* Label change, update clients. */
		old_label = fec->label;
		if (IS_ZEBRA_DEBUG_MPLS)
			zlog_debug("Update fec %pFX new label %u", p, in_label);

		fec->label = in_label;
		fec_update_clients(fec);

		/* Update label forwarding entries appropriately */
		ret = fec_change_update_lsp(zvrf, fec, old_label, false);
	}

	return ret;
}

/*
 * Remove static FEC to label binding. If there are no clients registered
 * for this FEC, delete the FEC; else notify clients
 * Note: Upon delete of static binding, if label index exists for this FEC,
 * client may need to be updated with derived label.
 */
int zebra_mpls_static_fec_del(struct zebra_vrf *zvrf, struct prefix *p)
{
	struct route_table *table;
	struct zebra_fec *fec;
	mpls_label_t old_label;

	table = zvrf->fec_table[family2afi(PREFIX_FAMILY(p))];
	if (!table)
		return -1;

	fec = fec_find(table, p);
	if (!fec) {
		flog_err(EC_ZEBRA_FEC_RM_FAILED,
			 "Failed to find FEC %pFX upon delete", p);
		return -1;
	}

	if (IS_ZEBRA_DEBUG_MPLS) {
		zlog_debug("Delete fec %pFX label %u label index %u", p,
			   fec->label, fec->label_index);
	}

	old_label = fec->label;
	UNSET_FLAG(fec->flags, FEC_FLAG_CONFIGURED);
	fec->label = MPLS_INVALID_LABEL;

	/* If no client exists, just delete the FEC. */
	if (list_isempty(fec->client_list)) {
		fec_del(fec);
		return 0;
	}

	/* Derive the local label (from label index) or reset it. */
	fec_derive_label_from_index(zvrf, fec);

	/* If there is a label change, update clients. */
	if (fec->label == old_label)
		return 0;
	fec_update_clients(fec);

	/* Update label forwarding entries appropriately */
	return fec_change_update_lsp(zvrf, fec, old_label, false);
}

/*
 * Display MPLS FEC to label binding configuration (VTY command handler).
 */
int zebra_mpls_write_fec_config(struct vty *vty, struct zebra_vrf *zvrf)
{
	struct route_node *rn;
	int af;
	struct zebra_fec *fec;
	int write = 0;

	for (af = AFI_IP; af < AFI_MAX; af++) {
		if (zvrf->fec_table[af] == NULL)
			continue;

		for (rn = route_top(zvrf->fec_table[af]); rn;
		     rn = route_next(rn)) {
			if (!rn->info)
				continue;

			char lstr[BUFSIZ];
			fec = rn->info;

			if (!CHECK_FLAG(fec->flags, FEC_FLAG_CONFIGURED))
				continue;

			write = 1;

			if (fec->label == MPLS_LABEL_IPV4_EXPLICIT_NULL ||
			    fec->label == MPLS_LABEL_IPV6_EXPLICIT_NULL)
				strlcpy(lstr, "explicit-null", sizeof(lstr));
			else if (fec->label == MPLS_LABEL_IMPLICIT_NULL)
				strlcpy(lstr, "implicit-null", sizeof(lstr));
			else
				snprintf(lstr, sizeof(lstr), "%d", fec->label);

			vty_out(vty, "mpls label bind %pFX %s\n", &rn->p, lstr);
		}
	}

	return write;
}

/*
 * Display MPLS FEC to label binding (VTY command handler).
 */
void zebra_mpls_print_fec_table(struct vty *vty, struct zebra_vrf *zvrf)
{
	struct route_node *rn;
	int af;

	for (af = AFI_IP; af < AFI_MAX; af++) {
		if (zvrf->fec_table[af] == NULL)
			continue;

		for (rn = route_top(zvrf->fec_table[af]); rn;
		     rn = route_next(rn)) {
			if (!rn->info)
				continue;
			fec_print(rn->info, vty);
		}
	}
}

/*
 * Display MPLS FEC to label binding for a specific FEC (VTY command handler).
 */
void zebra_mpls_print_fec(struct vty *vty, struct zebra_vrf *zvrf,
			  struct prefix *p)
{
	struct route_table *table;
	struct route_node *rn;

	table = zvrf->fec_table[family2afi(PREFIX_FAMILY(p))];
	if (!table)
		return;

	apply_mask(p);
	rn = route_node_lookup(table, p);
	if (!rn)
		return;

	route_unlock_node(rn);
	if (!rn->info)
		return;

	fec_print(rn->info, vty);
}

static void mpls_zebra_nhe_update(struct route_entry *re, afi_t afi,
				  struct nhg_hash_entry *new_nhe)
{
	struct nhg_hash_entry *nhe;

	nhe = zebra_nhg_rib_find_nhe(new_nhe, afi);

	route_entry_update_nhe(re, nhe);
}

static bool ftn_update_nexthop(bool add_p, struct nexthop *nexthop,
			       enum lsp_types_t type,
			       const struct zapi_nexthop *znh)
{
	if (add_p && nexthop->nh_label_type == ZEBRA_LSP_NONE)
		nexthop_add_labels(nexthop, type, znh->label_num, znh->labels);
	else if (!add_p && nexthop->nh_label_type == type)
		nexthop_del_labels(nexthop);
	else
		return false;

	return true;
}

void zebra_mpls_ftn_uninstall(struct zebra_vrf *zvrf, enum lsp_types_t type,
			      struct prefix *prefix, uint8_t route_type,
			      uint8_t route_instance)
{
	struct route_table *table;
	struct route_node *rn;
	struct route_entry *re;
	struct nexthop *nexthop;
	struct nhg_hash_entry *new_nhe;
	afi_t afi = family2afi(prefix->family);

	/* Lookup table.  */
	table = zebra_vrf_table(afi, SAFI_UNICAST, zvrf_id(zvrf));
	if (!table)
		return;

	/* Lookup existing route */
	rn = route_node_get(table, prefix);
	RNODE_FOREACH_RE (rn, re) {
		if (CHECK_FLAG(re->status, ROUTE_ENTRY_REMOVED))
			continue;
		if (re->type == route_type && re->instance == route_instance)
			break;
	}
	if (re == NULL)
		return;

	/*
	 * Nexthops are now shared by multiple routes, so we have to make
	 * a local copy, modify the copy, then update the route.
	 */
	new_nhe = zebra_nhe_copy(re->nhe, 0);

	for (nexthop = new_nhe->nhg.nexthop; nexthop; nexthop = nexthop->next)
		nexthop_del_labels(nexthop);

	/* Update backup routes/nexthops also, if present. */
	if (zebra_nhg_get_backup_nhg(new_nhe) != NULL) {
		for (nexthop = new_nhe->backup_info->nhe->nhg.nexthop; nexthop;
		     nexthop = nexthop->next)
			nexthop_del_labels(nexthop);
	}

	SET_FLAG(re->status, ROUTE_ENTRY_CHANGED);
	SET_FLAG(re->status, ROUTE_ENTRY_LABELS_CHANGED);

	/* This will create (or ref) a new nhe, so we will discard the local
	 * temporary nhe
	 */
	mpls_zebra_nhe_update(re, afi, new_nhe);

	zebra_nhg_free(new_nhe);

	rib_queue_add(rn);
}

/*
 * Iterate through a list of nexthops, for a match for 'znh'. If found,
 * update its labels according to 'add_p', and return 'true' if successful.
 */
static bool ftn_update_znh(bool add_p, enum lsp_types_t type,
			   struct nexthop *head, const struct zapi_nexthop *znh)
{
	bool found = false, success = false;
	struct nexthop *nexthop;

	for (nexthop = head; nexthop; nexthop = nexthop->next) {
		switch (nexthop->type) {
		case NEXTHOP_TYPE_IPV4:
		case NEXTHOP_TYPE_IPV4_IFINDEX:
			if (znh->type != NEXTHOP_TYPE_IPV4
			    && znh->type != NEXTHOP_TYPE_IPV4_IFINDEX)
				continue;
			if (!IPV4_ADDR_SAME(&nexthop->gate.ipv4,
					    &znh->gate.ipv4))
				continue;
			if (nexthop->type == NEXTHOP_TYPE_IPV4_IFINDEX
			    && nexthop->ifindex != znh->ifindex)
				continue;

			found = true;

			if (!ftn_update_nexthop(add_p, nexthop, type, znh))
				break;

			success = true;
			break;
		case NEXTHOP_TYPE_IPV6:
		case NEXTHOP_TYPE_IPV6_IFINDEX:
			if (znh->type != NEXTHOP_TYPE_IPV6
			    && znh->type != NEXTHOP_TYPE_IPV6_IFINDEX)
				continue;
			if (!IPV6_ADDR_SAME(&nexthop->gate.ipv6,
					    &znh->gate.ipv6))
				continue;
			if (nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX
			    && nexthop->ifindex != znh->ifindex)
				continue;

			found = true;

			if (!ftn_update_nexthop(add_p, nexthop, type, znh))
				break;
			success = true;
			break;
		case NEXTHOP_TYPE_IFINDEX:
			if (znh->type != NEXTHOP_TYPE_IFINDEX)
				continue;
			if (nexthop->ifindex != znh->ifindex)
				continue;

			found = true;

			if (!ftn_update_nexthop(add_p, nexthop, type, znh))
				break;
			success = true;
			break;
		case NEXTHOP_TYPE_BLACKHOLE:
			/* Not valid */
			continue;
		}

		if (found)
			break;
	}

	return success;
}

/*
 * Install/uninstall LSP and (optionally) FEC-To-NHLFE (FTN) bindings,
 * using zapi message info.
 * There are several changes that need to be made, in several zebra
 * data structures, so we want to do all the work required at once.
 */
void zebra_mpls_zapi_labels_process(bool add_p, struct zebra_vrf *zvrf,
				    const struct zapi_labels *zl)
{
	int i, counter, ret = 0;
	char buf[NEXTHOP_STRLEN];
	const struct zapi_nexthop *znh;
	struct route_table *table;
	struct route_node *rn = NULL;
	struct route_entry *re = NULL;
	struct nhg_hash_entry *new_nhe = NULL;
	bool found;
	afi_t afi = AFI_IP;
	const struct prefix *prefix = NULL;
	struct hash *lsp_table;
	struct zebra_ile tmp_ile;
	struct zebra_lsp *lsp = NULL;

	/* Prep LSP for add case */
	if (add_p) {
		/* Lookup table. */
		lsp_table = zvrf->lsp_table;
		if (!lsp_table)
			return;

		/* Find or create LSP object */
		tmp_ile.in_label = zl->local_label;
		lsp = hash_get(lsp_table, &tmp_ile, lsp_alloc);
	}

	/* Prep for route/FEC update if requested */
	if (CHECK_FLAG(zl->message, ZAPI_LABELS_FTN)) {
		prefix = &zl->route.prefix;

		afi = family2afi(prefix->family);

		/* Lookup table.  */
		table = zebra_vrf_table(afi, SAFI_UNICAST, zvrf_id(zvrf));
		if (table) {
			/* Lookup existing route */
			rn = route_node_get(table, prefix);
			RNODE_FOREACH_RE(rn, re) {
				if (CHECK_FLAG(re->status, ROUTE_ENTRY_REMOVED))
					continue;
				if (re->type == zl->route.type &&
				    re->instance == zl->route.instance)
					break;
			}
		}

		if (re) {
			/*
			 * Copy over current nexthops into a temporary group.
			 * We can't just change the values here since the nhgs
			 * are shared and if the labels change, we'll need
			 * to find or create a new nhg. We need to create
			 * a whole temporary group, make changes to it,
			 * then attach that to the route.
			 */
			new_nhe = zebra_nhe_copy(re->nhe, 0);

		} else {
			/*
			 * The old version of the zapi code
			 * attempted to manage LSPs before trying to
			 * find a route/FEC, so we'll continue that way.
			 */
			if (IS_ZEBRA_DEBUG_RECV || IS_ZEBRA_DEBUG_MPLS)
				zlog_debug(
					"%s: FTN update requested: no route for prefix %pFX",
					__func__, prefix);
		}
	}

	/*
	 * Use info from the zapi nexthops to add/replace/remove LSP/FECs
	 */

	counter = 0;
	for (i = 0; i < zl->nexthop_num; i++) {

		znh = &zl->nexthops[i];

		/* Attempt LSP update */
		if (add_p)
			ret = lsp_znh_install(lsp, zl->type, znh);
		else
			ret = mpls_lsp_uninstall(zvrf, zl->type,
						 zl->local_label, znh->type,
						 &znh->gate, znh->ifindex,
						 false);
		if (ret < 0) {
			if (IS_ZEBRA_DEBUG_RECV || IS_ZEBRA_DEBUG_MPLS) {
				zapi_nexthop2str(znh, buf, sizeof(buf));
				zlog_debug("%s: Unable to %sinstall LSP: label %u, znh %s",
					   __func__, (add_p ? "" : "un"),
					   zl->local_label, buf);
			}
			continue;
		}

		/* Attempt route/FEC update if requested */
		if (re == NULL)
			continue;

		/* Search the route's nexthops for a match, and update it. */
		found = ftn_update_znh(add_p, zl->type, new_nhe->nhg.nexthop,
				       znh);
		if (found) {
			counter++;
		} else if (IS_ZEBRA_DEBUG_RECV | IS_ZEBRA_DEBUG_MPLS) {
			zapi_nexthop2str(znh, buf, sizeof(buf));
			zlog_debug(
				"%s: Unable to update FEC: prefix %pFX, label %u, znh %s",
				__func__, prefix, zl->local_label, buf);
		}
	}

	/*
	 * Process backup LSPs/nexthop entries also. We associate backup
	 * LSP info with backup nexthops.
	 */
	if (!CHECK_FLAG(zl->message, ZAPI_LABELS_HAS_BACKUPS))
		goto znh_done;

	for (i = 0; i < zl->backup_nexthop_num; i++) {

		znh = &zl->backup_nexthops[i];

		if (add_p)
			ret = lsp_backup_znh_install(lsp, zl->type, znh);
		else
			ret = mpls_lsp_uninstall(zvrf, zl->type,
						 zl->local_label,
						 znh->type, &znh->gate,
						 znh->ifindex, true);

		if (ret < 0) {
			if (IS_ZEBRA_DEBUG_RECV ||
			    IS_ZEBRA_DEBUG_MPLS) {
				zapi_nexthop2str(znh, buf, sizeof(buf));
				zlog_debug("%s: Unable to %sinstall backup LSP: label %u, znh %s",
					   __func__, (add_p ? "" : "un"),
					   zl->local_label, buf);
			}
			continue;
		}

		/* Attempt backup nexthop/FEC update if requested */
		if (re == NULL || zebra_nhg_get_backup_nhg(new_nhe) == NULL)
			continue;

		/* Search the route's backup nexthops for a match
		 * and update it.
		 */
		found = ftn_update_znh(add_p, zl->type,
				       new_nhe->backup_info->nhe->nhg.nexthop,
				       znh);
		if (found) {
			counter++;
		} else if (IS_ZEBRA_DEBUG_RECV | IS_ZEBRA_DEBUG_MPLS) {
			zapi_nexthop2str(znh, buf, sizeof(buf));
			zlog_debug(
				"%s: Unable to update backup FEC: prefix %pFX, label %u, znh %s",
				__func__, prefix, zl->local_label, buf);
		}
	}

znh_done:

	/*
	 * If we made changes, update the route, and schedule it
	 * for rib processing
	 */
	if (re != NULL && counter > 0) {
		assert(rn != NULL);

		SET_FLAG(re->status, ROUTE_ENTRY_CHANGED);
		SET_FLAG(re->status, ROUTE_ENTRY_LABELS_CHANGED);

		mpls_zebra_nhe_update(re, afi, new_nhe);

		rib_queue_add(rn);
	}

	if (new_nhe)
		zebra_nhg_free(new_nhe);
}

/*
 * Install/update a NHLFE for an LSP in the forwarding table. This may be
 * a new LSP entry or a new NHLFE for an existing in-label or an update of
 * the out-label for an existing NHLFE (update case).
 */
static struct zebra_nhlfe *
lsp_add_nhlfe(struct zebra_lsp *lsp, enum lsp_types_t type,
	      uint8_t num_out_labels, const mpls_label_t *out_labels,
	      enum nexthop_types_t gtype, const union g_addr *gate,
	      ifindex_t ifindex, vrf_id_t vrf_id, bool is_backup)
{
	struct zebra_nhlfe *nhlfe;
	char buf[MPLS_LABEL_STRLEN];
	const char *backup_str;

	if (is_backup) {
		nhlfe = nhlfe_find(&lsp->backup_nhlfe_list, type, gtype,
				   gate, ifindex);
		backup_str = "backup ";
	} else {
		nhlfe = nhlfe_find(&lsp->nhlfe_list, type, gtype, gate,
				   ifindex);
		backup_str = "";
	}

	if (nhlfe) {
		struct nexthop *nh = nhlfe->nexthop;

		assert(nh);

		/* Clear deleted flag (in case it was set) */
		UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED);

		if (!nh->nh_label || num_out_labels == 0)
			/* No change */
			return nhlfe;

		if (nh->nh_label &&
		    nh->nh_label->num_labels == num_out_labels &&
		    !memcmp(nh->nh_label->label, out_labels,
			    sizeof(mpls_label_t) * num_out_labels))
			/* No change */
			return nhlfe;

		if (IS_ZEBRA_DEBUG_MPLS) {
			char buf2[MPLS_LABEL_STRLEN];
			char buf3[MPLS_LABEL_STRLEN];

			nhlfe2str(nhlfe, buf, sizeof(buf));
			mpls_label2str(num_out_labels, out_labels, buf2,
				       sizeof(buf2), 0, 0);
			mpls_label2str(nh->nh_label->num_labels,
				       nh->nh_label->label, buf3, sizeof(buf3),
				       nh->nh_label_type, 0);

			zlog_debug("LSP in-label %u type %d %snexthop %s out-label(s) changed to %s (old %s)",
				   lsp->ile.in_label, type, backup_str, buf,
				   buf2, buf3);
		}

		/* Update out label(s), trigger processing. */
		if (nh->nh_label && nh->nh_label->num_labels == num_out_labels)
			memcpy(nh->nh_label->label, out_labels,
			       sizeof(mpls_label_t) * num_out_labels);
		else {
			nexthop_del_labels(nh);
			nexthop_add_labels(nh, type, num_out_labels,
					   out_labels);
		}
	} else {
		/* Add LSP entry to this nexthop */
		nhlfe = nhlfe_add(lsp, type, gtype, gate, ifindex, vrf_id,
				  num_out_labels, out_labels, is_backup);
		if (!nhlfe)
			return NULL;

		if (IS_ZEBRA_DEBUG_MPLS) {
			char buf2[MPLS_LABEL_STRLEN];

			nhlfe2str(nhlfe, buf, sizeof(buf));
			if (num_out_labels)
				mpls_label2str(num_out_labels, out_labels, buf2,
					       sizeof(buf2), 0, 0);
			else
				snprintf(buf2, sizeof(buf2), "-");

			zlog_debug("Add LSP in-label %u type %d %snexthop %s out-label(s) %s",
				   lsp->ile.in_label, type, backup_str, buf,
				   buf2);
		}

		lsp->addr_family = NHLFE_FAMILY(nhlfe);
	}

	/* Mark NHLFE, queue LSP for processing. */
	SET_FLAG(nhlfe->flags, NHLFE_FLAG_CHANGED);

	return nhlfe;
}

/*
 * Install an LSP and forwarding entry; used primarily
 * from vrf zapi message processing.
 * TODO: handle vrf_id parameter when mpls API extends to interface or SRTE
 * changes
 */
int mpls_lsp_install(struct zebra_vrf *zvrf, enum lsp_types_t type,
		     mpls_label_t in_label, uint8_t num_out_labels,
		     const mpls_label_t *out_labels, enum nexthop_types_t gtype,
		     const union g_addr *gate, ifindex_t ifindex)
{
	struct hash *lsp_table;
	struct zebra_ile tmp_ile;
	struct zebra_lsp *lsp;
	struct zebra_nhlfe *nhlfe;

	/* Lookup table. */
	lsp_table = zvrf->lsp_table;
	if (!lsp_table)
		return -1;

	/* Find or create LSP object */
	tmp_ile.in_label = in_label;
	lsp = hash_get(lsp_table, &tmp_ile, lsp_alloc);

	nhlfe = lsp_add_nhlfe(lsp, type, num_out_labels, out_labels, gtype,
			      gate, ifindex, VRF_DEFAULT, false /*backup*/);
	if (nhlfe == NULL)
		return -1;

	/* Queue LSP for processing. */
	if (lsp_processq_add(lsp))
		return -1;

	return 0;
}

/*
 * Install or replace NHLFE, using info from zapi nexthop
 */
static int lsp_znh_install(struct zebra_lsp *lsp, enum lsp_types_t type,
			   const struct zapi_nexthop *znh)
{
	struct zebra_nhlfe *nhlfe;

	nhlfe = lsp_add_nhlfe(lsp, type, znh->label_num, znh->labels, znh->type,
			      &znh->gate, znh->ifindex, znh->vrf_id,
			      false /*backup*/);
	if (nhlfe == NULL)
		return -1;

	/* Update backup info if present */
	if (CHECK_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP)) {
		if (znh->backup_num > NEXTHOP_MAX_BACKUPS) {
			nhlfe_del(nhlfe);
			return -1;
		}

		nhlfe->nexthop->backup_num = znh->backup_num;
		memcpy(nhlfe->nexthop->backup_idx, znh->backup_idx,
		       znh->backup_num);
		SET_FLAG(nhlfe->nexthop->flags, NEXTHOP_FLAG_HAS_BACKUP);
	} else {
		/* Ensure there's no stale backup info */
		UNSET_FLAG(nhlfe->nexthop->flags, NEXTHOP_FLAG_HAS_BACKUP);
		nhlfe->nexthop->backup_num = 0;
	}

	/* Queue LSP for processing. */
	if (lsp_processq_add(lsp))
		return -1;

	return 0;
}

/*
 * Install/update backup NHLFE for an LSP, using info from a zapi message.
 */
static int lsp_backup_znh_install(struct zebra_lsp *lsp, enum lsp_types_t type,
				  const struct zapi_nexthop *znh)
{
	struct zebra_nhlfe *nhlfe;

	nhlfe = lsp_add_nhlfe(lsp, type, znh->label_num, znh->labels, znh->type,
			      &znh->gate, znh->ifindex, znh->vrf_id,
			      true /*backup*/);
	if (nhlfe == NULL) {
		if (IS_ZEBRA_DEBUG_MPLS)
			zlog_debug("%s: unable to add backup nhlfe, label: %u",
				   __func__, lsp->ile.in_label);
		return -1;
	}

	/* Queue LSP for processing. */
	if (lsp_processq_add(lsp))
		return -1;

	return 0;
}

struct zebra_lsp *mpls_lsp_find(struct zebra_vrf *zvrf, mpls_label_t in_label)
{
	struct hash *lsp_table;
	struct zebra_ile tmp_ile;

	/* Lookup table. */
	lsp_table = zvrf->lsp_table;
	if (!lsp_table)
		return NULL;

	/* If entry is not present, exit. */
	tmp_ile.in_label = in_label;
	return hash_lookup(lsp_table, &tmp_ile);
}

/*
 * Uninstall a particular NHLFE in the forwarding table. If this is
 * the only NHLFE, the entire LSP forwarding entry has to be deleted.
 */
int mpls_lsp_uninstall(struct zebra_vrf *zvrf, enum lsp_types_t type,
		       mpls_label_t in_label, enum nexthop_types_t gtype,
		       const union g_addr *gate, ifindex_t ifindex,
		       bool backup_p)
{
	struct hash *lsp_table;
	struct zebra_ile tmp_ile;
	struct zebra_lsp *lsp;
	struct zebra_nhlfe *nhlfe;
	char buf[NEXTHOP_STRLEN];
	bool schedule_lsp = false;

	/* Lookup table. */
	lsp_table = zvrf->lsp_table;
	if (!lsp_table)
		return -1;

	/* If entry is not present, exit. */
	tmp_ile.in_label = in_label;
	lsp = hash_lookup(lsp_table, &tmp_ile);
	if (!lsp)
		return 0;

	if (backup_p)
		nhlfe = nhlfe_find(&lsp->backup_nhlfe_list, type, gtype,
				   gate, ifindex);
	else
		nhlfe = nhlfe_find(&lsp->nhlfe_list, type, gtype, gate,
				   ifindex);
	if (!nhlfe)
		return 0;

	if (IS_ZEBRA_DEBUG_MPLS) {
		nhlfe2str(nhlfe, buf, sizeof(buf));
		zlog_debug("Del LSP in-label %u type %d nexthop %s flags 0x%x",
			   in_label, type, buf, nhlfe->flags);
	}

	if (CHECK_FLAG(lsp->flags, LSP_FLAG_INSTALLED) ||
	    CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED))
		schedule_lsp = true;

	/* Mark NHLFE for delete or directly delete, as appropriate. */
	if (schedule_lsp) {
		SET_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED);
		UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_CHANGED);

		if (IS_ZEBRA_DEBUG_MPLS)
			zlog_debug("Schedule LSP in-label %u flags 0x%x",
				   lsp->ile.in_label, lsp->flags);
		if (lsp_processq_add(lsp))
			return -1;
	} else {
		nhlfe_del(nhlfe);

		/* Free LSP entry if no other NHLFEs and not scheduled. */
		lsp_check_free(lsp_table, &lsp);
	}
	return 0;
}

int mpls_lsp_uninstall_all_vrf(struct zebra_vrf *zvrf, enum lsp_types_t type,
			       mpls_label_t in_label)
{
	struct hash *lsp_table;
	struct zebra_ile tmp_ile;
	struct zebra_lsp *lsp;

	/* Lookup table. */
	lsp_table = zvrf->lsp_table;
	if (!lsp_table)
		return -1;

	/* If entry is not present, exit. */
	tmp_ile.in_label = in_label;
	lsp = hash_lookup(lsp_table, &tmp_ile);
	if (!lsp)
		return 0;

	return mpls_lsp_uninstall_all(lsp_table, lsp, type);
}

/*
 * Uninstall all NHLFEs for a particular LSP forwarding entry.
 * If no other NHLFEs exist, the entry would be deleted.
 */
static void mpls_lsp_uninstall_all_type(struct hash_bucket *bucket, void *ctxt)
{
	struct lsp_uninstall_args *args = ctxt;
	struct zebra_lsp *lsp;
	struct hash *lsp_table;

	lsp = (struct zebra_lsp *)bucket->data;
	if (nhlfe_list_first(&lsp->nhlfe_list) == NULL)
		return;

	lsp_table = args->lsp_table;
	if (!lsp_table)
		return;

	mpls_lsp_uninstall_all(lsp_table, lsp, args->type);
}

/*
 * Uninstall all FEC-To-NHLFE (FTN) bindings of the given address-family and
 * LSP type.
 */
static void mpls_ftn_uninstall_all(struct zebra_vrf *zvrf,
				   int afi, enum lsp_types_t lsp_type)
{
	struct route_table *table;
	struct route_node *rn;
	struct route_entry *re;
	struct nexthop *nexthop;
	struct nexthop_group *nhg;
	bool update;

	/* Process routes of interested address-families. */
	table = zebra_vrf_table(afi, SAFI_UNICAST, zvrf_id(zvrf));
	if (!table)
		return;

	for (rn = route_top(table); rn; rn = route_next(rn)) {
		update = false;

		RNODE_FOREACH_RE (rn, re) {
			struct nhg_hash_entry *new_nhe;

			new_nhe = zebra_nhe_copy(re->nhe, 0);

			nhg = &new_nhe->nhg;
			for (nexthop = nhg->nexthop; nexthop;
			     nexthop = nexthop->next) {
				if (nexthop->nh_label_type != lsp_type)
					continue;

				nexthop_del_labels(nexthop);
				SET_FLAG(re->status, ROUTE_ENTRY_CHANGED);
				SET_FLAG(re->status,
					 ROUTE_ENTRY_LABELS_CHANGED);
				update = true;
			}

			/* Check for backup info and update that also */
			nhg = zebra_nhg_get_backup_nhg(new_nhe);
			if (nhg != NULL) {
				for (nexthop = nhg->nexthop; nexthop;
				     nexthop = nexthop->next) {
					if (nexthop->nh_label_type != lsp_type)
						continue;

					nexthop_del_labels(nexthop);
					SET_FLAG(re->status,
						 ROUTE_ENTRY_CHANGED);
					SET_FLAG(re->status,
						 ROUTE_ENTRY_LABELS_CHANGED);
					update = true;
				}
			}

			if (CHECK_FLAG(re->status, ROUTE_ENTRY_LABELS_CHANGED))
				mpls_zebra_nhe_update(re, afi, new_nhe);

			zebra_nhg_free(new_nhe);
		}

		if (update)
			rib_queue_add(rn);
	}
}

#if defined(HAVE_CUMULUS)
/*
 * Check that the label values used in LSP creation are consistent. The
 * main criteria is that if there is ECMP, the label operation must still
 * be consistent - i.e., all paths either do a swap or do PHP. This is due
 * to current HW restrictions.
 */
int zebra_mpls_lsp_label_consistent(struct zebra_vrf *zvrf,
				    mpls_label_t in_label,
				    mpls_label_t out_label,
				    enum nexthop_types_t gtype,
				    union g_addr *gate, ifindex_t ifindex)
{
	struct hash *slsp_table;
	struct zebra_ile tmp_ile;
	struct zebra_lsp *lsp;
	struct zebra_nhlfe *nhlfe;
	const struct nexthop *nh;

	/* Lookup table. */
	slsp_table = zvrf->slsp_table;
	if (!slsp_table)
		return 0;

	/* If entry is not present, exit. */
	tmp_ile.in_label = in_label;
	lsp = hash_lookup(slsp_table, &tmp_ile);
	if (!lsp)
		return 1;

	nhlfe = nhlfe_find(&lsp->nhlfe_list, ZEBRA_LSP_STATIC,
			   gtype, gate, ifindex);
	if (nhlfe) {
		nh = nhlfe->nexthop;

		if (nh == NULL || nh->nh_label == NULL)
			return 0;

		if (nh->nh_label->label[0] == out_label)
			return 1;

		/* If not only NHLFE, cannot allow label change. */
		if (nhlfe != nhlfe_list_first(&lsp->nhlfe_list) ||
		    nhlfe_list_next(&lsp->nhlfe_list, nhlfe) != NULL)
			return 0;
	} else {
		/* If other NHLFEs exist, label operation must match. */
		nhlfe = nhlfe_list_first(&lsp->nhlfe_list);
		if (nhlfe != NULL) {
			int cur_op, new_op;

			nh = nhlfe->nexthop;

			if (nh == NULL || nh->nh_label == NULL)
				return 0;

			cur_op = (nh->nh_label->label[0] ==
				  MPLS_LABEL_IMPLICIT_NULL);
			new_op = (out_label == MPLS_LABEL_IMPLICIT_NULL);
			if (cur_op != new_op)
				return 0;
		}
	}

	/* Label values are good. */
	return 1;
}
#endif /* HAVE_CUMULUS */

/*
 * Add static LSP entry. This may be the first entry for this incoming label
 * or an additional nexthop; an existing entry may also have outgoing label
 * changed.
 * Note: The label operation (swap or PHP) is common for the LSP entry (all
 * NHLFEs).
 */
int zebra_mpls_static_lsp_add(struct zebra_vrf *zvrf, mpls_label_t in_label,
			      mpls_label_t out_label,
			      enum nexthop_types_t gtype, union g_addr *gate,
			      ifindex_t ifindex)
{
	struct hash *slsp_table;
	struct zebra_ile tmp_ile;
	struct zebra_lsp *lsp;
	struct zebra_nhlfe *nhlfe;
	char buf[BUFSIZ];

	/* Lookup table. */
	slsp_table = zvrf->slsp_table;
	if (!slsp_table)
		return -1;

	/* Find or create LSP. */
	tmp_ile.in_label = in_label;
	lsp = hash_get(slsp_table, &tmp_ile, lsp_alloc);

	nhlfe = nhlfe_find(&lsp->nhlfe_list, ZEBRA_LSP_STATIC, gtype, gate,
			   ifindex);
	if (nhlfe) {
		struct nexthop *nh = nhlfe->nexthop;

		assert(nh);
		assert(nh->nh_label);

		/* Compare existing nexthop */
		if (nh->nh_label->num_labels == 1 &&
		    nh->nh_label->label[0] == out_label)
			/* No change */
			return 0;

		if (IS_ZEBRA_DEBUG_MPLS) {
			nhlfe2str(nhlfe, buf, sizeof(buf));
			zlog_debug(
				"Upd static LSP in-label %u nexthop %s out-label %u (old %u)",
				in_label, buf, out_label,
				nh->nh_label->label[0]);
		}
		if (nh->nh_label->num_labels == 1)
			nh->nh_label->label[0] = out_label;
		else {
			nexthop_del_labels(nh);
			nexthop_add_labels(nh, ZEBRA_LSP_STATIC, 1, &out_label);
		}

	} else {
		/* Add static LSP entry to this nexthop */
		nhlfe = nhlfe_add(lsp, ZEBRA_LSP_STATIC, gtype, gate, ifindex,
				  VRF_DEFAULT, 1, &out_label, false /*backup*/);
		if (!nhlfe)
			return -1;

		if (IS_ZEBRA_DEBUG_MPLS) {
			nhlfe2str(nhlfe, buf, sizeof(buf));
			zlog_debug(
				"Add static LSP in-label %u nexthop %s out-label %u",
				in_label, buf, out_label);
		}
	}

	/* (Re)Install LSP in the main table. */
	if (mpls_lsp_install(zvrf, ZEBRA_LSP_STATIC, in_label, 1, &out_label,
			     gtype, gate, ifindex))
		return -1;

	return 0;
}

/*
 * Delete static LSP entry. This may be the delete of one particular
 * NHLFE for this incoming label or the delete of the entire entry (i.e.,
 * all NHLFEs).
 * NOTE: Delete of the only NHLFE will also end up deleting the entire
 * LSP configuration.
 */
int zebra_mpls_static_lsp_del(struct zebra_vrf *zvrf, mpls_label_t in_label,
			      enum nexthop_types_t gtype, union g_addr *gate,
			      ifindex_t ifindex)
{
	struct hash *slsp_table;
	struct zebra_ile tmp_ile;
	struct zebra_lsp *lsp;
	struct zebra_nhlfe *nhlfe;

	/* Lookup table. */
	slsp_table = zvrf->slsp_table;
	if (!slsp_table)
		return -1;

	/* If entry is not present, exit. */
	tmp_ile.in_label = in_label;
	lsp = hash_lookup(slsp_table, &tmp_ile);
	if (!lsp)
		return 0;

	/* Is it delete of entire LSP or a specific NHLFE? */
	if (gtype == NEXTHOP_TYPE_BLACKHOLE) {
		if (IS_ZEBRA_DEBUG_MPLS)
			zlog_debug("Del static LSP in-label %u", in_label);

		/* Uninstall entire LSP from the main table. */
		mpls_static_lsp_uninstall_all(zvrf, in_label);

		/* Delete all static NHLFEs */
		frr_each_safe(nhlfe_list, &lsp->nhlfe_list, nhlfe) {
			nhlfe_del(nhlfe);
		}
	} else {
		/* Find specific NHLFE, exit if not found. */
		nhlfe = nhlfe_find(&lsp->nhlfe_list, ZEBRA_LSP_STATIC,
				   gtype, gate, ifindex);
		if (!nhlfe)
			return 0;

		if (IS_ZEBRA_DEBUG_MPLS) {
			char buf[BUFSIZ];
			nhlfe2str(nhlfe, buf, sizeof(buf));
			zlog_debug("Del static LSP in-label %u nexthop %s",
				   in_label, buf);
		}

		/* Uninstall LSP from the main table. */
		mpls_lsp_uninstall(zvrf, ZEBRA_LSP_STATIC, in_label, gtype,
				   gate, ifindex, false);

		/* Delete static LSP NHLFE */
		nhlfe_del(nhlfe);
	}

	/* Remove entire static LSP entry if no NHLFE - valid in either case
	 * above.
	 */
	if (nhlfe_list_first(&lsp->nhlfe_list) == NULL) {
		lsp = hash_release(slsp_table, &tmp_ile);
		lsp_free_nhlfe(lsp);
		XFREE(MTYPE_LSP, lsp);
	}

	return 0;
}

/*
 * Schedule all MPLS label forwarding entries for processing.
 * Called upon changes that may affect one or more of them such as
 * interface or nexthop state changes.
 */
void zebra_mpls_lsp_schedule(struct zebra_vrf *zvrf)
{
	if (!zvrf)
		return;
	hash_iterate(zvrf->lsp_table, lsp_schedule, NULL);
}

/*
 * Display MPLS label forwarding table for a specific LSP
 * (VTY command handler).
 */
void zebra_mpls_print_lsp(struct vty *vty, struct zebra_vrf *zvrf,
			  mpls_label_t label, bool use_json)
{
	struct hash *lsp_table;
	struct zebra_lsp *lsp;
	struct zebra_ile tmp_ile;
	json_object *json = NULL;

	/* Lookup table. */
	lsp_table = zvrf->lsp_table;
	if (!lsp_table) {
		if (use_json)
			vty_out(vty, "{}\n");
		return;
	}

	/* If entry is not present, exit. */
	tmp_ile.in_label = label;
	lsp = hash_lookup(lsp_table, &tmp_ile);
	if (!lsp) {
		if (use_json)
			vty_out(vty, "{}\n");
		return;
	}

	if (use_json) {
		json = lsp_json(lsp);
		vty_json(vty, json);
	} else
		lsp_print(vty, lsp);
}

/*
 * Display MPLS label forwarding table (VTY command handler).
 */
void zebra_mpls_print_lsp_table(struct vty *vty, struct zebra_vrf *zvrf,
				bool use_json)
{
	char buf[BUFSIZ];
	json_object *json = NULL;
	struct zebra_lsp *lsp = NULL;
	struct zebra_nhlfe *nhlfe = NULL;
	struct listnode *node = NULL;
	struct list *lsp_list = hash_get_sorted_list(zvrf->lsp_table, lsp_cmp);

	if (use_json) {
		json = json_object_new_object();

		for (ALL_LIST_ELEMENTS_RO(lsp_list, node, lsp))
			json_object_object_add(json,
					       label2str(lsp->ile.in_label, 0,
							 buf, sizeof(buf)),
					       lsp_json(lsp));

		vty_json(vty, json);
	} else {
		struct ttable *tt;

		/* Prepare table. */
		tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
		ttable_add_row(tt, "Inbound Label|Type|Nexthop|Outbound Label");
		tt->style.cell.rpad = 2;
		tt->style.corner = '+';
		ttable_restyle(tt);
		ttable_rowseps(tt, 0, BOTTOM, true, '-');

		for (ALL_LIST_ELEMENTS_RO(lsp_list, node, lsp)) {
			frr_each_safe(nhlfe_list, &lsp->nhlfe_list, nhlfe) {
				struct nexthop *nexthop;
				const char *out_label_str;
				char nh_buf[NEXTHOP_STRLEN];

				nexthop = nhlfe->nexthop;

				switch (nexthop->type) {
				case NEXTHOP_TYPE_IFINDEX: {
					struct zebra_ns *zns;
					struct interface *ifp;

					zns = zebra_ns_lookup(NS_DEFAULT);
					ifp = if_lookup_by_index_per_ns(
						zns, nexthop->ifindex);
					snprintf(nh_buf, sizeof(nh_buf), "%s",
						 ifp ? ifp->name : "Null");
					break;
				}
				case NEXTHOP_TYPE_IPV4:
				case NEXTHOP_TYPE_IPV4_IFINDEX:
					inet_ntop(AF_INET, &nexthop->gate.ipv4,
						  nh_buf, sizeof(nh_buf));
					break;
				case NEXTHOP_TYPE_IPV6:
				case NEXTHOP_TYPE_IPV6_IFINDEX:
					inet_ntop(AF_INET6, &nexthop->gate.ipv6,
						  nh_buf, sizeof(nh_buf));
					break;
				case NEXTHOP_TYPE_BLACKHOLE:
					break;
				}

				if (nexthop->type != NEXTHOP_TYPE_IFINDEX &&
				    nexthop->nh_label)
					out_label_str = mpls_label2str(
						nexthop->nh_label->num_labels,
						&nexthop->nh_label->label[0],
						buf, sizeof(buf),
						nexthop->nh_label_type, 1);
				else
					out_label_str = "-";

				ttable_add_row(tt, "%u|%s|%s|%s",
					       lsp->ile.in_label,
					       nhlfe_type2str(nhlfe->type),
					       nh_buf, out_label_str);
			}
		}

		/* Dump the generated table. */
		if (tt->nrows > 1) {
			char *table = ttable_dump(tt, "\n");
			vty_out(vty, "%s\n", table);
			XFREE(MTYPE_TMP_TTABLE, table);
		}
		ttable_del(tt);
	}

	list_delete(&lsp_list);
}

/*
 * Create printable string for static LSP configuration.
 */
static char *nhlfe_config_str(const struct zebra_nhlfe *nhlfe, char *buf,
			      int size)
{
	const struct nexthop *nh;

	nh = nhlfe->nexthop;

	buf[0] = '\0';
	switch (nh->type) {
	case NEXTHOP_TYPE_IPV4:
	case NEXTHOP_TYPE_IPV4_IFINDEX:
		inet_ntop(AF_INET, &nh->gate.ipv4, buf, size);
		if (nh->ifindex)
			strlcat(buf, ifindex2ifname(nh->ifindex, VRF_DEFAULT),
				size);
		break;
	case NEXTHOP_TYPE_IPV6:
	case NEXTHOP_TYPE_IPV6_IFINDEX:
		inet_ntop(AF_INET6, &nh->gate.ipv6, buf, size);
		if (nh->ifindex)
			strlcat(buf,
				ifindex2ifname(nh->ifindex, VRF_DEFAULT),
				size);
		break;
	case NEXTHOP_TYPE_IFINDEX:
		if (nh->ifindex)
			strlcat(buf,
				ifindex2ifname(nh->ifindex, VRF_DEFAULT),
				size);
		break;
	case NEXTHOP_TYPE_BLACKHOLE:
		break;
	}

	return buf;
}

/*
 * Display MPLS LSP configuration of all static LSPs (VTY command handler).
 */
int zebra_mpls_write_lsp_config(struct vty *vty, struct zebra_vrf *zvrf)
{
	struct zebra_lsp *lsp;
	struct zebra_nhlfe *nhlfe;
	struct nexthop *nh;
	struct listnode *node;
	struct list *slsp_list =
		hash_get_sorted_list(zvrf->slsp_table, lsp_cmp);

	for (ALL_LIST_ELEMENTS_RO(slsp_list, node, lsp)) {
		frr_each(nhlfe_list, &lsp->nhlfe_list, nhlfe) {
			char buf[BUFSIZ];
			char lstr[30];

			nh = nhlfe->nexthop;
			if (nh == NULL || nh->nh_label == NULL)
				continue;

			nhlfe_config_str(nhlfe, buf, sizeof(buf));

			switch (nh->nh_label->label[0]) {
			case MPLS_LABEL_IPV4_EXPLICIT_NULL:
			case MPLS_LABEL_IPV6_EXPLICIT_NULL:
				strlcpy(lstr, "explicit-null", sizeof(lstr));
				break;
			case MPLS_LABEL_IMPLICIT_NULL:
				strlcpy(lstr, "implicit-null", sizeof(lstr));
				break;
			default:
				snprintf(lstr, sizeof(lstr), "%u",
					 nh->nh_label->label[0]);
				break;
			}

			vty_out(vty, "mpls lsp %u %s %s\n", lsp->ile.in_label,
				buf, lstr);
		}
	}

	list_delete(&slsp_list);
	return (zvrf->slsp_table->count ? 1 : 0);
}

/*
 * Add/update global label block.
 */
int zebra_mpls_label_block_add(struct zebra_vrf *zvrf, uint32_t start_label,
			       uint32_t end_label)
{
	zvrf->mpls_srgb.start_label = start_label;
	zvrf->mpls_srgb.end_label = end_label;

	/* Evaluate registered FECs to see if any get a label or not. */
	fec_evaluate(zvrf);
	return 0;
}

/*
 * Delete global label block.
 */
int zebra_mpls_label_block_del(struct zebra_vrf *zvrf)
{
	zvrf->mpls_srgb.start_label = MPLS_DEFAULT_MIN_SRGB_LABEL;
	zvrf->mpls_srgb.end_label = MPLS_DEFAULT_MAX_SRGB_LABEL;

	/* Process registered FECs to clear their local label, if needed. */
	fec_evaluate(zvrf);
	return 0;
}

/*
 * Display MPLS global label block configuration (VTY command handler).
 */
int zebra_mpls_write_label_block_config(struct vty *vty, struct zebra_vrf *zvrf)
{
	if (zvrf->mpls_srgb.start_label == 0)
		return 0;

	if ((zvrf->mpls_srgb.start_label != MPLS_DEFAULT_MIN_SRGB_LABEL)
	    || (zvrf->mpls_srgb.end_label != MPLS_DEFAULT_MAX_SRGB_LABEL)) {
		vty_out(vty, "mpls label global-block %u %u\n",
			zvrf->mpls_srgb.start_label, zvrf->mpls_srgb.end_label);
	}

	return 1;
}

/*
 * Called when VRF becomes inactive, cleans up information but keeps
 * the table itself.
 */
void zebra_mpls_cleanup_tables(struct zebra_vrf *zvrf)
{
	struct zebra_vrf *def_zvrf;
	afi_t afi;

	if (zvrf_id(zvrf) == VRF_DEFAULT)
		hash_iterate(zvrf->lsp_table, lsp_uninstall_from_kernel, NULL);
	else {
		/*
		 * For other vrfs, we try to remove associated LSPs; we locate
		 * the LSPs in the default vrf.
		 */
		def_zvrf = zebra_vrf_lookup_by_id(VRF_DEFAULT);

		/* At shutdown, the default may be gone already */
		if (def_zvrf == NULL)
			return;

		for (afi = AFI_IP; afi < AFI_MAX; afi++) {
			if (zvrf->label[afi] != MPLS_LABEL_NONE)
				lsp_uninstall(def_zvrf, zvrf->label[afi]);
		}
	}
}

/*
 * When a vrf label is assigned and the client goes away
 * we should cleanup the vrf labels associated with
 * that zclient.
 */
void zebra_mpls_client_cleanup_vrf_label(uint8_t proto)
{
	struct vrf *vrf;
	struct zebra_vrf *def_zvrf = zebra_vrf_lookup_by_id(VRF_DEFAULT);

	if (def_zvrf == NULL)
		return;

	RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) {
		struct zebra_vrf *zvrf = vrf->info;
		afi_t afi;

		if (!zvrf)
			continue;

		for (afi = AFI_IP; afi < AFI_MAX; afi++) {
			if (zvrf->label_proto[afi] == proto
			    && zvrf->label[afi] != MPLS_LABEL_NONE)
				lsp_uninstall(def_zvrf, zvrf->label[afi]);

			/*
			 * Cleanup data structures by fiat
			 */
			zvrf->label_proto[afi] = 0;
			zvrf->label[afi] = MPLS_LABEL_NONE;
		}
	}
}

static void lsp_table_free(void *p)
{
	struct zebra_lsp *lsp = p;

	lsp_free_nhlfe(lsp);

	XFREE(MTYPE_LSP, lsp);
}

/*
 * Called upon process exiting, need to delete LSP forwarding
 * entries from the kernel.
 * NOTE: Currently supported only for default VRF.
 */
void zebra_mpls_close_tables(struct zebra_vrf *zvrf)
{
	hash_iterate(zvrf->lsp_table, lsp_uninstall_from_kernel, NULL);
	hash_clean_and_free(&zvrf->lsp_table, lsp_table_free);
	hash_clean_and_free(&zvrf->slsp_table, lsp_table_free);
	route_table_finish(zvrf->fec_table[AFI_IP]);
	route_table_finish(zvrf->fec_table[AFI_IP6]);
}

/*
 * Allocate MPLS tables for this VRF and do other initialization.
 * NOTE: Currently supported only for default VRF.
 */
void zebra_mpls_init_tables(struct zebra_vrf *zvrf)
{
	char buffer[80];

	if (!zvrf)
		return;

	snprintf(buffer, sizeof(buffer), "ZEBRA SLSP table: %s",
		 zvrf->vrf->name);
	zvrf->slsp_table = hash_create_size(8, label_hash, label_cmp, buffer);

	snprintf(buffer, sizeof(buffer), "ZEBRA LSP table: %s",
		 zvrf->vrf->name);
	zvrf->lsp_table = hash_create_size(8, label_hash, label_cmp, buffer);
	zvrf->fec_table[AFI_IP] = route_table_init();
	zvrf->fec_table[AFI_IP6] = route_table_init();
	zvrf->mpls_flags = 0;
	zvrf->mpls_srgb.start_label = MPLS_DEFAULT_MIN_SRGB_LABEL;
	zvrf->mpls_srgb.end_label = MPLS_DEFAULT_MAX_SRGB_LABEL;
}

void zebra_mpls_turned_on(void)
{
	if (!mpls_enabled) {
		mpls_processq_init();
		mpls_enabled = true;

		hook_register(zserv_client_close,
			      zebra_mpls_cleanup_fecs_for_client);
		hook_register(zserv_client_close,
			      zebra_mpls_cleanup_zclient_labels);
	}
}

/*
 * Global MPLS initialization.
 */
void zebra_mpls_init(void)
{
	mpls_enabled = false;
	mpls_pw_reach_strict = false;

	if (mpls_kernel_init() < 0) {
		flog_warn(EC_ZEBRA_MPLS_SUPPORT_DISABLED,
			  "Disabling MPLS support (no kernel support)");
		return;
	}

	zebra_mpls_turned_on();
}

void zebra_mpls_terminate(void)
{
	hook_unregister(zserv_client_close, zebra_mpls_cleanup_fecs_for_client);
	hook_unregister(zserv_client_close, zebra_mpls_cleanup_zclient_labels);
}
