summaryrefslogtreecommitdiffstats
path: root/lib/nexthop_group.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/nexthop_group.c')
-rw-r--r--lib/nexthop_group.c1400
1 files changed, 1400 insertions, 0 deletions
diff --git a/lib/nexthop_group.c b/lib/nexthop_group.c
new file mode 100644
index 0000000..25370eb
--- /dev/null
+++ b/lib/nexthop_group.c
@@ -0,0 +1,1400 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Nexthop Group structure definition.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ */
+#include <zebra.h>
+
+#include <vrf.h>
+#include <sockunion.h>
+#include <nexthop.h>
+#include <nexthop_group.h>
+#include <nexthop_group_private.h>
+#include <vty.h>
+#include <command.h>
+#include <jhash.h>
+
+#include "lib/nexthop_group_clippy.c"
+
+DEFINE_MTYPE_STATIC(LIB, NEXTHOP_GROUP, "Nexthop Group");
+
+/*
+ * Internal struct used to hold nhg config strings
+ */
+struct nexthop_hold {
+ char *nhvrf_name;
+ union sockunion *addr;
+ char *intf;
+ bool onlink;
+ char *labels;
+ vni_t vni;
+ uint32_t weight;
+ char *backup_str;
+};
+
+struct nexthop_group_hooks {
+ void (*new)(const char *name);
+ void (*modify)(const struct nexthop_group_cmd *nhgc);
+ void (*add_nexthop)(const struct nexthop_group_cmd *nhg,
+ const struct nexthop *nhop);
+ void (*del_nexthop)(const struct nexthop_group_cmd *nhg,
+ const struct nexthop *nhop);
+ void (*delete)(const char *name);
+};
+
+static struct nexthop_group_hooks nhg_hooks;
+
+static inline int
+nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1,
+ const struct nexthop_group_cmd *nhgc2);
+RB_GENERATE(nhgc_entry_head, nexthop_group_cmd, nhgc_entry,
+ nexthop_group_cmd_compare)
+
+static struct nhgc_entry_head nhgc_entries;
+
+static inline int
+nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1,
+ const struct nexthop_group_cmd *nhgc2)
+{
+ return strcmp(nhgc1->name, nhgc2->name);
+}
+
+static struct nexthop *nexthop_group_tail(const struct nexthop_group *nhg)
+{
+ struct nexthop *nexthop = nhg->nexthop;
+
+ while (nexthop && nexthop->next)
+ nexthop = nexthop->next;
+
+ return nexthop;
+}
+
+uint8_t nexthop_group_nexthop_num(const struct nexthop_group *nhg)
+{
+ struct nexthop *nhop;
+ uint8_t num = 0;
+
+ for (ALL_NEXTHOPS_PTR(nhg, nhop))
+ num++;
+
+ return num;
+}
+
+uint8_t nexthop_group_nexthop_num_no_recurse(const struct nexthop_group *nhg)
+{
+ struct nexthop *nhop;
+ uint8_t num = 0;
+
+ for (nhop = nhg->nexthop; nhop; nhop = nhop->next)
+ num++;
+
+ return num;
+}
+
+uint8_t nexthop_group_active_nexthop_num(const struct nexthop_group *nhg)
+{
+ struct nexthop *nhop;
+ uint8_t num = 0;
+
+ for (ALL_NEXTHOPS_PTR(nhg, nhop)) {
+ if (CHECK_FLAG(nhop->flags, NEXTHOP_FLAG_ACTIVE))
+ num++;
+ }
+
+ return num;
+}
+
+uint8_t
+nexthop_group_active_nexthop_num_no_recurse(const struct nexthop_group *nhg)
+{
+ struct nexthop *nhop;
+ uint8_t num = 0;
+
+ for (nhop = nhg->nexthop; nhop; nhop = nhop->next) {
+ if (CHECK_FLAG(nhop->flags, NEXTHOP_FLAG_ACTIVE))
+ num++;
+ }
+
+ return num;
+}
+
+bool nexthop_group_has_label(const struct nexthop_group *nhg)
+{
+ struct nexthop *nhop;
+
+ for (ALL_NEXTHOPS_PTR(nhg, nhop)) {
+ if (nhop->nh_label)
+ return true;
+ }
+
+ return false;
+}
+
+struct nexthop *nexthop_exists(const struct nexthop_group *nhg,
+ const struct nexthop *nh)
+{
+ struct nexthop *nexthop;
+
+ for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) {
+ if (nexthop_same(nh, nexthop))
+ return nexthop;
+ }
+
+ return NULL;
+}
+
+/*
+ * Helper that locates a nexthop in an nhg config list. Note that
+ * this uses a specific matching / equality rule that's different from
+ * the complete match performed by 'nexthop_same()'.
+ */
+static struct nexthop *nhg_nh_find(const struct nexthop_group *nhg,
+ const struct nexthop *nh)
+{
+ struct nexthop *nexthop;
+ int ret;
+
+ /* We compare: vrf, gateway, and interface */
+
+ for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) {
+
+ /* Compare vrf and type */
+ if (nexthop->vrf_id != nh->vrf_id)
+ continue;
+ if (nexthop->type != nh->type)
+ continue;
+
+ /* Compare gateway */
+ switch (nexthop->type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV6:
+ ret = nexthop_g_addr_cmp(nexthop->type,
+ &nexthop->gate, &nh->gate);
+ if (ret != 0)
+ continue;
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ ret = nexthop_g_addr_cmp(nexthop->type,
+ &nexthop->gate, &nh->gate);
+ if (ret != 0)
+ continue;
+ /* Intentional Fall-Through */
+ case NEXTHOP_TYPE_IFINDEX:
+ if (nexthop->ifindex != nh->ifindex)
+ continue;
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ if (nexthop->bh_type != nh->bh_type)
+ continue;
+ break;
+ }
+
+ return nexthop;
+ }
+
+ return NULL;
+}
+
+static bool
+nexthop_group_equal_common(const struct nexthop_group *nhg1,
+ const struct nexthop_group *nhg2,
+ uint8_t (*nexthop_group_nexthop_num_func)(
+ const struct nexthop_group *nhg))
+{
+ if (nhg1 && !nhg2)
+ return false;
+
+ if (!nhg1 && nhg2)
+ return false;
+
+ if (nhg1 == nhg2)
+ return true;
+
+ if (nexthop_group_nexthop_num_func(nhg1)
+ != nexthop_group_nexthop_num_func(nhg2))
+ return false;
+
+ return true;
+}
+
+/* This assumes ordered */
+bool nexthop_group_equal_no_recurse(const struct nexthop_group *nhg1,
+ const struct nexthop_group *nhg2)
+{
+ struct nexthop *nh1 = NULL;
+ struct nexthop *nh2 = NULL;
+
+ if (!nexthop_group_equal_common(nhg1, nhg2,
+ &nexthop_group_nexthop_num_no_recurse))
+ return false;
+
+ for (nh1 = nhg1->nexthop, nh2 = nhg2->nexthop; nh1 || nh2;
+ nh1 = nh1->next, nh2 = nh2->next) {
+ if (nh1 && !nh2)
+ return false;
+ if (!nh1 && nh2)
+ return false;
+ if (!nexthop_same(nh1, nh2))
+ return false;
+ }
+
+ return true;
+}
+
+/* This assumes ordered */
+bool nexthop_group_equal(const struct nexthop_group *nhg1,
+ const struct nexthop_group *nhg2)
+{
+ struct nexthop *nh1 = NULL;
+ struct nexthop *nh2 = NULL;
+
+ if (!nexthop_group_equal_common(nhg1, nhg2, &nexthop_group_nexthop_num))
+ return false;
+
+ for (nh1 = nhg1->nexthop, nh2 = nhg2->nexthop; nh1 || nh2;
+ nh1 = nexthop_next(nh1), nh2 = nexthop_next(nh2)) {
+ if (nh1 && !nh2)
+ return false;
+ if (!nh1 && nh2)
+ return false;
+ if (!nexthop_same(nh1, nh2))
+ return false;
+ }
+
+ return true;
+}
+struct nexthop_group *nexthop_group_new(void)
+{
+ return XCALLOC(MTYPE_NEXTHOP_GROUP, sizeof(struct nexthop_group));
+}
+
+void nexthop_group_copy(struct nexthop_group *to,
+ const struct nexthop_group *from)
+{
+ to->nhgr = from->nhgr;
+ /* Copy everything, including recursive info */
+ copy_nexthops(&to->nexthop, from->nexthop, NULL);
+}
+
+void nexthop_group_delete(struct nexthop_group **nhg)
+{
+ /* OK to call with NULL group */
+ if ((*nhg) == NULL)
+ return;
+
+ if ((*nhg)->nexthop)
+ nexthops_free((*nhg)->nexthop);
+
+ XFREE(MTYPE_NEXTHOP_GROUP, *nhg);
+}
+
+/* Add nexthop to the end of a nexthop list. */
+void _nexthop_add(struct nexthop **target, struct nexthop *nexthop)
+{
+ struct nexthop *last;
+
+ for (last = *target; last && last->next; last = last->next)
+ ;
+ if (last)
+ last->next = nexthop;
+ else
+ *target = nexthop;
+ nexthop->prev = last;
+}
+
+/* Add nexthop to sorted list of nexthops */
+static void _nexthop_add_sorted(struct nexthop **head,
+ struct nexthop *nexthop)
+{
+ struct nexthop *position, *prev;
+
+ assert(!nexthop->next);
+
+ for (position = *head, prev = NULL; position;
+ prev = position, position = position->next) {
+ if (nexthop_cmp(position, nexthop) > 0) {
+ nexthop->next = position;
+ nexthop->prev = prev;
+
+ if (nexthop->prev)
+ nexthop->prev->next = nexthop;
+ else
+ *head = nexthop;
+
+ position->prev = nexthop;
+ return;
+ }
+ }
+
+ nexthop->prev = prev;
+ if (prev)
+ prev->next = nexthop;
+ else
+ *head = nexthop;
+}
+
+void nexthop_group_add_sorted(struct nexthop_group *nhg,
+ struct nexthop *nexthop)
+{
+ struct nexthop *tail;
+
+ assert(!nexthop->next);
+
+ /* Try to just append to the end first;
+ * trust the list is already sorted
+ */
+ tail = nexthop_group_tail(nhg);
+
+ if (tail && (nexthop_cmp(tail, nexthop) < 0)) {
+ tail->next = nexthop;
+ nexthop->prev = tail;
+
+ return;
+ }
+
+ _nexthop_add_sorted(&nhg->nexthop, nexthop);
+}
+
+/* Delete nexthop from a nexthop list. */
+void _nexthop_del(struct nexthop_group *nhg, struct nexthop *nh)
+{
+ struct nexthop *nexthop;
+
+ for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) {
+ if (nexthop_same(nh, nexthop))
+ break;
+ }
+
+ assert(nexthop);
+
+ if (nexthop->prev)
+ nexthop->prev->next = nexthop->next;
+ else
+ nhg->nexthop = nexthop->next;
+
+ if (nexthop->next)
+ nexthop->next->prev = nexthop->prev;
+
+ nh->prev = NULL;
+ nh->next = NULL;
+}
+
+/* Unlink a nexthop from the list it's on, unconditionally */
+static void nexthop_unlink(struct nexthop_group *nhg, struct nexthop *nexthop)
+{
+
+ if (nexthop->prev)
+ nexthop->prev->next = nexthop->next;
+ else {
+ assert(nhg->nexthop == nexthop);
+ assert(nexthop->prev == NULL);
+ nhg->nexthop = nexthop->next;
+ }
+
+ if (nexthop->next)
+ nexthop->next->prev = nexthop->prev;
+
+ nexthop->prev = NULL;
+ nexthop->next = NULL;
+}
+
+/*
+ * Copy a list of nexthops in 'nh' to an nhg, enforcing canonical sort order
+ */
+void nexthop_group_copy_nh_sorted(struct nexthop_group *nhg,
+ const struct nexthop *nh)
+{
+ struct nexthop *nexthop, *tail;
+ const struct nexthop *nh1;
+
+ /* We'll try to append to the end of the new list;
+ * if the original list in nh is already sorted, this eliminates
+ * lots of comparison operations.
+ */
+ tail = nexthop_group_tail(nhg);
+
+ for (nh1 = nh; nh1; nh1 = nh1->next) {
+ nexthop = nexthop_dup(nh1, NULL);
+
+ if (tail && (nexthop_cmp(tail, nexthop) < 0)) {
+ tail->next = nexthop;
+ nexthop->prev = tail;
+
+ tail = nexthop;
+ continue;
+ }
+
+ _nexthop_add_sorted(&nhg->nexthop, nexthop);
+
+ if (tail == NULL)
+ tail = nexthop;
+ }
+}
+
+/* Copy a list of nexthops, no effort made to sort or order them. */
+void copy_nexthops(struct nexthop **tnh, const struct nexthop *nh,
+ struct nexthop *rparent)
+{
+ struct nexthop *nexthop;
+ const struct nexthop *nh1;
+
+ for (nh1 = nh; nh1; nh1 = nh1->next) {
+ nexthop = nexthop_dup(nh1, rparent);
+ _nexthop_add(tnh, nexthop);
+ }
+}
+
+uint32_t nexthop_group_hash_no_recurse(const struct nexthop_group *nhg)
+{
+ struct nexthop *nh;
+ uint32_t key = 0;
+
+ /*
+ * We are not interested in hashing over any recursively
+ * resolved nexthops
+ */
+ for (nh = nhg->nexthop; nh; nh = nh->next)
+ key = jhash_1word(nexthop_hash(nh), key);
+
+ return key;
+}
+
+uint32_t nexthop_group_hash(const struct nexthop_group *nhg)
+{
+ struct nexthop *nh;
+ uint32_t key = 0;
+
+ for (ALL_NEXTHOPS_PTR(nhg, nh))
+ key = jhash_1word(nexthop_hash(nh), key);
+
+ return key;
+}
+
+void nexthop_group_mark_duplicates(struct nexthop_group *nhg)
+{
+ struct nexthop *nexthop, *prev;
+
+ for (ALL_NEXTHOPS_PTR(nhg, nexthop)) {
+ UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE);
+ for (ALL_NEXTHOPS_PTR(nhg, prev)) {
+ if (prev == nexthop)
+ break;
+ if (nexthop_same(nexthop, prev)) {
+ SET_FLAG(nexthop->flags,
+ NEXTHOP_FLAG_DUPLICATE);
+ break;
+ }
+ }
+ }
+}
+
+static void nhgc_delete_nexthops(struct nexthop_group_cmd *nhgc)
+{
+ struct nexthop *nexthop;
+
+ nexthop = nhgc->nhg.nexthop;
+ while (nexthop) {
+ struct nexthop *next = nexthop_next(nexthop);
+
+ _nexthop_del(&nhgc->nhg, nexthop);
+ if (nhg_hooks.del_nexthop)
+ nhg_hooks.del_nexthop(nhgc, nexthop);
+
+ nexthop_free(nexthop);
+
+ nexthop = next;
+ }
+}
+
+struct nexthop_group_cmd *nhgc_find(const char *name)
+{
+ struct nexthop_group_cmd find;
+
+ strlcpy(find.name, name, sizeof(find.name));
+
+ return RB_FIND(nhgc_entry_head, &nhgc_entries, &find);
+}
+
+static int nhgc_cmp_helper(const char *a, const char *b)
+{
+ if (!a && !b)
+ return 0;
+
+ if (a && !b)
+ return -1;
+
+ if (!a && b)
+ return 1;
+
+ return strcmp(a, b);
+}
+
+static int nhgc_addr_cmp_helper(const union sockunion *a, const union sockunion *b)
+{
+ if (!a && !b)
+ return 0;
+
+ if (a && !b)
+ return -1;
+
+ if (!a && b)
+ return 1;
+
+ return sockunion_cmp(a, b);
+}
+
+static int nhgl_cmp(struct nexthop_hold *nh1, struct nexthop_hold *nh2)
+{
+ int ret;
+
+ ret = nhgc_addr_cmp_helper(nh1->addr, nh2->addr);
+ if (ret)
+ return ret;
+
+ ret = nhgc_cmp_helper(nh1->intf, nh2->intf);
+ if (ret)
+ return ret;
+
+ ret = nhgc_cmp_helper(nh1->nhvrf_name, nh2->nhvrf_name);
+ if (ret)
+ return ret;
+
+ ret = ((int)nh2->onlink) - ((int)nh1->onlink);
+ if (ret)
+ return ret;
+
+ return nhgc_cmp_helper(nh1->labels, nh2->labels);
+}
+
+static void nhgl_delete(struct nexthop_hold *nh)
+{
+ XFREE(MTYPE_TMP, nh->intf);
+
+ XFREE(MTYPE_TMP, nh->nhvrf_name);
+
+ if (nh->addr)
+ sockunion_free(nh->addr);
+
+ XFREE(MTYPE_TMP, nh->labels);
+
+ XFREE(MTYPE_TMP, nh);
+}
+
+static struct nexthop_group_cmd *nhgc_get(const char *name)
+{
+ struct nexthop_group_cmd *nhgc;
+
+ nhgc = nhgc_find(name);
+ if (!nhgc) {
+ nhgc = XCALLOC(MTYPE_TMP, sizeof(*nhgc));
+ strlcpy(nhgc->name, name, sizeof(nhgc->name));
+
+ QOBJ_REG(nhgc, nexthop_group_cmd);
+ RB_INSERT(nhgc_entry_head, &nhgc_entries, nhgc);
+
+ nhgc->nhg_list = list_new();
+ nhgc->nhg_list->cmp = (int (*)(void *, void *))nhgl_cmp;
+ nhgc->nhg_list->del = (void (*)(void *))nhgl_delete;
+
+ if (nhg_hooks.new)
+ nhg_hooks.new(name);
+ }
+
+ return nhgc;
+}
+
+static void nhgc_delete(struct nexthop_group_cmd *nhgc)
+{
+ nhgc_delete_nexthops(nhgc);
+
+ if (nhg_hooks.delete)
+ nhg_hooks.delete(nhgc->name);
+
+ RB_REMOVE(nhgc_entry_head, &nhgc_entries, nhgc);
+
+ list_delete(&nhgc->nhg_list);
+
+ QOBJ_UNREG(nhgc);
+ XFREE(MTYPE_TMP, nhgc);
+}
+
+DEFINE_QOBJ_TYPE(nexthop_group_cmd);
+
+DEFUN_NOSH(nexthop_group, nexthop_group_cmd, "nexthop-group NHGNAME",
+ "Enter into the nexthop-group submode\n"
+ "Specify the NAME of the nexthop-group\n")
+{
+ const char *nhg_name = argv[1]->arg;
+ struct nexthop_group_cmd *nhgc = NULL;
+
+ nhgc = nhgc_get(nhg_name);
+ VTY_PUSH_CONTEXT(NH_GROUP_NODE, nhgc);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_NOSH(no_nexthop_group, no_nexthop_group_cmd, "no nexthop-group NHGNAME",
+ NO_STR
+ "Delete the nexthop-group\n"
+ "Specify the NAME of the nexthop-group\n")
+{
+ const char *nhg_name = argv[2]->arg;
+ struct nexthop_group_cmd *nhgc = NULL;
+
+ nhgc = nhgc_find(nhg_name);
+ if (nhgc)
+ nhgc_delete(nhgc);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(nexthop_group_backup, nexthop_group_backup_cmd,
+ "backup-group WORD$name",
+ "Specify a group name containing backup nexthops\n"
+ "The name of the backup group\n")
+{
+ VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc);
+
+ strlcpy(nhgc->backup_list_name, name, sizeof(nhgc->backup_list_name));
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(no_nexthop_group_backup, no_nexthop_group_backup_cmd,
+ "no backup-group [WORD$name]",
+ NO_STR
+ "Clear group name containing backup nexthops\n"
+ "The name of the backup group\n")
+{
+ VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc);
+
+ nhgc->backup_list_name[0] = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(nexthop_group_resilience,
+ nexthop_group_resilience_cmd,
+ "resilient buckets (1-256) idle-timer (1-4294967295) unbalanced-timer (1-4294967295)",
+ "A resilient Nexthop Group\n"
+ "Buckets in the Hash for this Group\n"
+ "Number of buckets\n"
+ "The Idle timer for this Resilient Nexthop Group in seconds\n"
+ "Number of seconds of Idle time\n"
+ "The length of time that the Nexthop Group can be unbalanced\n"
+ "Number of seconds of Unbalanced time\n")
+{
+ VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc);
+
+ nhgc->nhg.nhgr.buckets = buckets;
+ nhgc->nhg.nhgr.idle_timer = idle_timer;
+ nhgc->nhg.nhgr.unbalanced_timer = unbalanced_timer;
+
+ if (nhg_hooks.modify)
+ nhg_hooks.modify(nhgc);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(no_nexthop_group_resilience,
+ no_nexthop_group_resilience_cmd,
+ "no resilient [buckets (1-256) idle-timer (1-4294967295) unbalanced-timer (1-4294967295)]",
+ NO_STR
+ "A resilient Nexthop Group\n"
+ "Buckets in the Hash for this Group\n"
+ "Number of buckets\n"
+ "The Idle timer for this Resilient Nexthop Group in seconds\n"
+ "Number of seconds of Idle time\n"
+ "The length of time that the Nexthop Group can be unbalanced\n"
+ "Number of seconds of Unbalanced time\n")
+{
+ VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc);
+
+ nhgc->nhg.nhgr.buckets = 0;
+ nhgc->nhg.nhgr.idle_timer = 0;
+ nhgc->nhg.nhgr.unbalanced_timer = 0;
+
+ return CMD_SUCCESS;
+}
+
+static void nexthop_group_save_nhop(struct nexthop_group_cmd *nhgc,
+ const char *nhvrf_name,
+ const union sockunion *addr,
+ const char *intf, bool onlink,
+ const char *labels, const uint32_t weight,
+ const char *backup_str)
+{
+ struct nexthop_hold *nh;
+
+ nh = XCALLOC(MTYPE_TMP, sizeof(*nh));
+
+ if (nhvrf_name)
+ nh->nhvrf_name = XSTRDUP(MTYPE_TMP, nhvrf_name);
+ if (intf)
+ nh->intf = XSTRDUP(MTYPE_TMP, intf);
+ if (addr)
+ nh->addr = sockunion_dup(addr);
+ if (labels)
+ nh->labels = XSTRDUP(MTYPE_TMP, labels);
+
+ nh->onlink = onlink;
+
+ nh->weight = weight;
+
+ if (backup_str)
+ nh->backup_str = XSTRDUP(MTYPE_TMP, backup_str);
+
+ listnode_add_sort(nhgc->nhg_list, nh);
+}
+
+/*
+ * Remove config info about a nexthop from group 'nhgc'. Note that we
+ * use only a subset of the available attributes here to determine
+ * a 'match'.
+ * Note that this doesn't change the list of nexthops, only the config
+ * information.
+ */
+static void nexthop_group_unsave_nhop(struct nexthop_group_cmd *nhgc,
+ const char *nhvrf_name,
+ const union sockunion *addr,
+ const char *intf)
+{
+ struct nexthop_hold *nh;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) {
+ if (nhgc_cmp_helper(nhvrf_name, nh->nhvrf_name) == 0
+ && nhgc_addr_cmp_helper(addr, nh->addr) == 0
+ && nhgc_cmp_helper(intf, nh->intf) == 0)
+ break;
+ }
+
+ /*
+ * Something has gone seriously wrong, fail gracefully
+ */
+ if (!nh)
+ return;
+
+ list_delete_node(nhgc->nhg_list, node);
+ nhgl_delete(nh);
+}
+
+/*
+ * Parse the config strings we support for a single nexthop. This gets used
+ * in a couple of different ways, and we distinguish between transient
+ * failures - such as a still-unprocessed interface - and fatal errors
+ * from label-string parsing.
+ */
+static bool nexthop_group_parse_nexthop(struct nexthop *nhop,
+ const union sockunion *addr,
+ const char *intf, bool onlink,
+ const char *name, const char *labels,
+ vni_t vni, int *lbl_ret,
+ uint32_t weight, const char *backup_str)
+{
+ int ret = 0;
+ struct vrf *vrf;
+ int num;
+ uint8_t labelnum = 0;
+
+ memset(nhop, 0, sizeof(*nhop));
+
+ if (name)
+ vrf = vrf_lookup_by_name(name);
+ else
+ vrf = vrf_lookup_by_id(VRF_DEFAULT);
+
+ if (!vrf)
+ return false;
+
+ nhop->vrf_id = vrf->vrf_id;
+
+ if (intf) {
+ nhop->ifindex = ifname2ifindex(intf, vrf->vrf_id);
+ if (nhop->ifindex == IFINDEX_INTERNAL)
+ return false;
+ }
+
+ if (onlink)
+ SET_FLAG(nhop->flags, NEXTHOP_FLAG_ONLINK);
+
+ if (addr) {
+ if (addr->sa.sa_family == AF_INET) {
+ nhop->gate.ipv4.s_addr = addr->sin.sin_addr.s_addr;
+ if (intf)
+ nhop->type = NEXTHOP_TYPE_IPV4_IFINDEX;
+ else
+ nhop->type = NEXTHOP_TYPE_IPV4;
+ } else {
+ nhop->gate.ipv6 = addr->sin6.sin6_addr;
+ if (intf)
+ nhop->type = NEXTHOP_TYPE_IPV6_IFINDEX;
+ else
+ nhop->type = NEXTHOP_TYPE_IPV6;
+ }
+ } else
+ nhop->type = NEXTHOP_TYPE_IFINDEX;
+
+ if (labels) {
+ mpls_label_t larray[MPLS_MAX_LABELS];
+
+ ret = mpls_str2label(labels, &labelnum, larray);
+
+ /* Return label parse result */
+ if (lbl_ret)
+ *lbl_ret = ret;
+
+ if (ret < 0)
+ return false;
+ else if (labelnum > 0)
+ nexthop_add_labels(nhop, ZEBRA_LSP_NONE, labelnum,
+ larray);
+ } else if (vni) {
+ mpls_label_t label = MPLS_INVALID_LABEL;
+
+ vni2label(vni, &label);
+ nexthop_add_labels(nhop, ZEBRA_LSP_EVPN, 1, &label);
+ }
+
+ nhop->weight = weight;
+
+ if (backup_str) {
+ /* Parse backup indexes */
+ ret = nexthop_str2backups(backup_str,
+ &num, nhop->backup_idx);
+ if (ret == 0) {
+ SET_FLAG(nhop->flags, NEXTHOP_FLAG_HAS_BACKUP);
+ nhop->backup_num = num;
+ } else
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Wrapper to parse the strings in a 'nexthop_hold'
+ */
+static bool nexthop_group_parse_nhh(struct nexthop *nhop,
+ const struct nexthop_hold *nhh)
+{
+ return (nexthop_group_parse_nexthop(
+ nhop, nhh->addr, nhh->intf, nhh->onlink, nhh->nhvrf_name,
+ nhh->labels, nhh->vni, NULL, nhh->weight, nhh->backup_str));
+}
+
+DEFPY(ecmp_nexthops, ecmp_nexthops_cmd,
+ "[no] nexthop\
+ <\
+ <A.B.C.D|X:X::X:X>$addr [INTERFACE$intf [onlink$onlink]]\
+ |INTERFACE$intf\
+ >\
+ [{ \
+ nexthop-vrf NAME$vrf_name \
+ |label WORD \
+ |vni (1-16777215) \
+ |weight (1-255) \
+ |backup-idx WORD \
+ }]",
+ NO_STR
+ "Specify one of the nexthops in this ECMP group\n"
+ "v4 Address\n"
+ "v6 Address\n"
+ "Interface to use\n"
+ "Treat nexthop as directly attached to the interface\n"
+ "Interface to use\n"
+ "If the nexthop is in a different vrf tell us\n"
+ "The nexthop-vrf Name\n"
+ "Specify label(s) for this nexthop\n"
+ "One or more labels in the range (16-1048575) separated by '/'\n"
+ "Specify VNI(s) for this nexthop\n"
+ "VNI in the range (1-16777215)\n"
+ "Weight to be used by the nexthop for purposes of ECMP\n"
+ "Weight value to be used\n"
+ "Specify backup nexthop indexes in another group\n"
+ "One or more indexes in the range (0-254) separated by ','\n")
+{
+ VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc);
+ struct nexthop nhop;
+ struct nexthop *nh;
+ int lbl_ret = 0;
+ bool legal;
+ int num;
+ uint8_t backups[NEXTHOP_MAX_BACKUPS];
+ bool yes = !no;
+
+ /* Pre-parse backup string to validate */
+ if (backup_idx) {
+ lbl_ret = nexthop_str2backups(backup_idx, &num, backups);
+ if (lbl_ret < 0) {
+ vty_out(vty, "%% Invalid backups\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ legal = nexthop_group_parse_nexthop(&nhop, addr, intf, !!onlink,
+ vrf_name, label, vni, &lbl_ret,
+ weight, backup_idx);
+
+ if (nhop.type == NEXTHOP_TYPE_IPV6
+ && IN6_IS_ADDR_LINKLOCAL(&nhop.gate.ipv6)) {
+ vty_out(vty,
+ "Specified a v6 LL with no interface, rejecting\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ /* Handle label-string errors */
+ if (!legal && lbl_ret < 0) {
+ switch (lbl_ret) {
+ case -1:
+ vty_out(vty, "%% Malformed label(s)\n");
+ break;
+ case -2:
+ vty_out(vty,
+ "%% Cannot use reserved label(s) (%d-%d)\n",
+ MPLS_LABEL_RESERVED_MIN,
+ MPLS_LABEL_RESERVED_MAX);
+ break;
+ case -3:
+ vty_out(vty,
+ "%% Too many labels. Enter %d or fewer\n",
+ MPLS_MAX_LABELS);
+ break;
+ }
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ /* Look for an existing nexthop in the config. Note that the test
+ * here tests only some attributes - it's not a complete comparison.
+ * Note that we've got two kinds of objects to manage: 'nexthop_hold'
+ * that represent config that may or may not be valid (yet), and
+ * actual nexthops that have been validated and parsed.
+ */
+ nh = nhg_nh_find(&nhgc->nhg, &nhop);
+
+ /* Always attempt to remove old config info. */
+ nexthop_group_unsave_nhop(nhgc, vrf_name, addr, intf);
+
+ /* Remove any existing nexthop, for delete and replace cases. */
+ if (nh) {
+ nexthop_unlink(&nhgc->nhg, nh);
+
+ if (nhg_hooks.del_nexthop)
+ nhg_hooks.del_nexthop(nhgc, nh);
+
+ nexthop_free(nh);
+ }
+ if (yes) {
+ /* Add/replace case: capture nexthop if valid, and capture
+ * config info always.
+ */
+ if (legal) {
+ nh = nexthop_new();
+
+ memcpy(nh, &nhop, sizeof(nhop));
+ _nexthop_add(&nhgc->nhg.nexthop, nh);
+ }
+
+ /* Save config always */
+ nexthop_group_save_nhop(nhgc, vrf_name, addr, intf, !!onlink,
+ label, weight, backup_idx);
+
+ if (legal && nhg_hooks.add_nexthop)
+ nhg_hooks.add_nexthop(nhgc, nh);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int nexthop_group_write(struct vty *vty);
+static struct cmd_node nexthop_group_node = {
+ .name = "nexthop-group",
+ .node = NH_GROUP_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-nh-group)# ",
+ .config_write = nexthop_group_write,
+};
+
+void nexthop_group_write_nexthop_simple(struct vty *vty,
+ const struct nexthop *nh,
+ char *altifname)
+{
+ char *ifname;
+
+ vty_out(vty, "nexthop ");
+
+ if (altifname)
+ ifname = altifname;
+ else
+ ifname = (char *)ifindex2ifname(nh->ifindex, nh->vrf_id);
+
+ switch (nh->type) {
+ case NEXTHOP_TYPE_IFINDEX:
+ vty_out(vty, "%s", ifname);
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ vty_out(vty, "%pI4", &nh->gate.ipv4);
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ vty_out(vty, "%pI4 %s", &nh->gate.ipv4, ifname);
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ vty_out(vty, "%pI6", &nh->gate.ipv6);
+ break;
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ vty_out(vty, "%pI6 %s", &nh->gate.ipv6, ifname);
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ vty_out(vty, "%s", "drop");
+ break;
+ }
+}
+
+void nexthop_group_write_nexthop(struct vty *vty, const struct nexthop *nh)
+{
+ struct vrf *vrf;
+ int i;
+
+ nexthop_group_write_nexthop_simple(vty, nh, NULL);
+
+ if (nh->vrf_id != VRF_DEFAULT) {
+ vrf = vrf_lookup_by_id(nh->vrf_id);
+ vty_out(vty, " nexthop-vrf %s", VRF_LOGNAME(vrf));
+ }
+
+ if (nh->nh_label && nh->nh_label->num_labels > 0) {
+ char buf[200];
+
+ mpls_label2str(nh->nh_label->num_labels, nh->nh_label->label,
+ buf, sizeof(buf), nh->nh_label_type, 0);
+ vty_out(vty, " label %s", buf);
+ }
+
+ if (nh->weight)
+ vty_out(vty, " weight %u", nh->weight);
+
+ if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) {
+ vty_out(vty, " backup-idx %d", nh->backup_idx[0]);
+
+ for (i = 1; i < nh->backup_num; i++)
+ vty_out(vty, ",%d", nh->backup_idx[i]);
+ }
+
+ vty_out(vty, "\n");
+}
+
+void nexthop_group_json_nexthop(json_object *j, const struct nexthop *nh)
+{
+ struct vrf *vrf;
+ json_object *json_backups = NULL;
+ int i;
+
+ switch (nh->type) {
+ case NEXTHOP_TYPE_IFINDEX:
+ json_object_string_add(j, "nexthop",
+ ifindex2ifname(nh->ifindex, nh->vrf_id));
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ json_object_string_addf(j, "nexthop", "%pI4", &nh->gate.ipv4);
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ json_object_string_addf(j, "nexthop", "%pI4", &nh->gate.ipv4);
+ json_object_string_add(j, "vrfId",
+ ifindex2ifname(nh->ifindex, nh->vrf_id));
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ json_object_string_addf(j, "nexthop", "%pI6", &nh->gate.ipv6);
+ break;
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ json_object_string_addf(j, "nexthop", "%pI6", &nh->gate.ipv6);
+ json_object_string_add(j, "vrfId",
+ ifindex2ifname(nh->ifindex, nh->vrf_id));
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ break;
+ }
+
+ if (nh->vrf_id != VRF_DEFAULT) {
+ vrf = vrf_lookup_by_id(nh->vrf_id);
+ json_object_string_add(j, "targetVrf", vrf->name);
+ }
+
+ if (nh->nh_label && nh->nh_label->num_labels > 0) {
+ char buf[200];
+
+ mpls_label2str(nh->nh_label->num_labels, nh->nh_label->label,
+ buf, sizeof(buf), nh->nh_label_type, 0);
+ json_object_string_add(j, "label", buf);
+ }
+
+ if (nh->weight)
+ json_object_int_add(j, "weight", nh->weight);
+
+ if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) {
+ json_backups = json_object_new_array();
+ for (i = 0; i < nh->backup_num; i++)
+ json_object_array_add(
+ json_backups,
+ json_object_new_int(nh->backup_idx[i]));
+
+ json_object_object_add(j, "backupIdx", json_backups);
+ }
+}
+
+static void nexthop_group_write_nexthop_internal(struct vty *vty,
+ const struct nexthop_hold *nh)
+{
+ vty_out(vty, "nexthop");
+
+ if (nh->addr)
+ vty_out(vty, " %pSU", nh->addr);
+
+ if (nh->intf)
+ vty_out(vty, " %s", nh->intf);
+
+ if (nh->onlink)
+ vty_out(vty, " onlink");
+
+ if (nh->nhvrf_name)
+ vty_out(vty, " nexthop-vrf %s", nh->nhvrf_name);
+
+ if (nh->labels)
+ vty_out(vty, " label %s", nh->labels);
+
+ if (nh->vni)
+ vty_out(vty, " vni %u", nh->vni);
+
+ if (nh->weight)
+ vty_out(vty, " weight %u", nh->weight);
+
+ if (nh->backup_str)
+ vty_out(vty, " backup-idx %s", nh->backup_str);
+
+ vty_out(vty, "\n");
+}
+
+static int nexthop_group_write(struct vty *vty)
+{
+ struct nexthop_group_cmd *nhgc;
+ struct nexthop_hold *nh;
+
+ RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
+ struct listnode *node;
+
+ vty_out(vty, "nexthop-group %s\n", nhgc->name);
+
+ if (nhgc->nhg.nhgr.buckets)
+ vty_out(vty,
+ " resilient buckets %u idle-timer %u unbalanced-timer %u\n",
+ nhgc->nhg.nhgr.buckets,
+ nhgc->nhg.nhgr.idle_timer,
+ nhgc->nhg.nhgr.unbalanced_timer);
+
+ if (nhgc->backup_list_name[0])
+ vty_out(vty, " backup-group %s\n",
+ nhgc->backup_list_name);
+
+ for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) {
+ vty_out(vty, " ");
+ nexthop_group_write_nexthop_internal(vty, nh);
+ }
+
+ vty_out(vty, "exit\n");
+ vty_out(vty, "!\n");
+ }
+
+ return 1;
+}
+
+void nexthop_group_enable_vrf(struct vrf *vrf)
+{
+ struct nexthop_group_cmd *nhgc;
+ struct nexthop_hold *nhh;
+
+ RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) {
+ struct nexthop nhop;
+ struct nexthop *nh;
+
+ if (!nexthop_group_parse_nhh(&nhop, nhh))
+ continue;
+
+ nh = nexthop_exists(&nhgc->nhg, &nhop);
+
+ if (nh)
+ continue;
+
+ if (nhop.vrf_id != vrf->vrf_id)
+ continue;
+
+ nh = nexthop_new();
+
+ memcpy(nh, &nhop, sizeof(nhop));
+ _nexthop_add(&nhgc->nhg.nexthop, nh);
+
+ if (nhg_hooks.add_nexthop)
+ nhg_hooks.add_nexthop(nhgc, nh);
+ }
+ }
+}
+
+void nexthop_group_disable_vrf(struct vrf *vrf)
+{
+ struct nexthop_group_cmd *nhgc;
+ struct nexthop_hold *nhh;
+
+ RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) {
+ struct nexthop nhop;
+ struct nexthop *nh;
+
+ if (!nexthop_group_parse_nhh(&nhop, nhh))
+ continue;
+
+ nh = nexthop_exists(&nhgc->nhg, &nhop);
+
+ if (!nh)
+ continue;
+
+ if (nh->vrf_id != vrf->vrf_id)
+ continue;
+
+ _nexthop_del(&nhgc->nhg, nh);
+
+ if (nhg_hooks.del_nexthop)
+ nhg_hooks.del_nexthop(nhgc, nh);
+
+ nexthop_free(nh);
+ }
+ }
+}
+
+void nexthop_group_interface_state_change(struct interface *ifp,
+ ifindex_t oldifindex)
+{
+ struct nexthop_group_cmd *nhgc;
+ struct nexthop_hold *nhh;
+
+ RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
+ struct listnode *node;
+ struct nexthop *nh;
+
+ if (if_is_up(ifp)) {
+ for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) {
+ struct nexthop nhop;
+
+ if (!nexthop_group_parse_nhh(&nhop, nhh))
+ continue;
+
+ switch (nhop.type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_BLACKHOLE:
+ continue;
+ case NEXTHOP_TYPE_IFINDEX:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ break;
+ }
+ nh = nexthop_exists(&nhgc->nhg, &nhop);
+
+ if (nh)
+ continue;
+
+ if (ifp->ifindex != nhop.ifindex)
+ continue;
+
+ nh = nexthop_new();
+
+ memcpy(nh, &nhop, sizeof(nhop));
+ _nexthop_add(&nhgc->nhg.nexthop, nh);
+
+ if (nhg_hooks.add_nexthop)
+ nhg_hooks.add_nexthop(nhgc, nh);
+ }
+ } else {
+ struct nexthop *next_nh;
+
+ for (nh = nhgc->nhg.nexthop; nh; nh = next_nh) {
+ next_nh = nh->next;
+ switch (nh->type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_BLACKHOLE:
+ continue;
+ case NEXTHOP_TYPE_IFINDEX:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ break;
+ }
+
+ if (oldifindex != nh->ifindex)
+ continue;
+
+ _nexthop_del(&nhgc->nhg, nh);
+
+ if (nhg_hooks.del_nexthop)
+ nhg_hooks.del_nexthop(nhgc, nh);
+
+ nexthop_free(nh);
+ }
+ }
+ }
+}
+
+static void nhg_name_autocomplete(vector comps, struct cmd_token *token)
+{
+ struct nexthop_group_cmd *nhgc;
+
+ RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, nhgc->name));
+ }
+}
+
+static const struct cmd_variable_handler nhg_name_handlers[] = {
+ {.tokenname = "NHGNAME", .completions = nhg_name_autocomplete},
+ {.completions = NULL}};
+
+void nexthop_group_init(void (*new)(const char *name),
+ void (*modify)(const struct nexthop_group_cmd *nhgc),
+ void (*add_nexthop)(const struct nexthop_group_cmd *nhg,
+ const struct nexthop *nhop),
+ void (*del_nexthop)(const struct nexthop_group_cmd *nhg,
+ const struct nexthop *nhop),
+ void (*delete)(const char *name))
+{
+ RB_INIT(nhgc_entry_head, &nhgc_entries);
+
+ cmd_variable_handler_register(nhg_name_handlers);
+
+ install_node(&nexthop_group_node);
+ install_element(CONFIG_NODE, &nexthop_group_cmd);
+ install_element(CONFIG_NODE, &no_nexthop_group_cmd);
+
+ install_default(NH_GROUP_NODE);
+ install_element(NH_GROUP_NODE, &nexthop_group_backup_cmd);
+ install_element(NH_GROUP_NODE, &no_nexthop_group_backup_cmd);
+ install_element(NH_GROUP_NODE, &ecmp_nexthops_cmd);
+
+ install_element(NH_GROUP_NODE, &nexthop_group_resilience_cmd);
+ install_element(NH_GROUP_NODE, &no_nexthop_group_resilience_cmd);
+
+ memset(&nhg_hooks, 0, sizeof(nhg_hooks));
+
+ if (new)
+ nhg_hooks.new = new;
+ if (modify)
+ nhg_hooks.modify = modify;
+ if (add_nexthop)
+ nhg_hooks.add_nexthop = add_nexthop;
+ if (del_nexthop)
+ nhg_hooks.del_nexthop = del_nexthop;
+ if (delete)
+ nhg_hooks.delete = delete;
+}