summaryrefslogtreecommitdiffstats
path: root/zebra/zebra_mpls.c
diff options
context:
space:
mode:
Diffstat (limited to 'zebra/zebra_mpls.c')
-rw-r--r--zebra/zebra_mpls.c4079
1 files changed, 4079 insertions, 0 deletions
diff --git a/zebra/zebra_mpls.c b/zebra/zebra_mpls.c
new file mode 100644
index 0000000..9756d9b
--- /dev/null
+++ b/zebra/zebra_mpls.c
@@ -0,0 +1,4079 @@
+/* Zebra MPLS code
+ * Copyright (C) 2013 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#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 "thread.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");
+
+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);
+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, 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;
+
+ /* Lookup table. */
+ lsp_table = zvrf->lsp_table;
+ if (!lsp_table)
+ return -1;
+
+ 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).
+ */
+ for (nexthop = re->nhe->nhg.nexthop;
+ nexthop; nexthop = nexthop->next) {
+ /* Skip inactive and recursive entries. */
+ if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE))
+ continue;
+ if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE))
+ 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 */
+ 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->nh_label->num_labels,
+ nexthop->nh_label->label,
+ false /*backup*/);
+ if (!nhlfe)
+ return -1;
+
+ if (IS_ZEBRA_DEBUG_MPLS) {
+ nhlfe2str(nhlfe, buf, BUFSIZ);
+ zlog_debug(
+ "Add LSP in-label %u type %d nexthop %s out-label %u",
+ lsp->ile.in_label, lsp_type, buf,
+ nexthop->nh_label->label[0]);
+ }
+
+ lsp->addr_family = NHLFE_FAMILY(nhlfe);
+
+ /* Mark NHLFE as changed. */
+ SET_FLAG(nhlfe->flags, NHLFE_FLAG_CHANGED);
+ added++;
+ }
+ }
+
+ /* 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);
+ }
+
+ 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 (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);
+ }
+ }
+}
+
+/*
+ * 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)
+{
+ 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))
+ return 0;
+
+ if (lsp_install(zvrf, fec->label, rn, re))
+ 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);
+}
+
+/*
+ * 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, 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
+ || 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)
+ && !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 = vrf_info_lookup(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 = vrf_info_lookup(VRF_DEFAULT);
+ assert(zvrf);
+
+ 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);
+ 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.
+ */
+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, 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_DEFAULT;
+ 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, uint8_t num_labels,
+ const mpls_label_t *labels, bool is_backup)
+{
+ struct zebra_nhlfe *nhlfe;
+
+ if (!lsp)
+ return NULL;
+
+ /* Must have labels */
+ if (num_labels == 0 || labels == NULL) {
+ if (IS_ZEBRA_DEBUG_MPLS)
+ zlog_debug("%s: invalid nexthop: no labels", __func__);
+
+ return NULL;
+ }
+
+ /* Allocate new object */
+ nhlfe = nhlfe_alloc(lsp, lsp_type, gtype, gate, ifindex, 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)
+{
+ nhlfe->nexthop->nh_label->label[0] = nh_label->label[0];
+}
+
+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));
+ 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);
+ 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), 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.errorfunc = NULL;
+ 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);
+
+ switch (op) {
+ case DPLANE_OP_LSP_INSTALL:
+ case DPLANE_OP_LSP_UPDATE:
+ /* Look for zebra LSP object */
+ zvrf = vrf_info_lookup(VRF_DEFAULT);
+ if (zvrf == NULL)
+ break;
+
+ 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));
+ break;
+ }
+
+ /* 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);
+ break;
+ }
+
+ /* 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);
+ break;
+
+ case 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));
+ break;
+ }
+ zebra_sr_policy_label_update(label,
+ ZEBRA_SR_POLICY_LABEL_REMOVED);
+ break;
+
+ default:
+ break;
+
+ } /* Switch */
+}
+
+/*
+ * 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 = vrf_info_lookup(VRF_DEFAULT);
+ if (zvrf == NULL)
+ return;
+
+ 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.
+ */
+int 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 -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;
+
+ /* We cannot install a label forwarding entry if local label is the
+ * implicit-null label.
+ */
+ if (fec->label == MPLS_LABEL_IMPLICIT_NULL)
+ return 0;
+
+ if (lsp_install(zvrf, fec->label, rn, re))
+ return -1;
+
+ return 0;
+}
+
+/*
+ * 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, 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, 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;
+
+ if (nh->nh_label == NULL || nh->nh_label->num_labels == 0)
+ return NULL;
+
+ nhlfe = nhlfe_add(lsp, lsp_type, nh->type, &nh->gate, nh->ifindex,
+ nh->nh_label->num_labels, nh->nh_label->label,
+ 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;
+
+ if (nh->nh_label == NULL || nh->nh_label->num_labels == 0)
+ return NULL;
+
+ nhlfe = nhlfe_add(lsp, lsp_type, nh->type, &nh->gate,
+ nh->ifindex, nh->nh_label->num_labels,
+ nh->nh_label->label, 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 = 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);
+
+ 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 (!(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);
+ 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 = vrf_info_lookup(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 (!(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,
+ 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)
+ 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, mpls_label_t label)
+{
+ return (zebra_mpls_fec_for_label(zvrf, 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 {
+ 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);
+ }
+
+ 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;
+ 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);
+}
+
+/*
+ * 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 (!(fec->flags & FEC_FLAG_CONFIGURED))
+ continue;
+
+ write = 1;
+ vty_out(vty, "mpls label bind %pFX %s\n", &rn->p,
+ label2str(fec->label, lstr, BUFSIZ));
+ }
+ }
+
+ 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, 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);
+ assert(nh->nh_label);
+
+ /* Clear deleted flag (in case it was set) */
+ UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_DELETED);
+ if (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);
+ mpls_label2str(nh->nh_label->num_labels,
+ nh->nh_label->label, buf3, sizeof(buf3),
+ 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->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,
+ 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));
+ mpls_label2str(num_out_labels, out_labels, buf2,
+ sizeof(buf2), 0);
+
+ 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.
+ */
+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, 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,
+ 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, 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, 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)
+ return;
+
+ /* If entry is not present, exit. */
+ tmp_ile.in_label = label;
+ lsp = hash_lookup(lsp_table, &tmp_ile);
+ if (!lsp)
+ 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, 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)
+ out_label_str = mpls_label2str(
+ nexthop->nh_label->num_labels,
+ &nexthop->nh_label->label[0],
+ buf, sizeof(buf), 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, 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(zvrf->lsp_table, lsp_table_free);
+ hash_free(zvrf->lsp_table);
+ hash_clean(zvrf->slsp_table, lsp_table_free);
+ hash_free(zvrf->slsp_table);
+ 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();
+}