summaryrefslogtreecommitdiffstats
path: root/isisd
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
commite2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch)
treef0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /isisd
parentInitial commit. (diff)
downloadfrr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz
frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'isisd')
-rw-r--r--isisd/.gitignore3
-rw-r--r--isisd/AUTHORS5
-rw-r--r--isisd/Makefile10
-rw-r--r--isisd/README3
-rw-r--r--isisd/fabricd.c784
-rw-r--r--isisd/fabricd.h45
-rw-r--r--isisd/isis_adjacency.c943
-rw-r--r--isisd/isis_adjacency.h143
-rw-r--r--isisd/isis_affinitymap.c97
-rw-r--r--isisd/isis_affinitymap.h25
-rw-r--r--isisd/isis_bfd.c219
-rw-r--r--isisd/isis_bfd.h16
-rw-r--r--isisd/isis_bpf.c301
-rw-r--r--isisd/isis_circuit.c1685
-rw-r--r--isisd/isis_circuit.h247
-rw-r--r--isisd/isis_cli.c4116
-rw-r--r--isisd/isis_common.h36
-rw-r--r--isisd/isis_constants.h168
-rw-r--r--isisd/isis_csm.c198
-rw-r--r--isisd/isis_csm.h38
-rw-r--r--isisd/isis_dlpi.c593
-rw-r--r--isisd/isis_dr.c309
-rw-r--r--isisd/isis_dr.h27
-rw-r--r--isisd/isis_dynhn.c189
-rw-r--r--isisd/isis_dynhn.h33
-rw-r--r--isisd/isis_errors.c48
-rw-r--r--isisd/isis_errors.h22
-rw-r--r--isisd/isis_events.c224
-rw-r--r--isisd/isis_events.h32
-rw-r--r--isisd/isis_flags.c64
-rw-r--r--isisd/isis_flags.h57
-rw-r--r--isisd/isis_flex_algo.c328
-rw-r--r--isisd/isis_flex_algo.h55
-rw-r--r--isisd/isis_ldp_sync.c655
-rw-r--r--isisd/isis_ldp_sync.h41
-rw-r--r--isisd/isis_lfa.c2366
-rw-r--r--isisd/isis_lfa.h172
-rw-r--r--isisd/isis_lsp.c2522
-rw-r--r--isisd/isis_lsp.h153
-rw-r--r--isisd/isis_main.c318
-rw-r--r--isisd/isis_misc.c528
-rw-r--r--isisd/isis_misc.h71
-rw-r--r--isisd/isis_mt.c557
-rw-r--r--isisd/isis_mt.h113
-rw-r--r--isisd/isis_nb.c1468
-rw-r--r--isisd/isis_nb.h841
-rw-r--r--isisd/isis_nb_config.c4947
-rw-r--r--isisd/isis_nb_notifications.c586
-rw-r--r--isisd/isis_nb_state.c630
-rw-r--r--isisd/isis_network.h24
-rw-r--r--isisd/isis_pdu.c2585
-rw-r--r--isisd/isis_pdu.h211
-rw-r--r--isisd/isis_pdu_counter.c113
-rw-r--r--isisd/isis_pdu_counter.h30
-rw-r--r--isisd/isis_pfpacket.c424
-rw-r--r--isisd/isis_redist.c847
-rw-r--r--isisd/isis_redist.h72
-rw-r--r--isisd/isis_route.c975
-rw-r--r--isisd/isis_route.h90
-rw-r--r--isisd/isis_routemap.c265
-rw-r--r--isisd/isis_routemap.h12
-rw-r--r--isisd/isis_snmp.c3459
-rw-r--r--isisd/isis_spf.c3363
-rw-r--r--isisd/isis_spf.h77
-rw-r--r--isisd/isis_spf_private.h412
-rw-r--r--isisd/isis_sr.c1322
-rw-r--r--isisd/isis_sr.h235
-rw-r--r--isisd/isis_srv6.c862
-rw-r--r--isisd/isis_srv6.h181
-rw-r--r--isisd/isis_te.c2147
-rw-r--r--isisd/isis_te.h120
-rw-r--r--isisd/isis_tlvs.c8268
-rw-r--r--isisd/isis_tlvs.h903
-rw-r--r--isisd/isis_tx_queue.c189
-rw-r--r--isisd/isis_tx_queue.h44
-rw-r--r--isisd/isis_vty_fabricd.c1141
-rw-r--r--isisd/isis_zebra.c1422
-rw-r--r--isisd/isis_zebra.h71
-rw-r--r--isisd/isisd.c3968
-rw-r--r--isisd/isisd.h426
-rw-r--r--isisd/iso_checksum.c60
-rw-r--r--isisd/iso_checksum.h15
-rw-r--r--isisd/subdir.am140
83 files changed, 61504 insertions, 0 deletions
diff --git a/isisd/.gitignore b/isisd/.gitignore
new file mode 100644
index 0000000..b6184ca
--- /dev/null
+++ b/isisd/.gitignore
@@ -0,0 +1,3 @@
+isisd
+fabricd
+isisd.conf
diff --git a/isisd/AUTHORS b/isisd/AUTHORS
new file mode 100644
index 0000000..80b3a28
--- /dev/null
+++ b/isisd/AUTHORS
@@ -0,0 +1,5 @@
+Sampo Saaristo <sambo@cs.tut.fi>
+Ofer Wald <ofersf@islands.co.il>
+Hannes Gredler <hannes@gredler.at>
+Subbaiah Venkata <svenkata@google.com>
+Olivier Dugeon <olivier.dugeon@orange.com>
diff --git a/isisd/Makefile b/isisd/Makefile
new file mode 100644
index 0000000..db3b70e
--- /dev/null
+++ b/isisd/Makefile
@@ -0,0 +1,10 @@
+all: ALWAYS
+ @$(MAKE) -s -C .. isisd/isisd
+%: ALWAYS
+ @$(MAKE) -s -C .. isisd/$@
+
+Makefile:
+ #nothing
+ALWAYS:
+.PHONY: ALWAYS makefiles
+.SUFFIXES:
diff --git a/isisd/README b/isisd/README
new file mode 100644
index 0000000..4f13ff6
--- /dev/null
+++ b/isisd/README
@@ -0,0 +1,3 @@
+Constraints
+
+ o Maximum number of interfaces 255
diff --git a/isisd/fabricd.c b/isisd/fabricd.c
new file mode 100644
index 0000000..b229aa6
--- /dev/null
+++ b/isisd/fabricd.c
@@ -0,0 +1,784 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - OpenFabric extensions
+ *
+ * Copyright (C) 2018 Christian Franke
+ *
+ * This file is part of FRRouting (FRR)
+ */
+#include <zebra.h>
+#include "isisd/fabricd.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_spf.h"
+#include "isisd/isis_tlvs.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_spf_private.h"
+#include "isisd/isis_tx_queue.h"
+#include "isisd/isis_csm.h"
+
+DEFINE_MTYPE_STATIC(ISISD, FABRICD_STATE, "ISIS OpenFabric");
+DEFINE_MTYPE_STATIC(ISISD, FABRICD_NEIGHBOR, "ISIS OpenFabric Neighbor Entry");
+DEFINE_MTYPE_STATIC(ISISD, FABRICD_FLOODING_INFO, "ISIS OpenFabric Flooding Log");
+
+/* Tracks initial synchronization as per section 2.4
+ *
+ * We declare the sync complete once we have seen at least one
+ * CSNP and there are no more LSPs with SSN or SRM set.
+ */
+enum fabricd_sync_state {
+ FABRICD_SYNC_PENDING,
+ FABRICD_SYNC_STARTED,
+ FABRICD_SYNC_COMPLETE
+};
+
+struct fabricd {
+ struct isis_area *area;
+
+ enum fabricd_sync_state initial_sync_state;
+ time_t initial_sync_start;
+ struct isis_circuit *initial_sync_circuit;
+ struct event *initial_sync_timeout;
+
+ struct isis_spftree *spftree;
+ struct skiplist *neighbors;
+ struct hash *neighbors_neighbors;
+
+ uint8_t tier;
+ uint8_t tier_config;
+ uint8_t tier_pending;
+ struct event *tier_calculation_timer;
+ struct event *tier_set_timer;
+
+ int csnp_delay;
+ bool always_send_csnp;
+};
+
+/* Code related to maintaining the neighbor lists */
+
+struct neighbor_entry {
+ uint8_t id[ISIS_SYS_ID_LEN];
+ struct isis_adjacency *adj;
+ bool present;
+};
+
+static struct neighbor_entry *neighbor_entry_new(const uint8_t *id,
+ struct isis_adjacency *adj)
+{
+ struct neighbor_entry *rv = XMALLOC(MTYPE_FABRICD_NEIGHBOR,
+ sizeof(*rv));
+
+ memcpy(rv->id, id, sizeof(rv->id));
+ rv->adj = adj;
+
+ return rv;
+}
+
+static void neighbor_entry_del(struct neighbor_entry *neighbor)
+{
+ XFREE(MTYPE_FABRICD_NEIGHBOR, neighbor);
+}
+
+static void neighbor_entry_del_void(void *arg)
+{
+ neighbor_entry_del((struct neighbor_entry *)arg);
+}
+
+static void neighbor_lists_clear(struct fabricd *f)
+{
+ while (!skiplist_empty(f->neighbors))
+ skiplist_delete_first(f->neighbors);
+
+ hash_clean(f->neighbors_neighbors, neighbor_entry_del_void);
+}
+
+static unsigned neighbor_entry_hash_key(const void *np)
+{
+ const struct neighbor_entry *n = np;
+
+ return jhash(n->id, sizeof(n->id), 0x55aa5a5a);
+}
+
+static bool neighbor_entry_hash_cmp(const void *a, const void *b)
+{
+ const struct neighbor_entry *na = a, *nb = b;
+
+ return memcmp(na->id, nb->id, sizeof(na->id)) == 0;
+}
+
+static int neighbor_entry_list_cmp(const void *a, const void *b)
+{
+ const struct neighbor_entry *na = a, *nb = b;
+
+ return -memcmp(na->id, nb->id, sizeof(na->id));
+}
+
+static struct neighbor_entry *neighbor_entry_lookup_list(struct skiplist *list,
+ const uint8_t *id)
+{
+ struct neighbor_entry n = { {0} };
+
+ memcpy(n.id, id, sizeof(n.id));
+
+ struct neighbor_entry *rv;
+
+ if (skiplist_search(list, &n, (void**)&rv))
+ return NULL;
+
+ if (!rv->present)
+ return NULL;
+
+ return rv;
+}
+
+static struct neighbor_entry *neighbor_entry_lookup_hash(struct hash *hash,
+ const uint8_t *id)
+{
+ struct neighbor_entry n = {{0}};
+
+ memcpy(n.id, id, sizeof(n.id));
+
+ struct neighbor_entry *rv = hash_lookup(hash, &n);
+
+ if (!rv || !rv->present)
+ return NULL;
+
+ return rv;
+}
+
+static int fabricd_handle_adj_state_change(struct isis_adjacency *arg)
+{
+ struct fabricd *f = arg->circuit->area->fabricd;
+
+ if (!f)
+ return 0;
+
+ while (!skiplist_empty(f->neighbors))
+ skiplist_delete_first(f->neighbors);
+
+ struct listnode *node;
+ struct isis_circuit *circuit;
+
+ for (ALL_LIST_ELEMENTS_RO(f->area->circuit_list, node, circuit)) {
+ if (circuit->state != C_STATE_UP)
+ continue;
+
+ struct isis_adjacency *adj = circuit->u.p2p.neighbor;
+
+ if (!adj || adj->adj_state != ISIS_ADJ_UP)
+ continue;
+
+ struct neighbor_entry *n = neighbor_entry_new(adj->sysid, adj);
+
+ skiplist_insert(f->neighbors, n, n);
+ }
+
+ return 0;
+}
+
+static void neighbors_neighbors_update(struct fabricd *f)
+{
+ hash_clean(f->neighbors_neighbors, neighbor_entry_del_void);
+
+ struct listnode *node;
+ struct isis_vertex *v;
+
+ for (ALL_QUEUE_ELEMENTS_RO(&f->spftree->paths, node, v)) {
+ if (v->d_N < 2 || !VTYPE_IS(v->type))
+ continue;
+
+ if (v->d_N > 2)
+ break;
+
+ struct neighbor_entry *n = neighbor_entry_new(v->N.id, NULL);
+ struct neighbor_entry *inserted;
+ inserted = hash_get(f->neighbors_neighbors, n,
+ hash_alloc_intern);
+ assert(inserted == n);
+ }
+}
+
+struct fabricd *fabricd_new(struct isis_area *area)
+{
+ struct fabricd *rv = XCALLOC(MTYPE_FABRICD_STATE, sizeof(*rv));
+
+ rv->area = area;
+ rv->initial_sync_state = FABRICD_SYNC_PENDING;
+
+ rv->spftree = isis_spftree_new(
+ area, &area->lspdb[IS_LEVEL_2 - 1], area->isis->sysid,
+ ISIS_LEVEL2, SPFTREE_IPV4, SPF_TYPE_FORWARD,
+ F_SPFTREE_HOPCOUNT_METRIC, SR_ALGORITHM_SPF);
+ rv->neighbors = skiplist_new(0, neighbor_entry_list_cmp,
+ neighbor_entry_del_void);
+ rv->neighbors_neighbors = hash_create(neighbor_entry_hash_key,
+ neighbor_entry_hash_cmp,
+ "Fabricd Neighbors");
+
+ rv->tier = rv->tier_config = ISIS_TIER_UNDEFINED;
+
+ rv->csnp_delay = FABRICD_DEFAULT_CSNP_DELAY;
+ return rv;
+};
+
+void fabricd_finish(struct fabricd *f)
+{
+ EVENT_OFF(f->initial_sync_timeout);
+
+ EVENT_OFF(f->tier_calculation_timer);
+
+ EVENT_OFF(f->tier_set_timer);
+
+ isis_spftree_del(f->spftree);
+ neighbor_lists_clear(f);
+ skiplist_free(f->neighbors);
+ hash_free(f->neighbors_neighbors);
+}
+
+static void fabricd_initial_sync_timeout(struct event *thread)
+{
+ struct fabricd *f = EVENT_ARG(thread);
+
+ if (IS_DEBUG_ADJ_PACKETS)
+ zlog_debug(
+ "OpenFabric: Initial synchronization on %s timed out!",
+ f->initial_sync_circuit->interface->name);
+ f->initial_sync_state = FABRICD_SYNC_PENDING;
+ f->initial_sync_circuit = NULL;
+}
+
+void fabricd_initial_sync_hello(struct isis_circuit *circuit)
+{
+ struct fabricd *f = circuit->area->fabricd;
+
+ if (!f)
+ return;
+
+ if (f->initial_sync_state > FABRICD_SYNC_PENDING)
+ return;
+
+ f->initial_sync_state = FABRICD_SYNC_STARTED;
+
+ long timeout = 2 * circuit->hello_interval[1] * circuit->hello_multiplier[1];
+
+ f->initial_sync_circuit = circuit;
+ if (f->initial_sync_timeout)
+ return;
+
+ event_add_timer(master, fabricd_initial_sync_timeout, f, timeout,
+ &f->initial_sync_timeout);
+ f->initial_sync_start = monotime(NULL);
+
+ if (IS_DEBUG_ADJ_PACKETS)
+ zlog_debug(
+ "OpenFabric: Started initial synchronization with %pSY on %s",
+ circuit->u.p2p.neighbor->sysid,
+ circuit->interface->name);
+}
+
+bool fabricd_initial_sync_is_in_progress(struct isis_area *area)
+{
+ struct fabricd *f = area->fabricd;
+
+ if (!f)
+ return false;
+
+ if (f->initial_sync_state > FABRICD_SYNC_PENDING
+ && f->initial_sync_state < FABRICD_SYNC_COMPLETE)
+ return true;
+
+ return false;
+}
+
+bool fabricd_initial_sync_is_complete(struct isis_area *area)
+{
+ struct fabricd *f = area->fabricd;
+
+ if (!f)
+ return false;
+
+ return f->initial_sync_state == FABRICD_SYNC_COMPLETE;
+}
+
+struct isis_circuit *fabricd_initial_sync_circuit(struct isis_area *area)
+{
+ struct fabricd *f = area->fabricd;
+ if (!f)
+ return NULL;
+
+ return f->initial_sync_circuit;
+}
+
+void fabricd_initial_sync_finish(struct isis_area *area)
+{
+ struct fabricd *f = area->fabricd;
+
+ if (!f)
+ return;
+
+ if (monotime(NULL) - f->initial_sync_start < 5)
+ return;
+
+ zlog_info("OpenFabric: Initial synchronization on %s complete.",
+ f->initial_sync_circuit->interface->name);
+ f->initial_sync_state = FABRICD_SYNC_COMPLETE;
+ f->initial_sync_circuit = NULL;
+ EVENT_OFF(f->initial_sync_timeout);
+}
+
+static void fabricd_bump_tier_calculation_timer(struct fabricd *f);
+static void fabricd_set_tier(struct fabricd *f, uint8_t tier);
+
+static uint8_t fabricd_calculate_fabric_tier(struct isis_area *area)
+{
+ struct isis_spftree *local_tree = fabricd_spftree(area);
+ struct listnode *node;
+
+ struct isis_vertex *furthest_t0 = NULL,
+ *second_furthest_t0 = NULL;
+
+ struct isis_vertex *v;
+
+ for (ALL_QUEUE_ELEMENTS_RO(&local_tree->paths, node, v)) {
+ struct isis_lsp *lsp = lsp_for_vertex(local_tree, v);
+
+ if (!lsp || !lsp->tlvs
+ || !lsp->tlvs->spine_leaf
+ || !lsp->tlvs->spine_leaf->has_tier
+ || lsp->tlvs->spine_leaf->tier != 0)
+ continue;
+
+ second_furthest_t0 = furthest_t0;
+ furthest_t0 = v;
+ }
+
+ if (!second_furthest_t0) {
+ zlog_info("OpenFabric: Could not find two T0 routers");
+ return ISIS_TIER_UNDEFINED;
+ }
+
+ zlog_info(
+ "OpenFabric: Found %pLS as furthest t0 from local system, dist == %u",
+ furthest_t0->N.id, furthest_t0->d_N);
+
+ struct isis_spftree *remote_tree =
+ isis_run_hopcount_spf(area, furthest_t0->N.id, NULL);
+
+ struct isis_vertex *furthest_from_remote =
+ isis_vertex_queue_last(&remote_tree->paths);
+
+ if (!furthest_from_remote) {
+ zlog_info("OpenFabric: Found no furthest node in remote spf");
+ isis_spftree_del(remote_tree);
+ return ISIS_TIER_UNDEFINED;
+ } else {
+ zlog_info(
+ "OpenFabric: Found %pLS as furthest from remote dist == %u",
+ furthest_from_remote->N.id, furthest_from_remote->d_N);
+ }
+
+ int64_t tier = furthest_from_remote->d_N - furthest_t0->d_N;
+ isis_spftree_del(remote_tree);
+
+ if (tier < 0 || tier >= ISIS_TIER_UNDEFINED) {
+ zlog_info("OpenFabric: Calculated tier %" PRId64 " seems implausible",
+ tier);
+ return ISIS_TIER_UNDEFINED;
+ }
+
+ zlog_info("OpenFabric: Calculated %" PRId64 " as tier", tier);
+ return tier;
+}
+
+static void fabricd_tier_set_timer(struct event *thread)
+{
+ struct fabricd *f = EVENT_ARG(thread);
+
+ fabricd_set_tier(f, f->tier_pending);
+}
+
+static void fabricd_tier_calculation_cb(struct event *thread)
+{
+ struct fabricd *f = EVENT_ARG(thread);
+ uint8_t tier = ISIS_TIER_UNDEFINED;
+
+ tier = fabricd_calculate_fabric_tier(f->area);
+ if (tier == ISIS_TIER_UNDEFINED)
+ return;
+
+ zlog_info("OpenFabric: Got tier %hhu from algorithm. Arming timer.",
+ tier);
+ f->tier_pending = tier;
+ event_add_timer(master, fabricd_tier_set_timer, f,
+ f->area->lsp_gen_interval[ISIS_LEVEL2 - 1],
+ &f->tier_set_timer);
+}
+
+static void fabricd_bump_tier_calculation_timer(struct fabricd *f)
+{
+ /* Cancel timer if we already know our tier */
+ if (f->tier != ISIS_TIER_UNDEFINED || f->tier_set_timer) {
+ EVENT_OFF(f->tier_calculation_timer);
+ return;
+ }
+
+ /* If we need to calculate the tier, wait some
+ * time for the topology to settle before running
+ * the calculation */
+ EVENT_OFF(f->tier_calculation_timer);
+
+ event_add_timer(master, fabricd_tier_calculation_cb, f,
+ 2 * f->area->lsp_gen_interval[ISIS_LEVEL2 - 1],
+ &f->tier_calculation_timer);
+}
+
+static void fabricd_set_tier(struct fabricd *f, uint8_t tier)
+{
+ if (f->tier == tier)
+ return;
+
+ zlog_info("OpenFabric: Set own tier to %hhu", tier);
+ f->tier = tier;
+
+ fabricd_bump_tier_calculation_timer(f);
+ lsp_regenerate_schedule(f->area, ISIS_LEVEL2, 0);
+}
+
+void fabricd_run_spf(struct isis_area *area)
+{
+ struct fabricd *f = area->fabricd;
+
+ if (!f)
+ return;
+
+ isis_run_hopcount_spf(area, area->isis->sysid, f->spftree);
+ neighbors_neighbors_update(f);
+ fabricd_bump_tier_calculation_timer(f);
+}
+
+struct isis_spftree *fabricd_spftree(struct isis_area *area)
+{
+ struct fabricd *f = area->fabricd;
+
+ if (!f)
+ return NULL;
+
+ return f->spftree;
+}
+
+void fabricd_configure_tier(struct isis_area *area, uint8_t tier)
+{
+ struct fabricd *f = area->fabricd;
+
+ if (!f || f->tier_config == tier)
+ return;
+
+ f->tier_config = tier;
+ fabricd_set_tier(f, tier);
+}
+
+uint8_t fabricd_tier(struct isis_area *area)
+{
+ struct fabricd *f = area->fabricd;
+
+ if (!f)
+ return ISIS_TIER_UNDEFINED;
+
+ return f->tier;
+}
+
+int fabricd_write_settings(struct isis_area *area, struct vty *vty)
+{
+ struct fabricd *f = area->fabricd;
+ int written = 0;
+
+ if (!f)
+ return written;
+
+ if (f->tier_config != ISIS_TIER_UNDEFINED) {
+ vty_out(vty, " fabric-tier %hhu\n", f->tier_config);
+ written++;
+ }
+
+ if (f->csnp_delay != FABRICD_DEFAULT_CSNP_DELAY
+ || f->always_send_csnp) {
+ vty_out(vty, " triggered-csnp-delay %d%s\n", f->csnp_delay,
+ f->always_send_csnp ? " always" : "");
+ }
+
+ return written;
+}
+
+static void move_to_queue(struct isis_lsp *lsp, struct neighbor_entry *n,
+ enum isis_tx_type type, struct isis_circuit *circuit)
+{
+ n->present = false;
+
+ if (n->adj && n->adj->circuit == circuit)
+ return;
+
+ if (IS_DEBUG_FLOODING) {
+ zlog_debug("OpenFabric: Adding %s to %s",
+ print_sys_hostname(n->id),
+ (type == TX_LSP_NORMAL) ? "RF" : "DNR");
+ }
+
+ if (n->adj)
+ isis_tx_queue_add(n->adj->circuit->tx_queue, lsp, type);
+
+ uint8_t *neighbor_id = XMALLOC(MTYPE_FABRICD_FLOODING_INFO,
+ sizeof(n->id));
+
+ memcpy(neighbor_id, n->id, sizeof(n->id));
+ listnode_add(lsp->flooding_neighbors[type], neighbor_id);
+}
+
+static void mark_neighbor_as_present(struct hash_bucket *bucket, void *arg)
+{
+ struct neighbor_entry *n = bucket->data;
+
+ n->present = true;
+}
+
+static void handle_firsthops(struct hash_bucket *bucket, void *arg)
+{
+ struct isis_lsp *lsp = arg;
+ struct fabricd *f = lsp->area->fabricd;
+ struct isis_vertex *vertex = bucket->data;
+
+ struct neighbor_entry *n;
+
+ n = neighbor_entry_lookup_list(f->neighbors, vertex->N.id);
+ if (n) {
+ if (IS_DEBUG_FLOODING) {
+ zlog_debug("Removing %s from NL as its in the reverse path",
+ print_sys_hostname(n->id));
+ }
+ n->present = false;
+ }
+
+ n = neighbor_entry_lookup_hash(f->neighbors_neighbors, vertex->N.id);
+ if (n) {
+ if (IS_DEBUG_FLOODING) {
+ zlog_debug("Removing %s from NN as its in the reverse path",
+ print_sys_hostname(n->id));
+ }
+ n->present = false;
+ }
+}
+
+static struct isis_lsp *lsp_for_neighbor(struct fabricd *f,
+ struct neighbor_entry *n)
+{
+ uint8_t id[ISIS_SYS_ID_LEN + 1] = {0};
+
+ memcpy(id, n->id, sizeof(n->id));
+
+ struct isis_vertex vertex = {0};
+
+ isis_vertex_id_init(&vertex, id, VTYPE_NONPSEUDO_TE_IS);
+
+ return lsp_for_vertex(f->spftree, &vertex);
+}
+
+static void fabricd_free_lsp_flooding_info(void *val)
+{
+ XFREE(MTYPE_FABRICD_FLOODING_INFO, val);
+}
+
+static void fabricd_lsp_reset_flooding_info(struct isis_lsp *lsp,
+ struct isis_circuit *circuit)
+{
+ lsp->flooding_time = time(NULL);
+
+ XFREE(MTYPE_FABRICD_FLOODING_INFO, lsp->flooding_interface);
+ for (enum isis_tx_type type = TX_LSP_NORMAL;
+ type <= TX_LSP_CIRCUIT_SCOPED; type++) {
+ if (lsp->flooding_neighbors[type]) {
+ list_delete_all_node(lsp->flooding_neighbors[type]);
+ continue;
+ }
+
+ lsp->flooding_neighbors[type] = list_new();
+ lsp->flooding_neighbors[type]->del =
+ fabricd_free_lsp_flooding_info;
+ }
+
+ if (circuit) {
+ lsp->flooding_interface = XSTRDUP(MTYPE_FABRICD_FLOODING_INFO,
+ circuit->interface->name);
+ }
+
+ lsp->flooding_circuit_scoped = false;
+}
+
+void fabricd_lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit)
+{
+ struct fabricd *f = lsp->area->fabricd;
+ assert(f);
+
+ fabricd_lsp_reset_flooding_info(lsp, circuit);
+
+ void *cursor = NULL;
+ struct neighbor_entry *n;
+
+ /* Mark all elements in NL as present */
+ while (!skiplist_next(f->neighbors, NULL, (void **)&n, &cursor))
+ n->present = true;
+
+ /* Mark all elements in NN as present */
+ hash_iterate(f->neighbors_neighbors, mark_neighbor_as_present, NULL);
+
+ struct isis_vertex *originator =
+ isis_find_vertex(&f->spftree->paths,
+ lsp->hdr.lsp_id,
+ VTYPE_NONPSEUDO_TE_IS);
+
+ /* Remove all IS from NL and NN in the shortest path
+ * to the IS that originated the LSP */
+ if (originator)
+ hash_iterate(originator->firsthops, handle_firsthops, lsp);
+
+ /* Iterate over all remaining IS in NL */
+ cursor = NULL;
+ while (!skiplist_next(f->neighbors, NULL, (void **)&n, &cursor)) {
+ if (!n->present)
+ continue;
+
+ struct isis_lsp *nlsp = lsp_for_neighbor(f, n);
+ if (!nlsp || !nlsp->tlvs) {
+ if (IS_DEBUG_FLOODING) {
+ zlog_debug("Moving %s to DNR as it has no LSP",
+ print_sys_hostname(n->id));
+ }
+
+ move_to_queue(lsp, n, TX_LSP_CIRCUIT_SCOPED, circuit);
+ continue;
+ }
+
+ if (IS_DEBUG_FLOODING) {
+ zlog_debug("Considering %s from NL...",
+ print_sys_hostname(n->id));
+ }
+
+ /* For all neighbors of the NL IS check whether they are present
+ * in NN. If yes, remove from NN and set need_reflood. */
+ bool need_reflood = false;
+ struct isis_extended_reach *er;
+ for (er = (struct isis_extended_reach *)nlsp->tlvs->extended_reach.head;
+ er; er = er->next) {
+ struct neighbor_entry *nn;
+
+ nn = neighbor_entry_lookup_hash(f->neighbors_neighbors,
+ er->id);
+
+ if (nn) {
+ if (IS_DEBUG_FLOODING) {
+ zlog_debug("Found neighbor %s in NN, removing it from NN and setting reflood.",
+ print_sys_hostname(nn->id));
+ }
+
+ nn->present = false;
+ need_reflood = true;
+ }
+ }
+
+ move_to_queue(lsp, n, need_reflood ?
+ TX_LSP_NORMAL : TX_LSP_CIRCUIT_SCOPED,
+ circuit);
+ }
+
+ if (IS_DEBUG_FLOODING) {
+ zlog_debug("OpenFabric: Flooding algorithm complete.");
+ }
+}
+
+void fabricd_trigger_csnp(struct isis_area *area, bool circuit_scoped)
+{
+ struct fabricd *f = area->fabricd;
+
+ if (!f)
+ return;
+
+ if (!circuit_scoped && !f->always_send_csnp)
+ return;
+
+ struct listnode *node;
+ struct isis_circuit *circuit;
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
+ if (!circuit->t_send_csnp[1])
+ continue;
+
+ EVENT_OFF(circuit->t_send_csnp[ISIS_LEVEL2 - 1]);
+ event_add_timer_msec(master, send_l2_csnp, circuit,
+ isis_jitter(f->csnp_delay, CSNP_JITTER),
+ &circuit->t_send_csnp[ISIS_LEVEL2 - 1]);
+ }
+}
+
+struct list *fabricd_ip_addrs(struct isis_circuit *circuit)
+{
+ if (listcount(circuit->ip_addrs))
+ return circuit->ip_addrs;
+
+ if (!fabricd || !circuit->area || !circuit->area->circuit_list)
+ return NULL;
+
+ struct listnode *node;
+ struct isis_circuit *c;
+
+ for (ALL_LIST_ELEMENTS_RO(circuit->area->circuit_list, node, c)) {
+ if (c->circ_type != CIRCUIT_T_LOOPBACK)
+ continue;
+
+ if (!listcount(c->ip_addrs))
+ return NULL;
+
+ return c->ip_addrs;
+ }
+
+ return NULL;
+}
+
+void fabricd_lsp_free(struct isis_lsp *lsp)
+{
+ XFREE(MTYPE_FABRICD_FLOODING_INFO, lsp->flooding_interface);
+ for (enum isis_tx_type type = TX_LSP_NORMAL;
+ type <= TX_LSP_CIRCUIT_SCOPED; type++) {
+ if (!lsp->flooding_neighbors[type])
+ continue;
+
+ list_delete(&lsp->flooding_neighbors[type]);
+ }
+}
+
+void fabricd_update_lsp_no_flood(struct isis_lsp *lsp,
+ struct isis_circuit *circuit)
+{
+ if (!fabricd)
+ return;
+
+ fabricd_lsp_reset_flooding_info(lsp, circuit);
+ lsp->flooding_circuit_scoped = true;
+}
+
+void fabricd_configure_triggered_csnp(struct isis_area *area, int delay,
+ bool always_send_csnp)
+{
+ struct fabricd *f = area->fabricd;
+
+ if (!f)
+ return;
+
+ f->csnp_delay = delay;
+ f->always_send_csnp = always_send_csnp;
+}
+
+void fabricd_init(void)
+{
+ hook_register(isis_adj_state_change_hook,
+ fabricd_handle_adj_state_change);
+}
diff --git a/isisd/fabricd.h b/isisd/fabricd.h
new file mode 100644
index 0000000..c1df0ad
--- /dev/null
+++ b/isisd/fabricd.h
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - OpenFabric extensions
+ *
+ * Copyright (C) 2018 Christian Franke
+ *
+ * This file is part of FRRouting (FRR)
+ */
+#ifndef FABRICD_H
+#define FABRICD_H
+
+#define FABRICD_DEFAULT_CSNP_DELAY 500
+
+struct fabricd;
+
+struct isis_circuit;
+struct isis_area;
+struct isis_spftree;
+struct isis_lsp;
+struct vty;
+
+struct fabricd *fabricd_new(struct isis_area *area);
+void fabricd_finish(struct fabricd *f);
+void fabricd_initial_sync_hello(struct isis_circuit *circuit);
+bool fabricd_initial_sync_is_complete(struct isis_area *area);
+bool fabricd_initial_sync_is_in_progress(struct isis_area *area);
+struct isis_circuit *fabricd_initial_sync_circuit(struct isis_area *area);
+void fabricd_initial_sync_finish(struct isis_area *area);
+void fabricd_run_spf(struct isis_area *area);
+struct isis_spftree *fabricd_spftree(struct isis_area *area);
+void fabricd_configure_tier(struct isis_area *area, uint8_t tier);
+uint8_t fabricd_tier(struct isis_area *area);
+int fabricd_write_settings(struct isis_area *area, struct vty *vty);
+void fabricd_lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit);
+void fabricd_trigger_csnp(struct isis_area *area, bool circuit_scoped);
+struct list *fabricd_ip_addrs(struct isis_circuit *circuit);
+void fabricd_lsp_free(struct isis_lsp *lsp);
+void fabricd_update_lsp_no_flood(struct isis_lsp *lsp,
+ struct isis_circuit *circuit);
+void fabricd_configure_triggered_csnp(struct isis_area *area, int delay,
+ bool always_send_csnp);
+void fabricd_init(void);
+void isis_vty_daemon_init(void);
+
+#endif
diff --git a/isisd/isis_adjacency.c b/isisd/isis_adjacency.c
new file mode 100644
index 0000000..cba1b91
--- /dev/null
+++ b/isisd/isis_adjacency.c
@@ -0,0 +1,943 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_adjacency.c
+ * handling of IS-IS adjacencies
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+
+#include "log.h"
+#include "memory.h"
+#include "hash.h"
+#include "vty.h"
+#include "linklist.h"
+#include "frrevent.h"
+#include "if.h"
+#include "stream.h"
+#include "bfd.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_dr.h"
+#include "isisd/isis_dynhn.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_events.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_tlvs.h"
+#include "isisd/fabricd.h"
+#include "isisd/isis_nb.h"
+
+DEFINE_MTYPE_STATIC(ISISD, ISIS_ADJACENCY, "ISIS adjacency");
+DEFINE_MTYPE(ISISD, ISIS_ADJACENCY_INFO, "ISIS adjacency info");
+
+static struct isis_adjacency *adj_alloc(struct isis_circuit *circuit,
+ const uint8_t *id)
+{
+ struct isis_adjacency *adj;
+
+ adj = XCALLOC(MTYPE_ISIS_ADJACENCY, sizeof(struct isis_adjacency));
+ memcpy(adj->sysid, id, ISIS_SYS_ID_LEN);
+
+ adj->snmp_idx = ++circuit->snmp_adj_idx_gen;
+
+ if (circuit->snmp_adj_list == NULL)
+ circuit->snmp_adj_list = list_new();
+
+ adj->snmp_list_node = listnode_add(circuit->snmp_adj_list, adj);
+
+ return adj;
+}
+
+struct isis_adjacency *isis_new_adj(const uint8_t *id, const uint8_t *snpa,
+ int level, struct isis_circuit *circuit)
+{
+ struct isis_adjacency *adj;
+ int i;
+
+ adj = adj_alloc(circuit, id); /* P2P kludge */
+
+ if (snpa) {
+ memcpy(adj->snpa, snpa, ETH_ALEN);
+ } else {
+ memset(adj->snpa, ' ', ETH_ALEN);
+ }
+
+ adj->circuit = circuit;
+ adj->level = level;
+ adj->flaps = 0;
+ adj->last_flap = time(NULL);
+ adj->threeway_state = ISIS_THREEWAY_DOWN;
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ listnode_add(circuit->u.bc.adjdb[level - 1], adj);
+ adj->dischanges[level - 1] = 0;
+ for (i = 0; i < DIS_RECORDS;
+ i++) /* clear N DIS state change records */
+ {
+ adj->dis_record[(i * ISIS_LEVELS) + level - 1].dis =
+ ISIS_UNKNOWN_DIS;
+ adj->dis_record[(i * ISIS_LEVELS) + level - 1]
+ .last_dis_change = time(NULL);
+ }
+ }
+ adj->adj_sids = list_new();
+ adj->srv6_endx_sids = list_new();
+ listnode_add(circuit->area->adjacency_list, adj);
+
+ return adj;
+}
+
+struct isis_adjacency *isis_adj_lookup(const uint8_t *sysid, struct list *adjdb)
+{
+ struct isis_adjacency *adj;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj))
+ if (memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN) == 0)
+ return adj;
+
+ return NULL;
+}
+
+struct isis_adjacency *isis_adj_lookup_snpa(const uint8_t *ssnpa,
+ struct list *adjdb)
+{
+ struct listnode *node;
+ struct isis_adjacency *adj;
+
+ for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj))
+ if (memcmp(adj->snpa, ssnpa, ETH_ALEN) == 0)
+ return adj;
+
+ return NULL;
+}
+
+struct isis_adjacency *isis_adj_find(const struct isis_area *area, int level,
+ const uint8_t *sysid)
+{
+ struct isis_adjacency *adj;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(area->adjacency_list, node, adj)) {
+ if (!(adj->level & level))
+ continue;
+
+ if (!memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN))
+ return adj;
+ }
+
+ return NULL;
+}
+
+DEFINE_HOOK(isis_adj_state_change_hook, (struct isis_adjacency *adj), (adj));
+
+void isis_delete_adj(void *arg)
+{
+ struct isis_adjacency *adj = arg;
+
+ if (!adj)
+ return;
+ /* Remove self from snmp list without walking the list*/
+ list_delete_node(adj->circuit->snmp_adj_list, adj->snmp_list_node);
+
+ EVENT_OFF(adj->t_expire);
+ if (adj->adj_state != ISIS_ADJ_DOWN)
+ adj->adj_state = ISIS_ADJ_DOWN;
+
+ hook_call(isis_adj_state_change_hook, adj);
+
+ XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->area_addresses);
+ XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->ipv4_addresses);
+ XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->ll_ipv6_addrs);
+ XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->global_ipv6_addrs);
+ adj_mt_finish(adj);
+ list_delete(&adj->adj_sids);
+ list_delete(&adj->srv6_endx_sids);
+
+ listnode_delete(adj->circuit->area->adjacency_list, adj);
+ XFREE(MTYPE_ISIS_ADJACENCY, adj);
+ return;
+}
+
+static const char *adj_state2string(int state)
+{
+
+ switch (state) {
+ case ISIS_ADJ_INITIALIZING:
+ return "Initializing";
+ case ISIS_ADJ_UP:
+ return "Up";
+ case ISIS_ADJ_DOWN:
+ return "Down";
+ default:
+ return "Unknown";
+ }
+
+ return NULL; /* not reached */
+}
+
+static const char *adj_level2string(int level)
+{
+ switch (level) {
+ case IS_LEVEL_1:
+ return "level-1";
+ case IS_LEVEL_2:
+ return "level-2";
+ case IS_LEVEL_1_AND_2:
+ return "level-1-2";
+ default:
+ return "unknown";
+ }
+
+ return NULL; /* not reached */
+}
+
+static void isis_adj_route_switchover(struct isis_adjacency *adj)
+{
+ union g_addr ip = {};
+ ifindex_t ifindex;
+ unsigned int i;
+
+ if (!adj->circuit || !adj->circuit->interface)
+ return;
+
+ ifindex = adj->circuit->interface->ifindex;
+
+ for (i = 0; i < adj->ipv4_address_count; i++) {
+ ip.ipv4 = adj->ipv4_addresses[i];
+ isis_circuit_switchover_routes(adj->circuit, AF_INET, &ip,
+ ifindex);
+ }
+
+ for (i = 0; i < adj->ll_ipv6_count; i++) {
+ ip.ipv6 = adj->ll_ipv6_addrs[i];
+ isis_circuit_switchover_routes(adj->circuit, AF_INET6, &ip,
+ ifindex);
+ }
+
+ for (i = 0; i < adj->global_ipv6_count; i++) {
+ ip.ipv6 = adj->global_ipv6_addrs[i];
+ isis_circuit_switchover_routes(adj->circuit, AF_INET6, &ip,
+ ifindex);
+ }
+}
+
+void isis_adj_process_threeway(struct isis_adjacency *adj,
+ struct isis_threeway_adj *tw_adj,
+ enum isis_adj_usage adj_usage)
+{
+ enum isis_threeway_state next_tw_state = ISIS_THREEWAY_DOWN;
+
+ if (tw_adj && !adj->circuit->disable_threeway_adj) {
+ if (tw_adj->state == ISIS_THREEWAY_DOWN) {
+ next_tw_state = ISIS_THREEWAY_INITIALIZING;
+ } else if (tw_adj->state == ISIS_THREEWAY_INITIALIZING) {
+ next_tw_state = ISIS_THREEWAY_UP;
+ } else if (tw_adj->state == ISIS_THREEWAY_UP) {
+ if (adj->threeway_state == ISIS_THREEWAY_DOWN)
+ next_tw_state = ISIS_THREEWAY_DOWN;
+ else
+ next_tw_state = ISIS_THREEWAY_UP;
+ }
+ } else {
+ next_tw_state = ISIS_THREEWAY_UP;
+ }
+
+ if (next_tw_state != adj->threeway_state) {
+ if (IS_DEBUG_ADJ_PACKETS) {
+ zlog_info("ISIS-Adj (%s): Threeway state change %s to %s",
+ adj->circuit->area->area_tag,
+ isis_threeway_state_name(adj->threeway_state),
+ isis_threeway_state_name(next_tw_state));
+ }
+ }
+
+ if (next_tw_state != ISIS_THREEWAY_DOWN)
+ fabricd_initial_sync_hello(adj->circuit);
+
+ if (next_tw_state == ISIS_THREEWAY_DOWN) {
+ isis_adj_state_change(&adj, ISIS_ADJ_DOWN,
+ "Neighbor restarted");
+ return;
+ }
+
+ if (next_tw_state == ISIS_THREEWAY_UP) {
+ if (adj->adj_state != ISIS_ADJ_UP) {
+ isis_adj_state_change(&adj, ISIS_ADJ_UP, NULL);
+ adj->adj_usage = adj_usage;
+ }
+ }
+
+ if (adj->threeway_state != next_tw_state) {
+ send_hello_sched(adj->circuit, 0, TRIGGERED_IIH_DELAY);
+ }
+
+ adj->threeway_state = next_tw_state;
+}
+const char *isis_adj_name(const struct isis_adjacency *adj)
+{
+ static char buf[ISO_SYSID_STRLEN];
+
+ if (!adj)
+ return "NONE";
+
+ struct isis_dynhn *dyn;
+
+ dyn = dynhn_find_by_id(adj->circuit->isis, adj->sysid);
+ if (dyn)
+ return dyn->hostname;
+
+ snprintfrr(buf, sizeof(buf), "%pSY", adj->sysid);
+ return buf;
+}
+void isis_log_adj_change(struct isis_adjacency *adj,
+ enum isis_adj_state old_state,
+ enum isis_adj_state new_state, const char *reason)
+{
+ zlog_info(
+ "%%ADJCHANGE: Adjacency to %s (%s) for %s changed from %s to %s, %s",
+ isis_adj_name(adj), adj->circuit->interface->name,
+ adj_level2string(adj->level), adj_state2string(old_state),
+ adj_state2string(new_state), reason ? reason : "unspecified");
+}
+void isis_adj_state_change(struct isis_adjacency **padj,
+ enum isis_adj_state new_state, const char *reason)
+{
+ struct isis_adjacency *adj = *padj;
+ enum isis_adj_state old_state = adj->adj_state;
+ struct isis_circuit *circuit = adj->circuit;
+ bool del = false;
+
+ if (new_state == old_state)
+ return;
+
+ if (old_state == ISIS_ADJ_UP &&
+ !CHECK_FLAG(adj->circuit->flags, ISIS_CIRCUIT_IF_DOWN_FROM_Z)) {
+ if (IS_DEBUG_EVENTS)
+ zlog_debug(
+ "ISIS-Adj (%s): Starting fast-reroute on state change %d->%d: %s",
+ circuit->area->area_tag, old_state, new_state,
+ reason ? reason : "unspecified");
+ isis_adj_route_switchover(adj);
+ }
+
+ adj->adj_state = new_state;
+ send_hello_sched(circuit, adj->level, TRIGGERED_IIH_DELAY);
+
+ if (IS_DEBUG_ADJ_PACKETS) {
+ zlog_debug("ISIS-Adj (%s): Adjacency state change %d->%d: %s",
+ circuit->area->area_tag, old_state, new_state,
+ reason ? reason : "unspecified");
+ }
+
+ if (circuit->area->log_adj_changes)
+ isis_log_adj_change(adj, old_state, new_state, reason);
+
+#ifndef FABRICD
+ /* send northbound notification */
+ isis_notif_adj_state_change(adj, new_state, reason);
+#endif /* ifndef FABRICD */
+
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) {
+ if ((adj->level & level) == 0)
+ continue;
+ if (new_state == ISIS_ADJ_UP) {
+ circuit->adj_state_changes++;
+ circuit->upadjcount[level - 1]++;
+ /* update counter & timers for debugging
+ * purposes */
+ adj->last_flap = time(NULL);
+ adj->flaps++;
+ } else if (old_state == ISIS_ADJ_UP) {
+ circuit->adj_state_changes++;
+
+ circuit->upadjcount[level - 1]--;
+ if (circuit->upadjcount[level - 1] == 0)
+ isis_tx_queue_clean(circuit->tx_queue);
+
+ if (new_state == ISIS_ADJ_DOWN) {
+ listnode_delete(
+ circuit->u.bc.adjdb[level - 1],
+ adj);
+
+ del = true;
+ }
+ }
+
+ if (circuit->u.bc.lan_neighs[level - 1]) {
+ list_delete_all_node(
+ circuit->u.bc.lan_neighs[level - 1]);
+ isis_adj_build_neigh_list(
+ circuit->u.bc.adjdb[level - 1],
+ circuit->u.bc.lan_neighs[level - 1]);
+ }
+
+ /* On adjacency state change send new pseudo LSP if we
+ * are the DR */
+ if (circuit->u.bc.is_dr[level - 1])
+ lsp_regenerate_schedule_pseudo(circuit, level);
+ }
+
+ } else if (circuit->circ_type == CIRCUIT_T_P2P) {
+ for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) {
+ if ((adj->level & level) == 0)
+ continue;
+ if (new_state == ISIS_ADJ_UP) {
+ circuit->upadjcount[level - 1]++;
+
+ /* update counter & timers for debugging
+ * purposes */
+ adj->last_flap = time(NULL);
+ adj->flaps++;
+
+ if (level == IS_LEVEL_1) {
+ event_add_timer(
+ master, send_l1_csnp, circuit,
+ 0, &circuit->t_send_csnp[0]);
+ } else {
+ event_add_timer(
+ master, send_l2_csnp, circuit,
+ 0, &circuit->t_send_csnp[1]);
+ }
+ } else if (old_state == ISIS_ADJ_UP) {
+ circuit->upadjcount[level - 1]--;
+ if (circuit->upadjcount[level - 1] == 0)
+ isis_tx_queue_clean(circuit->tx_queue);
+
+ if (new_state == ISIS_ADJ_DOWN) {
+ if (adj->circuit->u.p2p.neighbor == adj)
+ adj->circuit->u.p2p.neighbor =
+ NULL;
+
+ del = true;
+ }
+ }
+ }
+ }
+
+ hook_call(isis_adj_state_change_hook, adj);
+
+ if (del) {
+ isis_delete_adj(adj);
+ *padj = NULL;
+ }
+}
+
+
+void isis_adj_print(struct isis_adjacency *adj)
+{
+ struct isis_dynhn *dyn;
+
+ if (!adj)
+ return;
+ dyn = dynhn_find_by_id(adj->circuit->isis, adj->sysid);
+ if (dyn)
+ zlog_debug("%s", dyn->hostname);
+
+ zlog_debug("SystemId %20pSY SNPA %pSY, level %d; Holding Time %d",
+ adj->sysid, adj->snpa, adj->level, adj->hold_time);
+ if (adj->ipv4_address_count) {
+ zlog_debug("IPv4 Address(es):");
+ for (unsigned int i = 0; i < adj->ipv4_address_count; i++)
+ zlog_debug("%pI4", &adj->ipv4_addresses[i]);
+ }
+
+ if (adj->ll_ipv6_count) {
+ zlog_debug("IPv6 Address(es):");
+ for (unsigned int i = 0; i < adj->ll_ipv6_count; i++) {
+ char buf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &adj->ll_ipv6_addrs[i], buf,
+ sizeof(buf));
+ zlog_debug("%s", buf);
+ }
+ }
+ zlog_debug("Speaks: %s", nlpid2string(&adj->nlpids));
+
+ return;
+}
+
+const char *isis_adj_yang_state(enum isis_adj_state state)
+{
+ switch (state) {
+ case ISIS_ADJ_DOWN:
+ return "down";
+ case ISIS_ADJ_UP:
+ return "up";
+ case ISIS_ADJ_INITIALIZING:
+ return "init";
+ case ISIS_ADJ_UNKNOWN:
+ return "failed";
+ }
+
+ assert(!"Reached end of function where we are not expecting to");
+}
+
+void isis_adj_expire(struct event *thread)
+{
+ struct isis_adjacency *adj;
+
+ /*
+ * Get the adjacency
+ */
+ adj = EVENT_ARG(thread);
+ assert(adj);
+ adj->t_expire = NULL;
+
+ /* trigger the adj expire event */
+ isis_adj_state_change(&adj, ISIS_ADJ_DOWN, "holding time expired");
+}
+
+/*
+ * show isis neighbor [detail] json
+ */
+void isis_adj_print_json(struct isis_adjacency *adj, struct json_object *json,
+ char detail)
+{
+ json_object *iface_json, *ipv4_addr_json, *ipv6_link_json,
+ *ipv6_non_link_json, *topo_json, *dis_flaps_json,
+ *area_addr_json, *adj_sid_json;
+ time_t now;
+ struct isis_dynhn *dyn;
+ int level;
+ char buf[256];
+
+ json_object_string_add(json, "adj", isis_adj_name(adj));
+
+ if (detail == ISIS_UI_LEVEL_BRIEF) {
+ if (adj->circuit)
+ json_object_string_add(json, "interface",
+ adj->circuit->interface->name);
+ else
+ json_object_string_add(json, "interface",
+ "NULL circuit!");
+ json_object_int_add(json, "level", adj->level);
+ json_object_string_add(json, "state",
+ adj_state2string(adj->adj_state));
+ now = time(NULL);
+ if (adj->last_upd) {
+ if (adj->last_upd + adj->hold_time < now)
+ json_object_string_add(json, "last-upd",
+ "expiring");
+ else
+ json_object_string_add(
+ json, "expires-in",
+ time2string(adj->last_upd +
+ adj->hold_time - now));
+ }
+ json_object_string_addf(json, "snpa", "%pSY", adj->snpa);
+ }
+
+ if (detail == ISIS_UI_LEVEL_DETAIL) {
+ struct sr_adjacency *sra;
+ struct listnode *anode;
+
+ level = adj->level;
+ iface_json = json_object_new_object();
+ json_object_object_add(json, "interface", iface_json);
+ if (adj->circuit)
+ json_object_string_add(iface_json, "name",
+ adj->circuit->interface->name);
+ else
+ json_object_string_add(iface_json, "name",
+ "null-circuit");
+ json_object_int_add(json, "level", adj->level);
+ json_object_string_add(iface_json, "state",
+ adj_state2string(adj->adj_state));
+ now = time(NULL);
+ if (adj->last_upd) {
+ if (adj->last_upd + adj->hold_time < now)
+ json_object_string_add(iface_json, "last-upd",
+ "expiring");
+ else
+ json_object_string_add(
+ json, "expires-in",
+ time2string(adj->last_upd +
+ adj->hold_time - now));
+ } else
+ json_object_string_add(json, "expires-in",
+ time2string(adj->hold_time));
+ json_object_int_add(iface_json, "adj-flaps", adj->flaps);
+ json_object_string_add(iface_json, "last-ago",
+ time2string(now - adj->last_flap));
+ json_object_string_add(iface_json, "circuit-type",
+ circuit_t2string(adj->circuit_t));
+ json_object_string_add(iface_json, "speaks",
+ nlpid2string(&adj->nlpids));
+ if (adj->mt_count != 1 ||
+ adj->mt_set[0] != ISIS_MT_IPV4_UNICAST) {
+ topo_json = json_object_new_object();
+ json_object_object_add(iface_json, "topologies",
+ topo_json);
+ for (unsigned int i = 0; i < adj->mt_count; i++) {
+ snprintfrr(buf, sizeof(buf), "topo-%d", i);
+ json_object_string_add(
+ topo_json, buf,
+ isis_mtid2str(adj->mt_set[i]));
+ }
+ }
+ json_object_string_addf(iface_json, "snpa", "%pSY", adj->snpa);
+ if (adj->circuit &&
+ (adj->circuit->circ_type == CIRCUIT_T_BROADCAST)) {
+ dyn = dynhn_find_by_id(adj->circuit->isis, adj->lanid);
+ if (dyn) {
+ snprintfrr(buf, sizeof(buf), "%s-%02x",
+ dyn->hostname,
+ adj->lanid[ISIS_SYS_ID_LEN]);
+ json_object_string_add(iface_json, "lan-id",
+ buf);
+ } else {
+ json_object_string_addf(iface_json, "lan-id",
+ "%pSY", adj->lanid);
+ }
+
+ json_object_int_add(iface_json, "lan-prio",
+ adj->prio[adj->level - 1]);
+
+ dis_flaps_json = json_object_new_object();
+ json_object_object_add(iface_json, "dis-flaps",
+ dis_flaps_json);
+ json_object_string_add(
+ dis_flaps_json, "dis-record",
+ isis_disflag2string(
+ adj->dis_record[ISIS_LEVELS + level - 1]
+ .dis));
+ json_object_int_add(dis_flaps_json, "last",
+ adj->dischanges[level - 1]);
+ json_object_string_add(
+ dis_flaps_json, "ago",
+ time2string(now - (adj->dis_record[ISIS_LEVELS +
+ level - 1]
+ .last_dis_change)));
+ }
+
+ if (adj->area_address_count) {
+ area_addr_json = json_object_new_object();
+ json_object_object_add(iface_json, "area-address",
+ area_addr_json);
+ for (unsigned int i = 0; i < adj->area_address_count;
+ i++) {
+ json_object_string_addf(
+ area_addr_json, "isonet", "%pIS",
+ &adj->area_addresses[i]);
+ }
+ }
+ if (adj->ipv4_address_count) {
+ ipv4_addr_json = json_object_new_object();
+ json_object_object_add(iface_json, "ipv4-address",
+ ipv4_addr_json);
+ for (unsigned int i = 0; i < adj->ipv4_address_count;
+ i++){
+ inet_ntop(AF_INET, &adj->ipv4_addresses[i], buf,
+ sizeof(buf));
+ json_object_string_add(ipv4_addr_json, "ipv4", buf);
+ }
+ }
+ if (adj->ll_ipv6_count) {
+ ipv6_link_json = json_object_new_object();
+ json_object_object_add(iface_json, "ipv6-link-local",
+ ipv6_link_json);
+ for (unsigned int i = 0; i < adj->ll_ipv6_count; i++) {
+ char buf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &adj->ll_ipv6_addrs[i], buf,
+ sizeof(buf));
+ json_object_string_add(ipv6_link_json, "ipv6",
+ buf);
+ }
+ }
+ if (adj->global_ipv6_count) {
+ ipv6_non_link_json = json_object_new_object();
+ json_object_object_add(iface_json, "ipv6-global",
+ ipv6_non_link_json);
+ for (unsigned int i = 0; i < adj->global_ipv6_count;
+ i++) {
+ char buf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &adj->global_ipv6_addrs[i],
+ buf, sizeof(buf));
+ json_object_string_add(ipv6_non_link_json,
+ "ipv6", buf);
+ }
+ }
+
+ adj_sid_json = json_object_new_object();
+ json_object_object_add(iface_json, "adj-sid", adj_sid_json);
+ for (ALL_LIST_ELEMENTS_RO(adj->adj_sids, anode, sra)) {
+ const char *adj_type;
+ const char *backup;
+ uint32_t sid;
+
+ switch (sra->adj->circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ adj_type = "LAN Adjacency-SID";
+ sid = sra->u.ladj_sid->sid;
+ break;
+ case CIRCUIT_T_P2P:
+ adj_type = "Adjacency-SID";
+ sid = sra->u.adj_sid->sid;
+ break;
+ default:
+ continue;
+ }
+ backup = (sra->type == ISIS_SR_LAN_BACKUP) ? " (backup)"
+ : "";
+
+ json_object_string_add(adj_sid_json, "nexthop",
+ (sra->nexthop.family == AF_INET)
+ ? "IPv4"
+ : "IPv6");
+ json_object_string_add(adj_sid_json, "adj-type",
+ adj_type);
+ json_object_string_add(adj_sid_json, "is-backup",
+ backup);
+ json_object_int_add(adj_sid_json, "sid", sid);
+ }
+ }
+ return;
+}
+
+/*
+ * show isis neighbor [detail]
+ */
+void isis_adj_print_vty(struct isis_adjacency *adj, struct vty *vty,
+ char detail)
+{
+ time_t now;
+ struct isis_dynhn *dyn;
+ int level;
+
+ vty_out(vty, " %-20s", isis_adj_name(adj));
+
+ if (detail == ISIS_UI_LEVEL_BRIEF) {
+ if (adj->circuit)
+ vty_out(vty, "%-12s", adj->circuit->interface->name);
+ else
+ vty_out(vty, "NULL circuit!");
+ vty_out(vty, "%-3u", adj->level); /* level */
+ vty_out(vty, "%-13s", adj_state2string(adj->adj_state));
+ now = time(NULL);
+ if (adj->last_upd) {
+ if (adj->last_upd + adj->hold_time < now)
+ vty_out(vty, " Expiring");
+ else
+ vty_out(vty, " %-9llu",
+ (unsigned long long)adj->last_upd
+ + adj->hold_time - now);
+ } else
+ vty_out(vty, "- ");
+ vty_out(vty, "%-10pSY", adj->snpa);
+ vty_out(vty, "\n");
+ }
+
+ if (detail == ISIS_UI_LEVEL_DETAIL) {
+ struct sr_adjacency *sra;
+ struct listnode *anode;
+
+ level = adj->level;
+ vty_out(vty, "\n");
+ if (adj->circuit)
+ vty_out(vty, " Interface: %s",
+ adj->circuit->interface->name);
+ else
+ vty_out(vty, " Interface: NULL circuit");
+ vty_out(vty, ", Level: %u", adj->level); /* level */
+ vty_out(vty, ", State: %s", adj_state2string(adj->adj_state));
+ now = time(NULL);
+ if (adj->last_upd) {
+ if (adj->last_upd + adj->hold_time < now)
+ vty_out(vty, " Expiring");
+ else
+ vty_out(vty, ", Expires in %s",
+ time2string(adj->last_upd
+ + adj->hold_time - now));
+ } else
+ vty_out(vty, ", Expires in %s",
+ time2string(adj->hold_time));
+ vty_out(vty, "\n");
+ vty_out(vty, " Adjacency flaps: %u", adj->flaps);
+ vty_out(vty, ", Last: %s ago",
+ time2string(now - adj->last_flap));
+ vty_out(vty, "\n");
+ vty_out(vty, " Circuit type: %s",
+ circuit_t2string(adj->circuit_t));
+ vty_out(vty, ", Speaks: %s", nlpid2string(&adj->nlpids));
+ vty_out(vty, "\n");
+ if (adj->mt_count != 1
+ || adj->mt_set[0] != ISIS_MT_IPV4_UNICAST) {
+ vty_out(vty, " Topologies:\n");
+ for (unsigned int i = 0; i < adj->mt_count; i++)
+ vty_out(vty, " %s\n",
+ isis_mtid2str(adj->mt_set[i]));
+ }
+ vty_out(vty, " SNPA: %pSY", adj->snpa);
+ if (adj->circuit
+ && (adj->circuit->circ_type == CIRCUIT_T_BROADCAST)) {
+ dyn = dynhn_find_by_id(adj->circuit->isis, adj->lanid);
+ if (dyn)
+ vty_out(vty, ", LAN id: %s.%02x", dyn->hostname,
+ adj->lanid[ISIS_SYS_ID_LEN]);
+ else
+ vty_out(vty, ", LAN id: %pPN", adj->lanid);
+
+ vty_out(vty, "\n");
+ vty_out(vty, " LAN Priority: %u",
+ adj->prio[adj->level - 1]);
+
+ vty_out(vty, ", %s, DIS flaps: %u, Last: %s ago",
+ isis_disflag2string(
+ adj->dis_record[ISIS_LEVELS + level - 1]
+ .dis),
+ adj->dischanges[level - 1],
+ time2string(now - (adj->dis_record[ISIS_LEVELS
+ + level - 1]
+ .last_dis_change)));
+ }
+ vty_out(vty, "\n");
+
+ if (adj->area_address_count) {
+ vty_out(vty, " Area Address(es):\n");
+ for (unsigned int i = 0; i < adj->area_address_count;
+ i++) {
+ vty_out(vty, " %pIS\n",
+ &adj->area_addresses[i]);
+ }
+ }
+ if (adj->ipv4_address_count) {
+ vty_out(vty, " IPv4 Address(es):\n");
+ for (unsigned int i = 0; i < adj->ipv4_address_count;
+ i++)
+ vty_out(vty, " %pI4\n",
+ &adj->ipv4_addresses[i]);
+ }
+ if (adj->ll_ipv6_count) {
+ vty_out(vty, " IPv6 Address(es):\n");
+ for (unsigned int i = 0; i < adj->ll_ipv6_count; i++) {
+ char buf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &adj->ll_ipv6_addrs[i],
+ buf, sizeof(buf));
+ vty_out(vty, " %s\n", buf);
+ }
+ }
+ if (adj->global_ipv6_count) {
+ vty_out(vty, " Global IPv6 Address(es):\n");
+ for (unsigned int i = 0; i < adj->global_ipv6_count;
+ i++) {
+ char buf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &adj->global_ipv6_addrs[i],
+ buf, sizeof(buf));
+ vty_out(vty, " %s\n", buf);
+ }
+ }
+ if (adj->circuit && adj->circuit->bfd_config.enabled) {
+ vty_out(vty, " BFD is %s%s\n",
+ adj->bfd_session ? "active, status "
+ : "configured",
+ !adj->bfd_session
+ ? ""
+ : bfd_get_status_str(bfd_sess_status(
+ adj->bfd_session)));
+ }
+ for (ALL_LIST_ELEMENTS_RO(adj->adj_sids, anode, sra)) {
+ const char *adj_type;
+ const char *backup;
+ uint32_t sid;
+
+ switch (sra->adj->circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ adj_type = "LAN Adjacency-SID";
+ sid = sra->u.ladj_sid->sid;
+ break;
+ case CIRCUIT_T_P2P:
+ adj_type = "Adjacency-SID";
+ sid = sra->u.adj_sid->sid;
+ break;
+ default:
+ continue;
+ }
+ backup = (sra->type == ISIS_SR_LAN_BACKUP) ? " (backup)"
+ : "";
+
+ vty_out(vty, " %s %s%s: %u\n",
+ (sra->nexthop.family == AF_INET) ? "IPv4"
+ : "IPv6",
+ adj_type, backup, sid);
+ }
+ vty_out(vty, "\n");
+ }
+ return;
+}
+
+void isis_adj_build_neigh_list(struct list *adjdb, struct list *list)
+{
+ struct isis_adjacency *adj;
+ struct listnode *node;
+
+ if (!list) {
+ zlog_warn("%s: NULL list", __func__);
+ return;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) {
+ if (!adj) {
+ zlog_warn("%s: NULL adj", __func__);
+ return;
+ }
+
+ if ((adj->adj_state == ISIS_ADJ_UP
+ || adj->adj_state == ISIS_ADJ_INITIALIZING))
+ listnode_add(list, adj->snpa);
+ }
+ return;
+}
+
+void isis_adj_build_up_list(struct list *adjdb, struct list *list)
+{
+ struct isis_adjacency *adj;
+ struct listnode *node;
+
+ if (adjdb == NULL) {
+ zlog_warn("%s: adjacency DB is empty", __func__);
+ return;
+ }
+
+ if (!list) {
+ zlog_warn("%s: NULL list", __func__);
+ return;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) {
+ if (!adj) {
+ zlog_warn("%s: NULL adj", __func__);
+ return;
+ }
+
+ if (adj->adj_state == ISIS_ADJ_UP)
+ listnode_add(list, adj);
+ }
+
+ return;
+}
+
+int isis_adj_usage2levels(enum isis_adj_usage usage)
+{
+ switch (usage) {
+ case ISIS_ADJ_LEVEL1:
+ return IS_LEVEL_1;
+ case ISIS_ADJ_LEVEL2:
+ return IS_LEVEL_2;
+ case ISIS_ADJ_LEVEL1AND2:
+ return IS_LEVEL_1 | IS_LEVEL_2;
+ case ISIS_ADJ_NONE:
+ return 0;
+ }
+
+ assert(!"Reached end of function where we are not expecting to");
+}
diff --git a/isisd/isis_adjacency.h b/isisd/isis_adjacency.h
new file mode 100644
index 0000000..dc18105
--- /dev/null
+++ b/isisd/isis_adjacency.h
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_adjacency.h
+ * IS-IS adjacency handling
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ *
+ */
+
+#ifndef _ZEBRA_ISIS_ADJACENCY_H
+#define _ZEBRA_ISIS_ADJACENCY_H
+
+#include "isisd/isis_tlvs.h"
+
+DECLARE_MTYPE(ISIS_ADJACENCY_INFO);
+
+enum isis_adj_usage {
+ ISIS_ADJ_NONE,
+ ISIS_ADJ_LEVEL1,
+ ISIS_ADJ_LEVEL2,
+ ISIS_ADJ_LEVEL1AND2
+};
+
+enum isis_system_type {
+ ISIS_SYSTYPE_UNKNOWN,
+ ISIS_SYSTYPE_ES,
+ ISIS_SYSTYPE_IS,
+ ISIS_SYSTYPE_L1_IS,
+ ISIS_SYSTYPE_L2_IS
+};
+
+enum isis_adj_state {
+ ISIS_ADJ_UNKNOWN,
+ ISIS_ADJ_INITIALIZING,
+ ISIS_ADJ_UP,
+ ISIS_ADJ_DOWN
+};
+
+/*
+ * we use the following codes to give an indication _why_
+ * a specific adjacency is up or down
+ */
+enum isis_adj_updown_reason {
+ ISIS_ADJ_REASON_SEENSELF,
+ ISIS_ADJ_REASON_AREA_MISMATCH,
+ ISIS_ADJ_REASON_HOLDTIMER_EXPIRED,
+ ISIS_ADJ_REASON_AUTH_FAILED,
+ ISIS_ADJ_REASON_CHECKSUM_FAILED
+};
+
+#define DIS_RECORDS 8 /* keep the last 8 DIS state changes on record */
+
+struct isis_dis_record {
+ int dis; /* is our neighbor the DIS ? */
+ time_t last_dis_change; /* timestamp for last dis change */
+};
+
+struct bfd_session;
+struct isis_area;
+
+struct isis_adjacency {
+ uint8_t snpa[ETH_ALEN]; /* NeighbourSNPAAddress */
+ uint8_t sysid[ISIS_SYS_ID_LEN]; /* neighbourSystemIdentifier */
+ uint8_t lanid[ISIS_SYS_ID_LEN + 1]; /* LAN id on bcast circuits */
+ int dischanges[ISIS_LEVELS]; /* how many DIS changes ? */
+ /* an array of N levels for M records */
+ struct isis_dis_record dis_record[DIS_RECORDS * ISIS_LEVELS];
+ enum isis_adj_state adj_state; /* adjacencyState */
+ enum isis_adj_usage adj_usage; /* adjacencyUsage */
+ struct iso_address *area_addresses; /* areaAdressesOfNeighbour */
+ unsigned int area_address_count;
+ struct nlpids nlpids; /* protocols spoken ... */
+ struct in_addr *ipv4_addresses;
+ unsigned int ipv4_address_count;
+ struct in_addr router_address;
+ struct in6_addr *ll_ipv6_addrs; /* Link local IPv6 neighbor address */
+ unsigned int ll_ipv6_count;
+ struct in6_addr *global_ipv6_addrs; /* Global IPv6 neighbor address */
+ unsigned int global_ipv6_count;
+ struct in6_addr router_address6;
+ uint8_t prio[ISIS_LEVELS]; /* priorityOfNeighbour for DIS */
+ int circuit_t; /* from hello PDU hdr */
+ int level; /* level (1 or 2) */
+ enum isis_system_type sys_type; /* neighbourSystemType */
+ uint16_t hold_time; /* entryRemainingTime */
+ time_t last_upd;
+ time_t last_flap; /* last time the adj flapped */
+ enum isis_threeway_state threeway_state;
+ uint32_t ext_circuit_id;
+ int flaps; /* number of adjacency flaps */
+ struct event *t_expire; /* expire after hold_time */
+ struct isis_circuit *circuit; /* back pointer */
+ uint16_t *mt_set; /* Topologies this adjacency is valid for */
+ unsigned int mt_count; /* Number of entries in mt_set */
+ struct bfd_session_params *bfd_session;
+ struct list *adj_sids; /* Segment Routing Adj-SIDs. */
+ uint32_t snmp_idx;
+ struct listnode *snmp_list_node;
+
+ struct list *srv6_endx_sids; /* SRv6 End.X SIDs. */
+};
+
+struct isis_threeway_adj;
+
+struct isis_adjacency *isis_adj_lookup(const uint8_t *sysid,
+ struct list *adjdb);
+struct isis_adjacency *isis_adj_lookup_snpa(const uint8_t *ssnpa,
+ struct list *adjdb);
+struct isis_adjacency *isis_adj_find(const struct isis_area *area, int level,
+ const uint8_t *sysid);
+struct isis_adjacency *isis_new_adj(const uint8_t *id, const uint8_t *snpa,
+ int level, struct isis_circuit *circuit);
+void isis_delete_adj(void *adj);
+void isis_adj_process_threeway(struct isis_adjacency *adj,
+ struct isis_threeway_adj *tw_adj,
+ enum isis_adj_usage adj_usage);
+DECLARE_HOOK(isis_adj_state_change_hook, (struct isis_adjacency *adj), (adj));
+DECLARE_HOOK(isis_adj_ip_enabled_hook,
+ (struct isis_adjacency * adj, int family, bool global),
+ (adj, family, global));
+DECLARE_HOOK(isis_adj_ip_disabled_hook,
+ (struct isis_adjacency * adj, int family, bool global),
+ (adj, family, global));
+void isis_log_adj_change(struct isis_adjacency *adj,
+ enum isis_adj_state old_state,
+ enum isis_adj_state new_state, const char *reason);
+void isis_adj_state_change(struct isis_adjacency **adj,
+ enum isis_adj_state state, const char *reason);
+void isis_adj_print(struct isis_adjacency *adj);
+const char *isis_adj_yang_state(enum isis_adj_state state);
+void isis_adj_expire(struct event *thread);
+void isis_adj_print_vty(struct isis_adjacency *adj, struct vty *vty,
+ char detail);
+void isis_adj_print_json(struct isis_adjacency *adj, struct json_object *json,
+ char detail);
+void isis_adj_build_neigh_list(struct list *adjdb, struct list *list);
+void isis_adj_build_up_list(struct list *adjdb, struct list *list);
+int isis_adj_usage2levels(enum isis_adj_usage usage);
+void isis_bfd_startup_timer(struct event *thread);
+const char *isis_adj_name(const struct isis_adjacency *adj);
+#endif /* ISIS_ADJACENCY_H */
diff --git a/isisd/isis_affinitymap.c b/isisd/isis_affinitymap.c
new file mode 100644
index 0000000..41bad0a
--- /dev/null
+++ b/isisd/isis_affinitymap.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* IS-IS affinity-map
+ * Copyright 2023 6WIND S.A.
+ */
+
+#include <zebra.h>
+#include "lib/if.h"
+#include "lib/vrf.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_affinitymap.h"
+
+#ifndef FABRICD
+
+static bool isis_affinity_map_check_use(const char *affmap_name)
+{
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+ struct isis_area *area;
+ struct listnode *area_node, *fa_node;
+ struct flex_algo *fa;
+ struct affinity_map *map;
+ uint16_t pos;
+
+ if (!isis)
+ return false;
+
+ map = affinity_map_get(affmap_name);
+ pos = map->bit_position;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, area_node, area)) {
+ for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, fa_node,
+ fa)) {
+ if (admin_group_get(&fa->admin_group_exclude_any,
+ pos) ||
+ admin_group_get(&fa->admin_group_include_any,
+ pos) ||
+ admin_group_get(&fa->admin_group_include_all, pos))
+ return true;
+ }
+ }
+ return false;
+}
+
+static void isis_affinity_map_update(const char *affmap_name, uint16_t old_pos,
+ uint16_t new_pos)
+{
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+ struct listnode *area_node, *fa_node;
+ struct isis_area *area;
+ struct flex_algo *fa;
+ bool changed;
+
+ if (!isis)
+ return;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, area_node, area)) {
+ changed = false;
+ for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, fa_node,
+ fa)) {
+ if (admin_group_get(&fa->admin_group_exclude_any,
+ old_pos)) {
+ admin_group_unset(&fa->admin_group_exclude_any,
+ old_pos);
+ admin_group_set(&fa->admin_group_exclude_any,
+ new_pos);
+ changed = true;
+ }
+ if (admin_group_get(&fa->admin_group_include_any,
+ old_pos)) {
+ admin_group_unset(&fa->admin_group_include_any,
+ old_pos);
+ admin_group_set(&fa->admin_group_include_any,
+ new_pos);
+ changed = true;
+ }
+ if (admin_group_get(&fa->admin_group_include_all,
+ old_pos)) {
+ admin_group_unset(&fa->admin_group_include_all,
+ old_pos);
+ admin_group_set(&fa->admin_group_include_all,
+ new_pos);
+ changed = true;
+ }
+ }
+ if (changed)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ }
+}
+
+void isis_affinity_map_init(void)
+{
+ affinity_map_init();
+
+ affinity_map_set_check_use_hook(isis_affinity_map_check_use);
+ affinity_map_set_update_hook(isis_affinity_map_update);
+}
+
+#endif /* ifndef FABRICD */
diff --git a/isisd/isis_affinitymap.h b/isisd/isis_affinitymap.h
new file mode 100644
index 0000000..c432e99
--- /dev/null
+++ b/isisd/isis_affinitymap.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* IS-IS affinity-map header
+ * Copyright 2023 6WIND S.A.
+ */
+
+#ifndef __ISIS_AFFINITYMAP_H__
+#define __ISIS_AFFINITYMAP_H__
+
+#include "lib/affinitymap.h"
+
+#ifndef FABRICD
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void isis_affinity_map_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ifndef FABRICD */
+
+#endif /* __ISIS_AFFINITYMAP_H__ */
diff --git a/isisd/isis_bfd.c b/isisd/isis_bfd.c
new file mode 100644
index 0000000..5e24e35
--- /dev/null
+++ b/isisd/isis_bfd.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - BFD support
+ * Copyright (C) 2018 Christian Franke
+ */
+#include <zebra.h>
+
+#include "zclient.h"
+#include "nexthop.h"
+#include "bfd.h"
+#include "lib_errors.h"
+
+#include "isisd/isis_bfd.h"
+#include "isisd/isis_zebra.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isisd.h"
+#include "isisd/fabricd.h"
+
+DEFINE_MTYPE_STATIC(ISISD, BFD_SESSION, "ISIS BFD Session");
+
+static void adj_bfd_cb(struct bfd_session_params *bsp,
+ const struct bfd_session_status *bss, void *arg)
+{
+ struct isis_adjacency *adj = arg;
+
+ if (IS_DEBUG_BFD)
+ zlog_debug(
+ "ISIS-BFD: BFD changed status for adjacency %s old %s new %s",
+ isis_adj_name(adj),
+ bfd_get_status_str(bss->previous_state),
+ bfd_get_status_str(bss->state));
+
+ if (bss->state == BFD_STATUS_DOWN
+ && bss->previous_state == BFD_STATUS_UP) {
+ adj->circuit->area->bfd_signalled_down = true;
+ isis_adj_state_change(&adj, ISIS_ADJ_DOWN,
+ "bfd session went down");
+ }
+}
+
+static void bfd_handle_adj_down(struct isis_adjacency *adj)
+{
+ bfd_sess_free(&adj->bfd_session);
+}
+
+static void bfd_handle_adj_up(struct isis_adjacency *adj)
+{
+ struct isis_circuit *circuit = adj->circuit;
+ int family;
+ union g_addr dst_ip;
+ union g_addr src_ip;
+ struct list *local_ips;
+ struct prefix *local_ip;
+
+ if (!circuit->bfd_config.enabled) {
+ if (IS_DEBUG_BFD)
+ zlog_debug(
+ "ISIS-BFD: skipping BFD initialization on adjacency with %s because BFD is not enabled for the circuit",
+ isis_adj_name(adj));
+ goto out;
+ }
+
+ /* If IS-IS IPv6 is configured wait for IPv6 address to be programmed
+ * before starting up BFD
+ */
+ if (circuit->ipv6_router
+ && (listcount(circuit->ipv6_link) == 0
+ || adj->ll_ipv6_count == 0)) {
+ if (IS_DEBUG_BFD)
+ zlog_debug(
+ "ISIS-BFD: skipping BFD initialization on adjacency with %s because IPv6 is enabled but not ready",
+ isis_adj_name(adj));
+ return;
+ }
+
+ /*
+ * If IS-IS is enabled for both IPv4 and IPv6 on the circuit, prefer
+ * creating a BFD session over IPv6.
+ */
+ if (circuit->ipv6_router && adj->ll_ipv6_count) {
+ family = AF_INET6;
+ dst_ip.ipv6 = adj->ll_ipv6_addrs[0];
+ local_ips = circuit->ipv6_link;
+ if (list_isempty(local_ips)) {
+ if (IS_DEBUG_BFD)
+ zlog_debug(
+ "ISIS-BFD: skipping BFD initialization: IPv6 enabled and no local IPv6 addresses");
+ goto out;
+ }
+ local_ip = listgetdata(listhead(local_ips));
+ src_ip.ipv6 = local_ip->u.prefix6;
+ } else if (circuit->ip_router && adj->ipv4_address_count) {
+ family = AF_INET;
+ dst_ip.ipv4 = adj->ipv4_addresses[0];
+ local_ips = fabricd_ip_addrs(adj->circuit);
+ if (!local_ips || list_isempty(local_ips)) {
+ if (IS_DEBUG_BFD)
+ zlog_debug(
+ "ISIS-BFD: skipping BFD initialization: IPv4 enabled and no local IPv4 addresses");
+ goto out;
+ }
+ local_ip = listgetdata(listhead(local_ips));
+ src_ip.ipv4 = local_ip->u.prefix4;
+ } else
+ goto out;
+
+ if (adj->bfd_session == NULL)
+ adj->bfd_session = bfd_sess_new(adj_bfd_cb, adj);
+
+ bfd_sess_set_timers(adj->bfd_session, BFD_DEF_DETECT_MULT,
+ BFD_DEF_MIN_RX, BFD_DEF_MIN_TX);
+ if (family == AF_INET)
+ bfd_sess_set_ipv4_addrs(adj->bfd_session, &src_ip.ipv4,
+ &dst_ip.ipv4);
+ else
+ bfd_sess_set_ipv6_addrs(adj->bfd_session, &src_ip.ipv6,
+ &dst_ip.ipv6);
+ bfd_sess_set_interface(adj->bfd_session, adj->circuit->interface->name);
+ bfd_sess_set_vrf(adj->bfd_session,
+ adj->circuit->interface->vrf->vrf_id);
+ bfd_sess_set_profile(adj->bfd_session, circuit->bfd_config.profile);
+ bfd_sess_install(adj->bfd_session);
+ return;
+out:
+ bfd_handle_adj_down(adj);
+}
+
+static int bfd_handle_adj_state_change(struct isis_adjacency *adj)
+{
+ if (adj->adj_state == ISIS_ADJ_UP)
+ bfd_handle_adj_up(adj);
+ else
+ bfd_handle_adj_down(adj);
+ return 0;
+}
+
+static void bfd_adj_cmd(struct isis_adjacency *adj)
+{
+ if (adj->adj_state == ISIS_ADJ_UP && adj->circuit->bfd_config.enabled)
+ bfd_handle_adj_up(adj);
+ else
+ bfd_handle_adj_down(adj);
+}
+
+void isis_bfd_circuit_cmd(struct isis_circuit *circuit)
+{
+ switch (circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+ struct list *adjdb = circuit->u.bc.adjdb[level - 1];
+
+ struct listnode *node;
+ struct isis_adjacency *adj;
+
+ if (!adjdb)
+ continue;
+ for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj))
+ bfd_adj_cmd(adj);
+ }
+ break;
+ case CIRCUIT_T_P2P:
+ if (circuit->u.p2p.neighbor)
+ bfd_adj_cmd(circuit->u.p2p.neighbor);
+ break;
+ default:
+ break;
+ }
+}
+
+static int bfd_handle_adj_ip_enabled(struct isis_adjacency *adj, int family,
+ bool global)
+{
+
+ if (family != AF_INET6 || global)
+ return 0;
+
+ if (adj->bfd_session)
+ return 0;
+
+ if (adj->adj_state != ISIS_ADJ_UP)
+ return 0;
+
+ bfd_handle_adj_up(adj);
+
+ return 0;
+}
+
+static int bfd_handle_circuit_add_addr(struct isis_circuit *circuit)
+{
+ struct isis_adjacency *adj;
+ struct listnode *node;
+
+ if (circuit->area == NULL)
+ return 0;
+
+ for (ALL_LIST_ELEMENTS_RO(circuit->area->adjacency_list, node, adj)) {
+ if (adj->bfd_session)
+ continue;
+
+ if (adj->adj_state != ISIS_ADJ_UP)
+ continue;
+
+ bfd_handle_adj_up(adj);
+ }
+
+ return 0;
+}
+
+void isis_bfd_init(struct event_loop *tm)
+{
+ bfd_protocol_integration_init(zclient, tm);
+
+ hook_register(isis_adj_state_change_hook, bfd_handle_adj_state_change);
+ hook_register(isis_adj_ip_enabled_hook, bfd_handle_adj_ip_enabled);
+ hook_register(isis_circuit_add_addr_hook, bfd_handle_circuit_add_addr);
+}
diff --git a/isisd/isis_bfd.h b/isisd/isis_bfd.h
new file mode 100644
index 0000000..3cf0ed5
--- /dev/null
+++ b/isisd/isis_bfd.h
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - BFD support
+ * Copyright (C) 2018 Christian Franke
+ */
+#ifndef ISIS_BFD_H
+#define ISIS_BFD_H
+
+struct isis_circuit;
+struct event_loop;
+
+void isis_bfd_circuit_cmd(struct isis_circuit *circuit);
+void isis_bfd_init(struct event_loop *tm);
+
+#endif
+
diff --git a/isisd/isis_bpf.c b/isisd/isis_bpf.c
new file mode 100644
index 0000000..96d6291
--- /dev/null
+++ b/isisd/isis_bpf.c
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_bpf.c
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+#if ISIS_METHOD == ISIS_METHOD_BPF
+#include <net/if.h>
+#include <netinet/if_ether.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <net/bpf.h>
+
+#include "log.h"
+#include "network.h"
+#include "stream.h"
+#include "if.h"
+#include "lib_errors.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_network.h"
+#include "isisd/isis_pdu.h"
+
+#include "privs.h"
+
+struct bpf_insn llcfilter[] = {
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
+ ETHER_HDR_LEN), /* check first byte */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ISO_SAP, 0, 5),
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ETHER_HDR_LEN + 1),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ISO_SAP, 0,
+ 3), /* check second byte */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ETHER_HDR_LEN + 2),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x03, 0, 1), /* check third byte */
+ BPF_STMT(BPF_RET + BPF_K, (unsigned int)-1),
+ BPF_STMT(BPF_RET + BPF_K, 0)};
+unsigned int readblen = 0;
+uint8_t *readbuff = NULL;
+
+/*
+ * Table 9 - Architectural constants for use with ISO 8802 subnetworks
+ * ISO 10589 - 8.4.8
+ */
+
+static const uint8_t ALL_L1_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x14};
+static const uint8_t ALL_L2_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x15};
+static char sock_buff[16384];
+
+static int open_bpf_dev(struct isis_circuit *circuit)
+{
+ int i = 0, fd;
+ char bpfdev[128];
+ struct ifreq ifr;
+ unsigned int blen, immediate;
+#ifdef BIOCSSEESENT
+ unsigned int seesent;
+#endif
+ struct timeval timeout;
+ struct bpf_program bpf_prog;
+
+ do {
+ (void)snprintf(bpfdev, sizeof(bpfdev), "/dev/bpf%d", i++);
+ fd = open(bpfdev, O_RDWR);
+ } while (fd < 0 && errno == EBUSY);
+
+ if (fd < 0) {
+ zlog_warn("open_bpf_dev(): failed to create bpf socket: %s",
+ safe_strerror(errno));
+ return ISIS_WARNING;
+ }
+
+ zlog_debug("Opened BPF device %s", bpfdev);
+
+ memcpy(ifr.ifr_name, circuit->interface->name, sizeof(ifr.ifr_name));
+ if (ioctl(fd, BIOCSETIF, (caddr_t)&ifr) < 0) {
+ zlog_warn("open_bpf_dev(): failed to bind to interface: %s",
+ safe_strerror(errno));
+ return ISIS_WARNING;
+ }
+
+ if (ioctl(fd, BIOCGBLEN, (caddr_t)&blen) < 0) {
+ zlog_warn("failed to get BPF buffer len");
+ blen = circuit->interface->mtu;
+ }
+
+ readblen = blen;
+
+ if (readbuff == NULL)
+ readbuff = malloc(blen);
+
+ zlog_debug("BPF buffer len = %u", blen);
+
+ /* BPF(4): reads return immediately upon packet reception.
+ * Otherwise, a read will block until either the kernel
+ * buffer becomes full or a timeout occurs.
+ */
+ immediate = 1;
+ if (ioctl(fd, BIOCIMMEDIATE, (caddr_t)&immediate) < 0) {
+ zlog_warn("failed to set BPF dev to immediate mode");
+ }
+
+#ifdef BIOCSSEESENT
+ /*
+ * We want to see only incoming packets
+ */
+ seesent = 0;
+ if (ioctl(fd, BIOCSSEESENT, (caddr_t)&seesent) < 0) {
+ zlog_warn("failed to set BPF dev to incoming only mode");
+ }
+#endif
+
+ /*
+ * ...but all of them
+ */
+ if (ioctl(fd, BIOCPROMISC) < 0) {
+ zlog_warn("failed to set BPF dev to promiscuous mode");
+ }
+
+ /*
+ * If the buffer length is smaller than our mtu, lets try to increase it
+ */
+ if (blen < circuit->interface->mtu) {
+ if (ioctl(fd, BIOCSBLEN, &circuit->interface->mtu) < 0) {
+ zlog_warn("failed to set BPF buffer len (%u to %u)",
+ blen, circuit->interface->mtu);
+ }
+ }
+
+ /*
+ * Set a timeout parameter - hope this helps select()
+ */
+ timeout.tv_sec = 600;
+ timeout.tv_usec = 0;
+ if (ioctl(fd, BIOCSRTIMEOUT, (caddr_t)&timeout) < 0) {
+ zlog_warn("failed to set BPF device timeout");
+ }
+
+ /*
+ * And set the filter
+ */
+ memset(&bpf_prog, 0, sizeof(bpf_prog));
+ bpf_prog.bf_len = 8;
+ bpf_prog.bf_insns = &(llcfilter[0]);
+ if (ioctl(fd, BIOCSETF, (caddr_t)&bpf_prog) < 0) {
+ zlog_warn("%s: failed to install filter: %s", __func__,
+ safe_strerror(errno));
+ return ISIS_WARNING;
+ }
+
+ assert(fd > 0);
+
+ circuit->fd = fd;
+
+ return ISIS_OK;
+}
+
+/*
+ * Create the socket and set the tx/rx funcs
+ */
+int isis_sock_init(struct isis_circuit *circuit)
+{
+ int retval = ISIS_OK;
+
+ frr_with_privs(&isisd_privs) {
+
+ retval = open_bpf_dev(circuit);
+
+ if (retval != ISIS_OK) {
+ zlog_warn("%s: could not initialize the socket",
+ __func__);
+ break;
+ }
+
+ if (if_is_broadcast(circuit->interface)) {
+ circuit->tx = isis_send_pdu_bcast;
+ circuit->rx = isis_recv_pdu_bcast;
+ } else {
+ zlog_warn("%s: unknown circuit type", __func__);
+ retval = ISIS_WARNING;
+ break;
+ }
+ }
+
+ return retval;
+}
+
+int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa)
+{
+ int bytesread = 0, bytestoread = 0, offset, one = 1;
+ uint8_t *buff_ptr;
+ struct bpf_hdr *bpf_hdr;
+
+ assert(circuit->fd > 0);
+
+ if (ioctl(circuit->fd, FIONREAD, (caddr_t)&bytestoread) < 0) {
+ zlog_warn("ioctl() FIONREAD failed: %s", safe_strerror(errno));
+ }
+
+ if (bytestoread) {
+ bytesread = read(circuit->fd, readbuff, readblen);
+ }
+ if (bytesread < 0) {
+ zlog_warn("%s: read() failed: %s", __func__,
+ safe_strerror(errno));
+ return ISIS_WARNING;
+ }
+
+ if (bytesread == 0)
+ return ISIS_WARNING;
+
+ buff_ptr = readbuff;
+ while (buff_ptr < readbuff + bytesread) {
+ bpf_hdr = (struct bpf_hdr *) buff_ptr;
+ assert(bpf_hdr->bh_caplen == bpf_hdr->bh_datalen);
+ offset = bpf_hdr->bh_hdrlen + LLC_LEN + ETHER_HDR_LEN;
+
+ /* then we lose the BPF, LLC and ethernet headers */
+ stream_write(circuit->rcv_stream, buff_ptr + offset,
+ bpf_hdr->bh_caplen - LLC_LEN - ETHER_HDR_LEN);
+ stream_set_getp(circuit->rcv_stream, 0);
+
+ memcpy(ssnpa, buff_ptr + bpf_hdr->bh_hdrlen + ETHER_ADDR_LEN,
+ ETHER_ADDR_LEN);
+
+ isis_handle_pdu(circuit, ssnpa);
+ stream_reset(circuit->rcv_stream);
+ buff_ptr += BPF_WORDALIGN(bpf_hdr->bh_hdrlen +
+ bpf_hdr->bh_datalen);
+ }
+
+
+ if (ioctl(circuit->fd, BIOCFLUSH, &one) < 0)
+ zlog_warn("Flushing failed: %s", safe_strerror(errno));
+
+ return ISIS_OK;
+}
+
+int isis_send_pdu_bcast(struct isis_circuit *circuit, int level)
+{
+ struct ether_header *eth;
+ ssize_t written;
+ size_t buflen;
+
+ buflen = stream_get_endp(circuit->snd_stream) + LLC_LEN + ETHER_HDR_LEN;
+ if (buflen > sizeof(sock_buff)) {
+ zlog_warn(
+ "%s: sock_buff size %zu is less than output pdu size %zu on circuit %s",
+ __func__, sizeof(sock_buff), buflen,
+ circuit->interface->name);
+ return ISIS_WARNING;
+ }
+
+ stream_set_getp(circuit->snd_stream, 0);
+
+ /*
+ * First the eth header
+ */
+ eth = (struct ether_header *)sock_buff;
+ if (level == 1)
+ memcpy(eth->ether_dhost, ALL_L1_ISS, ETH_ALEN);
+ else
+ memcpy(eth->ether_dhost, ALL_L2_ISS, ETH_ALEN);
+ memcpy(eth->ether_shost, circuit->u.bc.snpa, ETH_ALEN);
+ size_t frame_size = stream_get_endp(circuit->snd_stream) + LLC_LEN;
+ eth->ether_type = htons(isis_ethertype(frame_size));
+
+ /*
+ * Then the LLC
+ */
+ sock_buff[ETHER_HDR_LEN] = ISO_SAP;
+ sock_buff[ETHER_HDR_LEN + 1] = ISO_SAP;
+ sock_buff[ETHER_HDR_LEN + 2] = 0x03;
+
+ /* then we copy the data */
+ memcpy(sock_buff + (LLC_LEN + ETHER_HDR_LEN), circuit->snd_stream->data,
+ stream_get_endp(circuit->snd_stream));
+
+ /* now we can send this */
+ written = write(circuit->fd, sock_buff, buflen);
+ if (written < 0) {
+ zlog_warn("IS-IS bpf: could not transmit packet on %s: %s",
+ circuit->interface->name, safe_strerror(errno));
+ if (ERRNO_IO_RETRY(errno))
+ return ISIS_WARNING;
+ return ISIS_ERROR;
+ }
+
+ return ISIS_OK;
+}
+
+#endif /* ISIS_METHOD == ISIS_METHOD_BPF */
diff --git a/isisd/isis_circuit.c b/isisd/isis_circuit.c
new file mode 100644
index 0000000..ffa6ad3
--- /dev/null
+++ b/isisd/isis_circuit.c
@@ -0,0 +1,1685 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_circuit.h
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+#include <zebra.h>
+#ifdef GNU_LINUX
+#include <net/ethernet.h>
+#else
+#include <netinet/if_ether.h>
+#endif
+
+#include "log.h"
+#include "memory.h"
+#include "vrf.h"
+#include "if.h"
+#include "linklist.h"
+#include "command.h"
+#include "frrevent.h"
+#include "vty.h"
+#include "hash.h"
+#include "prefix.h"
+#include "stream.h"
+#include "qobj.h"
+#include "lib/northbound_cli.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_network.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_dr.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isis_events.h"
+#include "isisd/isis_srv6.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_errors.h"
+#include "isisd/isis_tx_queue.h"
+#include "isisd/isis_nb.h"
+#include "isisd/isis_ldp_sync.h"
+
+DEFINE_MTYPE_STATIC(ISISD, ISIS_CIRCUIT, "ISIS circuit");
+
+DEFINE_QOBJ_TYPE(isis_circuit);
+
+DEFINE_HOOK(isis_if_new_hook, (struct interface *ifp), (ifp));
+
+/*
+ * Prototypes.
+ */
+int isis_if_new_hook(struct interface *);
+int isis_if_delete_hook(struct interface *);
+
+DEFINE_HOOK(isis_circuit_new_hook, (struct isis_circuit *circuit), (circuit));
+DEFINE_HOOK(isis_circuit_del_hook, (struct isis_circuit *circuit), (circuit));
+
+static void isis_circuit_enable(struct isis_circuit *circuit)
+{
+ struct isis_area *area = circuit->area;
+ struct interface *ifp = circuit->interface;
+
+ if (!area) {
+ area = isis_area_lookup(circuit->tag, ifp->vrf->vrf_id);
+ if (area)
+ isis_area_add_circuit(area, circuit);
+ }
+
+ if (if_is_operative(ifp))
+ isis_csm_state_change(IF_UP_FROM_Z, circuit, ifp);
+}
+
+static void isis_circuit_disable(struct isis_circuit *circuit)
+{
+ struct isis_area *area = circuit->area;
+ struct interface *ifp = circuit->interface;
+
+ if (if_is_operative(ifp))
+ isis_csm_state_change(IF_DOWN_FROM_Z, circuit, ifp);
+
+ if (area)
+ isis_area_del_circuit(area, circuit);
+}
+
+struct isis_circuit *isis_circuit_new(struct interface *ifp, const char *tag)
+{
+ struct isis_circuit *circuit;
+ int i;
+
+ circuit = XCALLOC(MTYPE_ISIS_CIRCUIT, sizeof(struct isis_circuit));
+
+ circuit->tag = XSTRDUP(MTYPE_ISIS_CIRCUIT, tag);
+
+ /*
+ * Default values
+ */
+#ifndef FABRICD
+ circuit->is_type_config = yang_get_default_enum(
+ "/frr-interface:lib/interface/frr-isisd:isis/circuit-type");
+ circuit->flags = 0;
+
+ circuit->pad_hellos = yang_get_default_enum(
+ "/frr-interface:lib/interface/frr-isisd:isis/hello/padding");
+ circuit->hello_interval[0] = yang_get_default_uint32(
+ "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-1");
+ circuit->hello_interval[1] = yang_get_default_uint32(
+ "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-2");
+ circuit->hello_multiplier[0] = yang_get_default_uint32(
+ "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-1");
+ circuit->hello_multiplier[1] = yang_get_default_uint32(
+ "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-2");
+ circuit->csnp_interval[0] = yang_get_default_uint16(
+ "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-1");
+ circuit->csnp_interval[1] = yang_get_default_uint16(
+ "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-2");
+ circuit->psnp_interval[0] = yang_get_default_uint16(
+ "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-1");
+ circuit->psnp_interval[1] = yang_get_default_uint16(
+ "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-2");
+ circuit->priority[0] = yang_get_default_uint8(
+ "/frr-interface:lib/interface/frr-isisd:isis/priority/level-1");
+ circuit->priority[1] = yang_get_default_uint8(
+ "/frr-interface:lib/interface/frr-isisd:isis/priority/level-2");
+ circuit->metric[0] = yang_get_default_uint32(
+ "/frr-interface:lib/interface/frr-isisd:isis/metric/level-1");
+ circuit->metric[1] = yang_get_default_uint32(
+ "/frr-interface:lib/interface/frr-isisd:isis/metric/level-2");
+ circuit->te_metric[0] = yang_get_default_uint32(
+ "/frr-interface:lib/interface/frr-isisd:isis/metric/level-1");
+ circuit->te_metric[1] = yang_get_default_uint32(
+ "/frr-interface:lib/interface/frr-isisd:isis/metric/level-2");
+
+ for (i = 0; i < 2; i++) {
+ circuit->level_arg[i].level = i + 1;
+ circuit->level_arg[i].circuit = circuit;
+ }
+#else
+ circuit->is_type_config = IS_LEVEL_1_AND_2;
+ circuit->flags = 0;
+ circuit->pad_hellos = ISIS_HELLO_PADDING_ALWAYS;
+ for (i = 0; i < 2; i++) {
+ circuit->hello_interval[i] = DEFAULT_HELLO_INTERVAL;
+ circuit->hello_multiplier[i] = DEFAULT_HELLO_MULTIPLIER;
+ circuit->csnp_interval[i] = DEFAULT_CSNP_INTERVAL;
+ circuit->psnp_interval[i] = DEFAULT_PSNP_INTERVAL;
+ circuit->priority[i] = DEFAULT_PRIORITY;
+ circuit->metric[i] = DEFAULT_CIRCUIT_METRIC;
+ circuit->te_metric[i] = DEFAULT_CIRCUIT_METRIC;
+ circuit->level_arg[i].level = i + 1;
+ circuit->level_arg[i].circuit = circuit;
+ }
+#endif /* ifndef FABRICD */
+
+ circuit->is_type = circuit->is_type_config;
+
+ circuit_mt_init(circuit);
+ isis_lfa_excluded_ifaces_init(circuit, ISIS_LEVEL1);
+ isis_lfa_excluded_ifaces_init(circuit, ISIS_LEVEL2);
+
+ circuit->ldp_sync_info = ldp_sync_info_create();
+ circuit->ldp_sync_info->enabled = LDP_IGP_SYNC_ENABLED;
+
+ QOBJ_REG(circuit, isis_circuit);
+
+ isis_circuit_if_bind(circuit, ifp);
+
+ circuit->ip_addrs = list_new();
+ circuit->ipv6_link = list_new();
+ circuit->ipv6_non_link = list_new();
+
+ if (ifp->ifindex != IFINDEX_INTERNAL)
+ isis_circuit_enable(circuit);
+
+ return circuit;
+}
+
+void isis_circuit_del(struct isis_circuit *circuit)
+{
+ if (!circuit)
+ return;
+
+ if (circuit->interface->ifindex != IFINDEX_INTERNAL)
+ isis_circuit_disable(circuit);
+
+ isis_circuit_if_unbind(circuit, circuit->interface);
+
+ QOBJ_UNREG(circuit);
+
+ ldp_sync_info_free(&circuit->ldp_sync_info);
+
+ circuit_mt_finish(circuit);
+ isis_lfa_excluded_ifaces_clear(circuit, ISIS_LEVEL1);
+ isis_lfa_excluded_ifaces_clear(circuit, ISIS_LEVEL2);
+
+ list_delete(&circuit->ip_addrs);
+ list_delete(&circuit->ipv6_link);
+ list_delete(&circuit->ipv6_non_link);
+
+ if (circuit->ext) {
+ isis_del_ext_subtlvs(circuit->ext);
+ circuit->ext = NULL;
+ }
+
+ XFREE(MTYPE_TMP, circuit->bfd_config.profile);
+ XFREE(MTYPE_ISIS_CIRCUIT, circuit->tag);
+
+ /* and lastly the circuit itself */
+ XFREE(MTYPE_ISIS_CIRCUIT, circuit);
+
+ return;
+}
+
+void isis_circuit_configure(struct isis_circuit *circuit,
+ struct isis_area *area)
+{
+ assert(area);
+ circuit->isis = area->isis;
+ circuit->area = area;
+
+ /*
+ * Whenever the is-type of an area is changed, the is-type of each
+ * circuit
+ * in that area is updated to a non-empty subset of the area is-type.
+ * Inversely, when configuring a new circuit, this property should be
+ * ensured as well.
+ */
+ if (area->is_type != IS_LEVEL_1_AND_2)
+ circuit->is_type = area->is_type;
+
+ /*
+ * Add the circuit into area
+ */
+ listnode_add(area->circuit_list, circuit);
+
+ circuit->idx = flags_get_index(&area->flags);
+
+ hook_call(isis_circuit_new_hook, circuit);
+
+ return;
+}
+
+void isis_circuit_deconfigure(struct isis_circuit *circuit,
+ struct isis_area *area)
+{
+ hook_call(isis_circuit_del_hook, circuit);
+
+ /* Free the index of SRM and SSN flags */
+ flags_free_index(&area->flags, circuit->idx);
+ circuit->idx = 0;
+
+ /* Reset IS type to configured */
+ circuit->is_type = circuit->is_type_config;
+
+ /* Remove circuit from area */
+ assert(circuit->area == area);
+ listnode_delete(area->circuit_list, circuit);
+ circuit->area = NULL;
+ circuit->isis = NULL;
+
+ return;
+}
+
+struct isis_circuit *circuit_scan_by_ifp(struct interface *ifp)
+{
+ return (struct isis_circuit *)ifp->info;
+}
+
+DEFINE_HOOK(isis_circuit_add_addr_hook, (struct isis_circuit *circuit),
+ (circuit));
+
+void isis_circuit_add_addr(struct isis_circuit *circuit,
+ struct connected *connected)
+{
+ struct listnode *node;
+ struct prefix_ipv4 *ipv4;
+ struct prefix_ipv6 *ipv6;
+
+ if (connected->address->family == AF_INET) {
+ uint32_t addr = connected->address->u.prefix4.s_addr;
+ addr = ntohl(addr);
+ if (IPV4_NET0(addr) || IPV4_NET127(addr) || IN_CLASSD(addr))
+ return;
+
+ for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node, ipv4))
+ if (prefix_same((struct prefix *)ipv4,
+ connected->address))
+ return;
+
+ ipv4 = prefix_ipv4_new();
+ ipv4->prefixlen = connected->address->prefixlen;
+ ipv4->prefix = connected->address->u.prefix4;
+ listnode_add(circuit->ip_addrs, ipv4);
+
+ /* Update Local IP address parameter if MPLS TE is enable */
+ if (circuit->ext && circuit->area
+ && IS_MPLS_TE(circuit->area->mta)) {
+ circuit->ext->local_addr.s_addr = ipv4->prefix.s_addr;
+ SET_SUBTLV(circuit->ext, EXT_LOCAL_ADDR);
+ }
+
+ if (circuit->area)
+ lsp_regenerate_schedule(circuit->area, circuit->is_type,
+ 0);
+
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("Added IP address %pFX to circuit %s",
+ connected->address,
+ circuit->interface->name);
+#endif /* EXTREME_DEBUG */
+ }
+ if (connected->address->family == AF_INET6) {
+ if (IN6_IS_ADDR_LOOPBACK(&connected->address->u.prefix6))
+ return;
+
+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node, ipv6))
+ if (prefix_same((struct prefix *)ipv6,
+ connected->address))
+ return;
+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node, ipv6))
+ if (prefix_same((struct prefix *)ipv6,
+ connected->address))
+ return;
+
+ ipv6 = prefix_ipv6_new();
+ ipv6->prefixlen = connected->address->prefixlen;
+ ipv6->prefix = connected->address->u.prefix6;
+
+ if (IN6_IS_ADDR_LINKLOCAL(&ipv6->prefix))
+ listnode_add(circuit->ipv6_link, ipv6);
+ else {
+ listnode_add(circuit->ipv6_non_link, ipv6);
+ /* Update Local IPv6 address param. if MPLS TE is on */
+ if (circuit->ext && circuit->area
+ && IS_MPLS_TE(circuit->area->mta)) {
+ IPV6_ADDR_COPY(&circuit->ext->local_addr6,
+ &ipv6->prefix);
+ SET_SUBTLV(circuit->ext, EXT_LOCAL_ADDR6);
+ }
+ }
+ if (circuit->area)
+ lsp_regenerate_schedule(circuit->area, circuit->is_type,
+ 0);
+
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("Added IPv6 address %pFX to circuit %s",
+ connected->address,
+ circuit->interface->name);
+#endif /* EXTREME_DEBUG */
+ }
+
+ hook_call(isis_circuit_add_addr_hook, circuit);
+
+ return;
+}
+
+void isis_circuit_del_addr(struct isis_circuit *circuit,
+ struct connected *connected)
+{
+ struct prefix_ipv4 *ipv4, *ip = NULL;
+ struct listnode *node;
+ struct prefix_ipv6 *ipv6, *ip6 = NULL;
+ int found = 0;
+
+ if (connected->address->family == AF_INET) {
+ ipv4 = prefix_ipv4_new();
+ ipv4->prefixlen = connected->address->prefixlen;
+ ipv4->prefix = connected->address->u.prefix4;
+
+ for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node, ip))
+ if (prefix_same((struct prefix *)ip,
+ (struct prefix *)ipv4))
+ break;
+
+ if (ip) {
+ listnode_delete(circuit->ip_addrs, ip);
+ prefix_ipv4_free(&ip);
+ if (circuit->area)
+ lsp_regenerate_schedule(circuit->area,
+ circuit->is_type, 0);
+ } else {
+ zlog_warn(
+ "Nonexistent ip address %pFX removal attempt from circuit %s",
+ connected->address, circuit->interface->name);
+ zlog_warn("Current ip addresses on %s:",
+ circuit->interface->name);
+ for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node,
+ ip)) {
+ zlog_warn(" %pFX", ip);
+ }
+ zlog_warn("End of addresses");
+ }
+
+ prefix_ipv4_free(&ipv4);
+ }
+ if (connected->address->family == AF_INET6) {
+ ipv6 = prefix_ipv6_new();
+ ipv6->prefixlen = connected->address->prefixlen;
+ ipv6->prefix = connected->address->u.prefix6;
+
+ if (IN6_IS_ADDR_LINKLOCAL(&ipv6->prefix)) {
+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node,
+ ip6)) {
+ if (prefix_same((struct prefix *)ip6,
+ (struct prefix *)ipv6))
+ break;
+ }
+ if (ip6) {
+ listnode_delete(circuit->ipv6_link, ip6);
+ prefix_ipv6_free(&ip6);
+ found = 1;
+ }
+ } else {
+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node,
+ ip6)) {
+ if (prefix_same((struct prefix *)ip6,
+ (struct prefix *)ipv6))
+ break;
+ }
+ if (ip6) {
+ listnode_delete(circuit->ipv6_non_link, ip6);
+ prefix_ipv6_free(&ip6);
+ found = 1;
+ }
+ }
+
+ if (!found) {
+ zlog_warn(
+ "Nonexistent ip address %pFX removal attempt from circuit %s",
+ connected->address, circuit->interface->name);
+ zlog_warn("Current ip addresses on %s:",
+ circuit->interface->name);
+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node,
+ ip6))
+ zlog_warn(" %pFX", (struct prefix *)ip6);
+ zlog_warn(" -----");
+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node,
+ ip6))
+ zlog_warn(" %pFX", (struct prefix *)ip6);
+ zlog_warn("End of addresses");
+ } else if (circuit->area)
+ lsp_regenerate_schedule(circuit->area, circuit->is_type,
+ 0);
+
+ prefix_ipv6_free(&ipv6);
+ }
+ return;
+}
+
+static uint8_t isis_circuit_id_gen(struct isis *isis, struct interface *ifp)
+{
+ /* Circuit ids MUST be unique for any broadcast circuits. Otherwise,
+ * Pseudo-Node LSPs cannot be generated correctly.
+ *
+ * Currently, allocate one circuit ID for any circuit, limiting the total
+ * numer of circuits IS-IS can run on to 255.
+ *
+ * We should revisit this when implementing 3-way adjacencies for p2p, since
+ * we then have extended interface IDs available.
+ */
+ uint8_t id = ifp->ifindex;
+ unsigned int i;
+
+ for (i = 0; i < 256; i++) {
+ if (id && !_ISIS_CHECK_FLAG(isis->circuit_ids_used, id))
+ break;
+ id++;
+ }
+
+ if (i == 256) {
+ zlog_warn("Could not allocate a circuit id for '%s'",
+ ifp->name);
+ return 0;
+ }
+
+ _ISIS_SET_FLAG(isis->circuit_ids_used, id);
+ return id;
+}
+
+void isis_circuit_if_add(struct isis_circuit *circuit, struct interface *ifp)
+{
+ struct listnode *node, *nnode;
+ struct connected *conn;
+
+ if (if_is_broadcast(ifp)) {
+ if (fabricd || circuit->circ_type_config == CIRCUIT_T_P2P)
+ circuit->circ_type = CIRCUIT_T_P2P;
+ else
+ circuit->circ_type = CIRCUIT_T_BROADCAST;
+ } else if (if_is_pointopoint(ifp)) {
+ circuit->circ_type = CIRCUIT_T_P2P;
+ } else if (if_is_loopback(ifp)) {
+ circuit->circ_type = CIRCUIT_T_LOOPBACK;
+ circuit->is_passive = 1;
+ } else {
+ /* It's normal in case of loopback etc. */
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("%s: unsupported media", __func__);
+ circuit->circ_type = CIRCUIT_T_UNKNOWN;
+ }
+
+ for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, conn))
+ isis_circuit_add_addr(circuit, conn);
+
+}
+
+void isis_circuit_if_del(struct isis_circuit *circuit, struct interface *ifp)
+{
+ struct listnode *node, *nnode;
+ struct connected *conn;
+
+ assert(circuit->interface == ifp);
+
+ /* destroy addresses */
+ for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, conn))
+ isis_circuit_del_addr(circuit, conn);
+
+ circuit->circ_type = CIRCUIT_T_UNKNOWN;
+}
+
+void isis_circuit_if_bind(struct isis_circuit *circuit, struct interface *ifp)
+{
+ assert(circuit != NULL);
+ assert(ifp != NULL);
+ if (circuit->interface)
+ assert(circuit->interface == ifp);
+ else
+ circuit->interface = ifp;
+ if (ifp->info)
+ assert(ifp->info == circuit);
+ else
+ ifp->info = circuit;
+}
+
+void isis_circuit_if_unbind(struct isis_circuit *circuit, struct interface *ifp)
+{
+ assert(circuit != NULL);
+ assert(ifp != NULL);
+ assert(circuit->interface == ifp);
+ assert(ifp->info == circuit);
+ circuit->interface = NULL;
+ ifp->info = NULL;
+}
+
+static void isis_circuit_update_all_srmflags(struct isis_circuit *circuit,
+ int is_set)
+{
+ struct isis_area *area;
+ struct isis_lsp *lsp;
+ int level;
+
+ assert(circuit);
+ area = circuit->area;
+ assert(area);
+ for (level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+ if (!(level & circuit->is_type))
+ continue;
+
+ if (!lspdb_count(&area->lspdb[level - 1]))
+ continue;
+
+ frr_each (lspdb, &area->lspdb[level - 1], lsp) {
+ if (is_set) {
+ isis_tx_queue_add(circuit->tx_queue, lsp,
+ TX_LSP_NORMAL);
+ } else {
+ isis_tx_queue_del(circuit->tx_queue, lsp);
+ }
+ }
+ }
+}
+
+size_t isis_circuit_pdu_size(struct isis_circuit *circuit)
+{
+ return ISO_MTU(circuit);
+}
+
+static bool isis_circuit_lfa_enabled(struct isis_circuit *circuit, int level)
+{
+ return (circuit->lfa_protection[level - 1] ||
+ circuit->rlfa_protection[level - 1] ||
+ circuit->tilfa_protection[level - 1]);
+}
+
+void isis_circuit_switchover_routes(struct isis_circuit *circuit, int family,
+ union g_addr *nexthop_ip, ifindex_t ifindex)
+{
+ char is_type;
+
+ if (!circuit->area)
+ return;
+
+ is_type = circuit->area->is_type;
+ if ((is_type == IS_LEVEL_1 || is_type == IS_LEVEL_1_AND_2) &&
+ isis_circuit_lfa_enabled(circuit, IS_LEVEL_1))
+ isis_area_switchover_routes(circuit->area, family, nexthop_ip,
+ ifindex, IS_LEVEL_1);
+ if ((is_type == IS_LEVEL_2 || is_type == IS_LEVEL_1_AND_2) &&
+ isis_circuit_lfa_enabled(circuit, IS_LEVEL_2))
+ isis_area_switchover_routes(circuit->area, family, nexthop_ip,
+ ifindex, IS_LEVEL_2);
+}
+
+void isis_circuit_stream(struct isis_circuit *circuit, struct stream **stream)
+{
+ size_t stream_size = isis_circuit_pdu_size(circuit);
+
+ if (!*stream) {
+ *stream = stream_new(stream_size);
+ } else {
+ if (STREAM_SIZE(*stream) != stream_size)
+ stream_resize_inplace(stream, stream_size);
+ stream_reset(*stream);
+ }
+}
+
+void isis_circuit_prepare(struct isis_circuit *circuit)
+{
+#if ISIS_METHOD != ISIS_METHOD_DLPI
+ event_add_read(master, isis_receive, circuit, circuit->fd,
+ &circuit->t_read);
+#else
+ event_add_timer_msec(master, isis_receive, circuit,
+ listcount(circuit->area->circuit_list) * 100,
+ &circuit->t_read);
+#endif
+}
+
+int isis_circuit_up(struct isis_circuit *circuit)
+{
+ int retv;
+
+ /* Set the flags for all the lsps of the circuit. */
+ isis_circuit_update_all_srmflags(circuit, 1);
+
+ if (circuit->state == C_STATE_UP)
+ return ISIS_OK;
+
+ if (circuit->is_passive) {
+ circuit->last_uptime = time(NULL);
+ /* make sure the union fields are initialized, else we
+ * could end with garbage values from a previous circuit
+ * type, which would then cause a segfault when building
+ * LSPs or computing the SPF tree
+ */
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ circuit->u.bc.adjdb[0] = list_new();
+ circuit->u.bc.adjdb[1] = list_new();
+ } else if (circuit->circ_type == CIRCUIT_T_P2P) {
+ circuit->u.p2p.neighbor = NULL;
+ }
+ return ISIS_OK;
+ }
+
+ if (circuit->area->lsp_mtu > isis_circuit_pdu_size(circuit)) {
+ flog_err(
+ EC_ISIS_CONFIG,
+ "Interface MTU %zu on %s is too low to support area lsp mtu %u!",
+ isis_circuit_pdu_size(circuit),
+ circuit->interface->name, circuit->area->lsp_mtu);
+
+ /* Allow ISIS to continue configuration. With this
+ * configuration failure ISIS will attempt to send lsp
+ * packets but will fail until the mtu is configured properly
+ */
+ }
+
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ circuit->circuit_id =
+ isis_circuit_id_gen(circuit->isis, circuit->interface);
+ if (!circuit->circuit_id) {
+ flog_err(
+ EC_ISIS_CONFIG,
+ "There are already 255 broadcast circuits active!");
+ return ISIS_ERROR;
+ }
+
+ /*
+ * Get the Hardware Address
+ */
+ if (circuit->interface->hw_addr_len != ETH_ALEN) {
+ zlog_warn("unsupported link layer");
+ } else {
+ memcpy(circuit->u.bc.snpa, circuit->interface->hw_addr,
+ ETH_ALEN);
+ }
+#ifdef EXTREME_DEGUG
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("%s: if_id %d, isomtu %d snpa %pSY",
+ __func__, circuit->interface->ifindex,
+ ISO_MTU(circuit), circuit->u.bc.snpa);
+#endif /* EXTREME_DEBUG */
+
+ circuit->u.bc.adjdb[0] = list_new();
+ circuit->u.bc.adjdb[1] = list_new();
+
+ /*
+ * ISO 10589 - 8.4.1 Enabling of broadcast circuits
+ */
+
+ /* initilizing the hello sending threads
+ * for a broadcast IF
+ */
+
+ /* 8.4.1 a) commence sending of IIH PDUs */
+
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+ if (!(circuit->is_type & level))
+ continue;
+
+ send_hello_sched(circuit, level, TRIGGERED_IIH_DELAY);
+ circuit->u.bc.lan_neighs[level - 1] = list_new();
+
+ event_add_timer(master, isis_run_dr,
+ &circuit->level_arg[level - 1],
+ 2 * circuit->hello_interval[level - 1],
+ &circuit->u.bc.t_run_dr[level - 1]);
+ }
+
+ /* 8.4.1 b) FIXME: solicit ES - 8.4.6 */
+ /* 8.4.1 c) FIXME: listen for ESH PDUs */
+ } else if (circuit->circ_type == CIRCUIT_T_P2P) {
+ /* initializing the hello send threads
+ * for a ptp IF
+ */
+ circuit->u.p2p.neighbor = NULL;
+ send_hello_sched(circuit, 0, TRIGGERED_IIH_DELAY);
+ }
+
+ /* initializing PSNP timers */
+ if (circuit->is_type & IS_LEVEL_1)
+ event_add_timer(
+ master, send_l1_psnp, circuit,
+ isis_jitter(circuit->psnp_interval[0], PSNP_JITTER),
+ &circuit->t_send_psnp[0]);
+
+ if (circuit->is_type & IS_LEVEL_2)
+ event_add_timer(
+ master, send_l2_psnp, circuit,
+ isis_jitter(circuit->psnp_interval[1], PSNP_JITTER),
+ &circuit->t_send_psnp[1]);
+
+ /* unified init for circuits; ignore warnings below this level */
+ retv = isis_sock_init(circuit);
+ if (retv != ISIS_OK) {
+ isis_circuit_down(circuit);
+ return retv;
+ }
+
+ /* initialize the circuit streams after opening connection */
+ isis_circuit_stream(circuit, &circuit->rcv_stream);
+ isis_circuit_stream(circuit, &circuit->snd_stream);
+
+ isis_circuit_prepare(circuit);
+
+ circuit->tx_queue = isis_tx_queue_new(circuit, send_lsp);
+
+ circuit->last_uptime = time(NULL);
+
+ if (circuit->area->mta && circuit->area->mta->status)
+ isis_link_params_update(circuit, circuit->interface);
+
+ isis_if_ldp_sync_enable(circuit);
+
+#ifndef FABRICD
+ /* send northbound notification */
+ isis_notif_if_state_change(circuit, false);
+#endif /* ifndef FABRICD */
+
+ return ISIS_OK;
+}
+
+void isis_circuit_down(struct isis_circuit *circuit)
+{
+#ifndef FABRICD
+ /* send northbound notification */
+ isis_notif_if_state_change(circuit, true);
+#endif /* ifndef FABRICD */
+
+ isis_if_ldp_sync_disable(circuit);
+
+ /* log adjacency changes if configured to do so */
+ if (circuit->area->log_adj_changes) {
+ struct isis_adjacency *adj = NULL;
+ if (circuit->circ_type == CIRCUIT_T_P2P) {
+ adj = circuit->u.p2p.neighbor;
+ if (adj)
+ isis_log_adj_change(
+ adj, adj->adj_state, ISIS_ADJ_DOWN,
+ "circuit is being brought down");
+ } else if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ struct list *adj_list;
+ struct listnode *node;
+ if (circuit->u.bc.adjdb[0]) {
+ adj_list = list_new();
+ isis_adj_build_up_list(circuit->u.bc.adjdb[0],
+ adj_list);
+ for (ALL_LIST_ELEMENTS_RO(adj_list, node, adj))
+ isis_log_adj_change(
+ adj, adj->adj_state,
+ ISIS_ADJ_DOWN,
+ "circuit is being brought down");
+ list_delete(&adj_list);
+ }
+ if (circuit->u.bc.adjdb[1]) {
+ adj_list = list_new();
+ isis_adj_build_up_list(circuit->u.bc.adjdb[1],
+ adj_list);
+ for (ALL_LIST_ELEMENTS_RO(adj_list, node, adj))
+ isis_log_adj_change(
+ adj, adj->adj_state,
+ ISIS_ADJ_DOWN,
+ "circuit is being brought down");
+ list_delete(&adj_list);
+ }
+ }
+ }
+
+ /* Clear the flags for all the lsps of the circuit. */
+ isis_circuit_update_all_srmflags(circuit, 0);
+
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ /* destroy neighbour lists */
+ if (circuit->u.bc.lan_neighs[0]) {
+ list_delete(&circuit->u.bc.lan_neighs[0]);
+ circuit->u.bc.lan_neighs[0] = NULL;
+ }
+ if (circuit->u.bc.lan_neighs[1]) {
+ list_delete(&circuit->u.bc.lan_neighs[1]);
+ circuit->u.bc.lan_neighs[1] = NULL;
+ }
+ /* destroy adjacency databases */
+ if (circuit->u.bc.adjdb[0]) {
+ circuit->u.bc.adjdb[0]->del = isis_delete_adj;
+ list_delete(&circuit->u.bc.adjdb[0]);
+ circuit->u.bc.adjdb[0] = NULL;
+ }
+ if (circuit->u.bc.adjdb[1]) {
+ circuit->u.bc.adjdb[1]->del = isis_delete_adj;
+ list_delete(&circuit->u.bc.adjdb[1]);
+ circuit->u.bc.adjdb[1] = NULL;
+ }
+ if (circuit->u.bc.is_dr[0]) {
+ isis_dr_resign(circuit, 1);
+ circuit->u.bc.is_dr[0] = 0;
+ }
+ memset(circuit->u.bc.l1_desig_is, 0, ISIS_SYS_ID_LEN + 1);
+ if (circuit->u.bc.is_dr[1]) {
+ isis_dr_resign(circuit, 2);
+ circuit->u.bc.is_dr[1] = 0;
+ }
+ memset(circuit->u.bc.l2_desig_is, 0, ISIS_SYS_ID_LEN + 1);
+ memset(circuit->u.bc.snpa, 0, ETH_ALEN);
+
+ EVENT_OFF(circuit->u.bc.t_send_lan_hello[0]);
+ EVENT_OFF(circuit->u.bc.t_send_lan_hello[1]);
+ EVENT_OFF(circuit->u.bc.t_run_dr[0]);
+ EVENT_OFF(circuit->u.bc.t_run_dr[1]);
+ EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[0]);
+ EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[1]);
+ circuit->lsp_regenerate_pending[0] = 0;
+ circuit->lsp_regenerate_pending[1] = 0;
+
+ _ISIS_CLEAR_FLAG(circuit->isis->circuit_ids_used,
+ circuit->circuit_id);
+ circuit->circuit_id = 0;
+ } else if (circuit->circ_type == CIRCUIT_T_P2P) {
+ isis_delete_adj(circuit->u.p2p.neighbor);
+ circuit->u.p2p.neighbor = NULL;
+ EVENT_OFF(circuit->u.p2p.t_send_p2p_hello);
+ }
+
+ /*
+ * All adjacencies have to be gone, delete snmp list
+ * and reset snmpd idx generator
+ */
+ if (circuit->snmp_adj_list != NULL)
+ list_delete(&circuit->snmp_adj_list);
+
+ circuit->snmp_adj_idx_gen = 0;
+
+ /* Cancel all active threads */
+ EVENT_OFF(circuit->t_send_csnp[0]);
+ EVENT_OFF(circuit->t_send_csnp[1]);
+ EVENT_OFF(circuit->t_send_psnp[0]);
+ EVENT_OFF(circuit->t_send_psnp[1]);
+ EVENT_OFF(circuit->t_read);
+
+ if (circuit->tx_queue) {
+ isis_tx_queue_free(circuit->tx_queue);
+ circuit->tx_queue = NULL;
+ }
+
+ /* send one gratuitous hello to spead up convergence */
+ if (circuit->state == C_STATE_UP) {
+ if (circuit->is_type & IS_LEVEL_1)
+ send_hello(circuit, IS_LEVEL_1);
+ if (circuit->is_type & IS_LEVEL_2)
+ send_hello(circuit, IS_LEVEL_2);
+ }
+
+ circuit->upadjcount[0] = 0;
+ circuit->upadjcount[1] = 0;
+
+ /* close the socket */
+ if (circuit->fd) {
+ close(circuit->fd);
+ circuit->fd = 0;
+ }
+
+ if (circuit->rcv_stream != NULL) {
+ stream_free(circuit->rcv_stream);
+ circuit->rcv_stream = NULL;
+ }
+
+ if (circuit->snd_stream != NULL) {
+ stream_free(circuit->snd_stream);
+ circuit->snd_stream = NULL;
+ }
+
+ event_cancel_event(master, circuit);
+
+ return;
+}
+
+void circuit_update_nlpids(struct isis_circuit *circuit)
+{
+ circuit->nlpids.count = 0;
+
+ if (circuit->ip_router) {
+ circuit->nlpids.nlpids[0] = NLPID_IP;
+ circuit->nlpids.count++;
+ }
+ if (circuit->ipv6_router) {
+ circuit->nlpids.nlpids[circuit->nlpids.count] = NLPID_IPV6;
+ circuit->nlpids.count++;
+ }
+ return;
+}
+
+void isis_circuit_print_json(struct isis_circuit *circuit,
+ struct json_object *json, char detail)
+{
+ int level;
+ json_object *iface_json, *ipv4_addr_json, *ipv6_link_json,
+ *ipv6_non_link_json, *hold_json, *lan_prio_json, *levels_json,
+ *level_json;
+ char buf_prx[INET6_BUFSIZ];
+ char buf[255];
+
+ snprintfrr(buf, sizeof(buf), "0x%x", circuit->circuit_id);
+ if (detail == ISIS_UI_LEVEL_BRIEF) {
+ iface_json = json_object_new_object();
+ json_object_object_add(json, "interface", iface_json);
+ json_object_string_add(iface_json, "name",
+ circuit->interface->name);
+ json_object_string_add(iface_json, "circuit-id", buf);
+ json_object_string_add(iface_json, "state",
+ circuit_state2string(circuit->state));
+ json_object_string_add(iface_json, "type",
+ circuit_type2string(circuit->circ_type));
+ json_object_string_add(iface_json, "level",
+ circuit_t2string(circuit->is_type));
+ }
+
+ if (detail == ISIS_UI_LEVEL_DETAIL) {
+ struct listnode *node;
+ struct prefix *ip_addr;
+
+ iface_json = json_object_new_object();
+ json_object_object_add(json, "interface", iface_json);
+ json_object_string_add(iface_json, "name",
+ circuit->interface->name);
+ json_object_string_add(iface_json, "state",
+ circuit_state2string(circuit->state));
+ if (circuit->is_passive)
+ json_object_string_add(iface_json, "is-passive",
+ "passive");
+ else
+ json_object_string_add(iface_json, "is-passive",
+ "active");
+ json_object_string_add(iface_json, "circuit-id", buf);
+ json_object_string_add(iface_json, "type",
+ circuit_type2string(circuit->circ_type));
+ json_object_string_add(iface_json, "level",
+ circuit_t2string(circuit->is_type));
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST)
+ json_object_string_addf(iface_json, "snpa", "%pSY",
+ circuit->u.bc.snpa);
+
+
+ levels_json = json_object_new_array();
+ json_object_object_add(iface_json, "levels", levels_json);
+ for (level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) {
+ if ((circuit->is_type & level) == 0)
+ continue;
+ level_json = json_object_new_object();
+ json_object_string_add(level_json, "level",
+ circuit_t2string(level));
+ if (circuit->area->newmetric)
+ json_object_int_add(level_json, "metric",
+ circuit->te_metric[0]);
+ else
+ json_object_int_add(level_json, "metric",
+ circuit->metric[0]);
+ if (!circuit->is_passive) {
+ json_object_int_add(level_json,
+ "active-neighbors",
+ circuit->upadjcount[0]);
+ json_object_int_add(level_json,
+ "hello-interval",
+ circuit->hello_interval[0]);
+ hold_json = json_object_new_object();
+ json_object_object_add(level_json, "holddown",
+ hold_json);
+ json_object_int_add(
+ hold_json, "count",
+ circuit->hello_multiplier[0]);
+ json_object_string_add(
+ hold_json, "pad",
+ isis_hello_padding2string(
+ circuit->pad_hellos));
+ json_object_int_add(level_json, "cnsp-interval",
+ circuit->csnp_interval[0]);
+ json_object_int_add(level_json, "psnp-interval",
+ circuit->psnp_interval[0]);
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ lan_prio_json =
+ json_object_new_object();
+ json_object_object_add(level_json,
+ "lan",
+ lan_prio_json);
+ json_object_int_add(
+ lan_prio_json, "priority",
+ circuit->priority[0]);
+ json_object_string_add(
+ lan_prio_json, "is-dis",
+ (circuit->u.bc.is_dr[0]
+ ? "yes"
+ : "no"));
+ }
+ }
+ json_object_array_add(levels_json, level_json);
+ }
+
+ if (listcount(circuit->ip_addrs) > 0) {
+ ipv4_addr_json = json_object_new_object();
+ json_object_object_add(iface_json, "ip-prefix",
+ ipv4_addr_json);
+ for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node,
+ ip_addr)) {
+ snprintfrr(buf_prx, INET6_BUFSIZ, "%pFX",
+ ip_addr);
+ json_object_string_add(ipv4_addr_json, "ip",
+ buf_prx);
+ }
+ }
+ if (listcount(circuit->ipv6_link) > 0) {
+ ipv6_link_json = json_object_new_object();
+ json_object_object_add(iface_json, "ipv6-link-locals",
+ ipv6_link_json);
+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node,
+ ip_addr)) {
+ snprintfrr(buf_prx, INET6_BUFSIZ, "%pFX",
+ ip_addr);
+ json_object_string_add(ipv6_link_json, "ipv6",
+ buf_prx);
+ }
+ }
+ if (listcount(circuit->ipv6_non_link) > 0) {
+ ipv6_non_link_json = json_object_new_object();
+ json_object_object_add(iface_json, "ipv6-prefixes",
+ ipv6_non_link_json);
+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node,
+ ip_addr)) {
+ snprintfrr(buf_prx, INET6_BUFSIZ, "%pFX",
+ ip_addr);
+ json_object_string_add(ipv6_non_link_json,
+ "ipv6", buf_prx);
+ }
+ }
+ }
+ return;
+}
+
+void isis_circuit_print_vty(struct isis_circuit *circuit, struct vty *vty,
+ char detail)
+{
+ if (detail == ISIS_UI_LEVEL_BRIEF) {
+ vty_out(vty, " %-12s", circuit->interface->name);
+ vty_out(vty, "0x%-7x", circuit->circuit_id);
+ vty_out(vty, "%-9s", circuit_state2string(circuit->state));
+ vty_out(vty, "%-9s", circuit_type2string(circuit->circ_type));
+ vty_out(vty, "%-9s", circuit_t2string(circuit->is_type));
+ vty_out(vty, "\n");
+ }
+
+ if (detail == ISIS_UI_LEVEL_DETAIL) {
+ struct listnode *node;
+ struct prefix *ip_addr;
+
+ vty_out(vty, " Interface: %s", circuit->interface->name);
+ vty_out(vty, ", State: %s",
+ circuit_state2string(circuit->state));
+ if (circuit->is_passive)
+ vty_out(vty, ", Passive");
+ else
+ vty_out(vty, ", Active");
+ vty_out(vty, ", Circuit Id: 0x%x", circuit->circuit_id);
+ vty_out(vty, "\n");
+ vty_out(vty, " Type: %s",
+ circuit_type2string(circuit->circ_type));
+ vty_out(vty, ", Level: %s", circuit_t2string(circuit->is_type));
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST)
+ vty_out(vty, ", SNPA: %-10pSY", circuit->u.bc.snpa);
+ vty_out(vty, "\n");
+ if (circuit->is_type & IS_LEVEL_1) {
+ vty_out(vty, " Level-1 Information:\n");
+ if (circuit->area->newmetric)
+ vty_out(vty, " Metric: %d",
+ circuit->te_metric[0]);
+ else
+ vty_out(vty, " Metric: %d",
+ circuit->metric[0]);
+ if (!circuit->is_passive) {
+ vty_out(vty, ", Active neighbors: %u\n",
+ circuit->upadjcount[0]);
+ vty_out(vty,
+ " Hello interval: %u, Holddown count: %u, Padding: %s\n",
+ circuit->hello_interval[0],
+ circuit->hello_multiplier[0],
+ isis_hello_padding2string(
+ circuit->pad_hellos));
+ vty_out(vty,
+ " CNSP interval: %u, PSNP interval: %u\n",
+ circuit->csnp_interval[0],
+ circuit->psnp_interval[0]);
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST)
+ vty_out(vty,
+ " LAN Priority: %u, %s\n",
+ circuit->priority[0],
+ (circuit->u.bc.is_dr[0]
+ ? "is DIS"
+ : "is not DIS"));
+ } else {
+ vty_out(vty, "\n");
+ }
+ }
+ if (circuit->is_type & IS_LEVEL_2) {
+ vty_out(vty, " Level-2 Information:\n");
+ if (circuit->area->newmetric)
+ vty_out(vty, " Metric: %d",
+ circuit->te_metric[1]);
+ else
+ vty_out(vty, " Metric: %d",
+ circuit->metric[1]);
+ if (!circuit->is_passive) {
+ vty_out(vty, ", Active neighbors: %u\n",
+ circuit->upadjcount[1]);
+ vty_out(vty,
+ " Hello interval: %u, Holddown count: %u, Padding: %s\n",
+ circuit->hello_interval[1],
+ circuit->hello_multiplier[1],
+ isis_hello_padding2string(
+ circuit->pad_hellos));
+ vty_out(vty,
+ " CNSP interval: %u, PSNP interval: %u\n",
+ circuit->csnp_interval[1],
+ circuit->psnp_interval[1]);
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST)
+ vty_out(vty,
+ " LAN Priority: %u, %s\n",
+ circuit->priority[1],
+ (circuit->u.bc.is_dr[1]
+ ? "is DIS"
+ : "is not DIS"));
+ } else {
+ vty_out(vty, "\n");
+ }
+ }
+ if (listcount(circuit->ip_addrs) > 0) {
+ vty_out(vty, " IP Prefix(es):\n");
+ for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node,
+ ip_addr))
+ vty_out(vty, " %pFX\n", ip_addr);
+ }
+ if (listcount(circuit->ipv6_link) > 0) {
+ vty_out(vty, " IPv6 Link-Locals:\n");
+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node,
+ ip_addr))
+ vty_out(vty, " %pFX\n", ip_addr);
+ }
+ if (listcount(circuit->ipv6_non_link) > 0) {
+ vty_out(vty, " IPv6 Prefixes:\n");
+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node,
+ ip_addr))
+ vty_out(vty, " %pFX\n", ip_addr);
+ }
+
+ vty_out(vty, "\n");
+ }
+ return;
+}
+
+#ifdef FABRICD
+DEFINE_HOOK(isis_circuit_config_write,
+ (struct isis_circuit *circuit, struct vty *vty),
+ (circuit, vty));
+
+static int isis_interface_config_write(struct vty *vty)
+{
+ struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT);
+ int write = 0;
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+ int i;
+
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ /* IF name */
+ if_vty_config_start(vty, ifp);
+ write++;
+ /* IF desc */
+ if (ifp->desc) {
+ vty_out(vty, " description %s\n", ifp->desc);
+ write++;
+ }
+ /* ISIS Circuit */
+ do {
+ circuit = circuit_scan_by_ifp(ifp);
+ if (circuit == NULL)
+ break;
+ if (circuit->ip_router) {
+ vty_out(vty, " ip router " PROTO_NAME " %s\n",
+ circuit->tag);
+ write++;
+ }
+ if (circuit->is_passive) {
+ vty_out(vty, " " PROTO_NAME " passive\n");
+ write++;
+ }
+ if (circuit->circ_type_config == CIRCUIT_T_P2P) {
+ vty_out(vty, " " PROTO_NAME " network point-to-point\n");
+ write++;
+ }
+ if (circuit->ipv6_router) {
+ vty_out(vty, " ipv6 router " PROTO_NAME " %s\n",
+ circuit->tag);
+ write++;
+ }
+
+ /* ISIS - circuit type */
+ if (!fabricd) {
+ if (circuit->is_type == IS_LEVEL_1) {
+ vty_out(vty, " " PROTO_NAME " circuit-type level-1\n");
+ write++;
+ } else {
+ if (circuit->is_type == IS_LEVEL_2) {
+ vty_out(vty,
+ " " PROTO_NAME " circuit-type level-2-only\n");
+ write++;
+ }
+ }
+ }
+
+ /* ISIS - CSNP interval */
+ if (circuit->csnp_interval[0]
+ == circuit->csnp_interval[1]) {
+ if (circuit->csnp_interval[0]
+ != DEFAULT_CSNP_INTERVAL) {
+ vty_out(vty, " " PROTO_NAME " csnp-interval %d\n",
+ circuit->csnp_interval[0]);
+ write++;
+ }
+ } else {
+ for (i = 0; i < 2; i++) {
+ if (circuit->csnp_interval[i]
+ != DEFAULT_CSNP_INTERVAL) {
+ vty_out(vty,
+ " " PROTO_NAME " csnp-interval %d level-%d\n",
+ circuit->csnp_interval
+ [i],
+ i + 1);
+ write++;
+ }
+ }
+ }
+
+ /* ISIS - PSNP interval */
+ if (circuit->psnp_interval[0]
+ == circuit->psnp_interval[1]) {
+ if (circuit->psnp_interval[0]
+ != DEFAULT_PSNP_INTERVAL) {
+ vty_out(vty, " " PROTO_NAME " psnp-interval %d\n",
+ circuit->psnp_interval[0]);
+ write++;
+ }
+ } else {
+ for (i = 0; i < 2; i++) {
+ if (circuit->psnp_interval[i]
+ != DEFAULT_PSNP_INTERVAL) {
+ vty_out(vty,
+ " " PROTO_NAME " psnp-interval %d level-%d\n",
+ circuit->psnp_interval
+ [i],
+ i + 1);
+ write++;
+ }
+ }
+ }
+
+ /* ISIS - Hello padding - Defaults to always so only
+ * display if not always */
+ switch (circuit->pad_hellos) {
+ case ISIS_HELLO_PADDING_DISABLED:
+ vty_out(vty, " no " PROTO_NAME " hello padding\n");
+ write++;
+ break;
+ case ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION:
+ vty_out(vty, PROTO_NAME
+ " hello padding during-adjacency-formation\n");
+ write++;
+ break;
+ case ISIS_HELLO_PADDING_ALWAYS:
+ break;
+ }
+
+ if (circuit->disable_threeway_adj) {
+ vty_out(vty, " no isis three-way-handshake\n");
+ write++;
+ }
+
+ /* ISIS - Hello interval */
+ if (circuit->hello_interval[0]
+ == circuit->hello_interval[1]) {
+ if (circuit->hello_interval[0]
+ != DEFAULT_HELLO_INTERVAL) {
+ vty_out(vty,
+ " " PROTO_NAME " hello-interval %d\n",
+ circuit->hello_interval[0]);
+ write++;
+ }
+ } else {
+ for (i = 0; i < 2; i++) {
+ if (circuit->hello_interval[i]
+ != DEFAULT_HELLO_INTERVAL) {
+ vty_out(vty,
+ " " PROTO_NAME " hello-interval %d level-%d\n",
+ circuit->hello_interval
+ [i],
+ i + 1);
+ write++;
+ }
+ }
+ }
+
+ /* ISIS - Hello Multiplier */
+ if (circuit->hello_multiplier[0]
+ == circuit->hello_multiplier[1]) {
+ if (circuit->hello_multiplier[0]
+ != DEFAULT_HELLO_MULTIPLIER) {
+ vty_out(vty,
+ " " PROTO_NAME " hello-multiplier %d\n",
+ circuit->hello_multiplier[0]);
+ write++;
+ }
+ } else {
+ for (i = 0; i < 2; i++) {
+ if (circuit->hello_multiplier[i]
+ != DEFAULT_HELLO_MULTIPLIER) {
+ vty_out(vty,
+ " " PROTO_NAME " hello-multiplier %d level-%d\n",
+ circuit->hello_multiplier
+ [i],
+ i + 1);
+ write++;
+ }
+ }
+ }
+
+ /* ISIS - Priority */
+ if (circuit->priority[0] == circuit->priority[1]) {
+ if (circuit->priority[0] != DEFAULT_PRIORITY) {
+ vty_out(vty, " " PROTO_NAME " priority %d\n",
+ circuit->priority[0]);
+ write++;
+ }
+ } else {
+ for (i = 0; i < 2; i++) {
+ if (circuit->priority[i]
+ != DEFAULT_PRIORITY) {
+ vty_out(vty,
+ " " PROTO_NAME " priority %d level-%d\n",
+ circuit->priority[i],
+ i + 1);
+ write++;
+ }
+ }
+ }
+
+ /* ISIS - Metric */
+ if (circuit->te_metric[0] == circuit->te_metric[1]) {
+ if (circuit->te_metric[0]
+ != DEFAULT_CIRCUIT_METRIC) {
+ vty_out(vty, " " PROTO_NAME " metric %d\n",
+ circuit->te_metric[0]);
+ write++;
+ }
+ } else {
+ for (i = 0; i < 2; i++) {
+ if (circuit->te_metric[i]
+ != DEFAULT_CIRCUIT_METRIC) {
+ vty_out(vty,
+ " " PROTO_NAME " metric %d level-%d\n",
+ circuit->te_metric[i],
+ i + 1);
+ write++;
+ }
+ }
+ }
+ if (circuit->passwd.type == ISIS_PASSWD_TYPE_HMAC_MD5) {
+ vty_out(vty, " " PROTO_NAME " password md5 %s\n",
+ circuit->passwd.passwd);
+ write++;
+ } else if (circuit->passwd.type
+ == ISIS_PASSWD_TYPE_CLEARTXT) {
+ vty_out(vty, " " PROTO_NAME " password clear %s\n",
+ circuit->passwd.passwd);
+ write++;
+ }
+ if (circuit->bfd_config.enabled) {
+ vty_out(vty, " " PROTO_NAME " bfd\n");
+ write++;
+ }
+ write += hook_call(isis_circuit_config_write,
+ circuit, vty);
+ } while (0);
+ if_vty_config_end(vty);
+ }
+
+ return write;
+}
+#endif /* ifdef FABRICD */
+
+void isis_circuit_af_set(struct isis_circuit *circuit, bool ip_router,
+ bool ipv6_router)
+{
+ struct isis_area *area = circuit->area;
+ int old_ipr = circuit->ip_router;
+ int old_ipv6r = circuit->ipv6_router;
+
+ /* is there something to do? */
+ if (old_ipr == ip_router && old_ipv6r == ipv6_router)
+ return;
+
+ circuit->ip_router = ip_router;
+ circuit->ipv6_router = ipv6_router;
+ circuit_update_nlpids(circuit);
+
+ if (area) {
+ area->ip_circuits += ip_router - old_ipr;
+ area->ipv6_circuits += ipv6_router - old_ipv6r;
+
+ if (ip_router || ipv6_router)
+ lsp_regenerate_schedule(area, circuit->is_type, 0);
+ }
+}
+
+ferr_r isis_circuit_passive_set(struct isis_circuit *circuit, bool passive)
+{
+ if (circuit->is_passive == passive)
+ return ferr_ok();
+
+ if (if_is_loopback(circuit->interface) && !passive)
+ return ferr_cfg_invalid("loopback is always passive");
+
+ if (circuit->state != C_STATE_UP) {
+ circuit->is_passive = passive;
+ } else {
+ struct isis_area *area = circuit->area;
+ isis_csm_state_change(ISIS_DISABLE, circuit, area);
+ circuit->is_passive = passive;
+ isis_csm_state_change(ISIS_ENABLE, circuit, area);
+ }
+
+ return ferr_ok();
+}
+
+ferr_r isis_circuit_metric_set(struct isis_circuit *circuit, int level,
+ int metric)
+{
+ assert(level == IS_LEVEL_1 || level == IS_LEVEL_2);
+ if (metric > MAX_WIDE_LINK_METRIC)
+ return ferr_cfg_invalid("metric %d too large for wide metric",
+ metric);
+ if (circuit->area && circuit->area->oldmetric
+ && metric > MAX_NARROW_LINK_METRIC)
+ return ferr_cfg_invalid("metric %d too large for narrow metric",
+ metric);
+
+ /* Don't modify metric if advertise high metrics is configured */
+ if (circuit->area && circuit->area->advertise_high_metrics)
+ return ferr_ok();
+
+ /* inform ldp-sync of metric change
+ * if ldp-sync is running need to save metric
+ * and restore new values after ldp-sync completion.
+ */
+ if (isis_ldp_sync_if_metric_config(circuit, level, metric)) {
+ circuit->te_metric[level - 1] = metric;
+ circuit->metric[level - 1] = metric;
+ if (circuit->area)
+ lsp_regenerate_schedule(circuit->area, level, 0);
+ }
+ return ferr_ok();
+}
+
+ferr_r isis_circuit_passwd_unset(struct isis_circuit *circuit)
+{
+ memset(&circuit->passwd, 0, sizeof(circuit->passwd));
+ return ferr_ok();
+}
+
+ferr_r isis_circuit_passwd_set(struct isis_circuit *circuit,
+ uint8_t passwd_type, const char *passwd)
+{
+ int len;
+
+ if (!passwd)
+ return ferr_code_bug("no circuit password given");
+
+ len = strlen(passwd);
+ if (len > 254)
+ return ferr_code_bug(
+ "circuit password too long (max 254 chars)");
+
+ circuit->passwd.len = len;
+ strlcpy((char *)circuit->passwd.passwd, passwd,
+ sizeof(circuit->passwd.passwd));
+ circuit->passwd.type = passwd_type;
+ return ferr_ok();
+}
+
+ferr_r isis_circuit_passwd_cleartext_set(struct isis_circuit *circuit,
+ const char *passwd)
+{
+ return isis_circuit_passwd_set(circuit, ISIS_PASSWD_TYPE_CLEARTXT,
+ passwd);
+}
+
+ferr_r isis_circuit_passwd_hmac_md5_set(struct isis_circuit *circuit,
+ const char *passwd)
+{
+ return isis_circuit_passwd_set(circuit, ISIS_PASSWD_TYPE_HMAC_MD5,
+ passwd);
+}
+
+void isis_circuit_circ_type_set(struct isis_circuit *circuit, int circ_type)
+{
+ if (circuit->circ_type == circ_type)
+ return;
+
+ if (circuit->state != C_STATE_UP) {
+ circuit->circ_type = circ_type;
+ circuit->circ_type_config = circ_type;
+ } else {
+ struct isis_area *area = circuit->area;
+
+ isis_csm_state_change(ISIS_DISABLE, circuit, area);
+ circuit->circ_type = circ_type;
+ circuit->circ_type_config = circ_type;
+ isis_csm_state_change(ISIS_ENABLE, circuit, area);
+ }
+}
+
+int isis_circuit_mt_enabled_set(struct isis_circuit *circuit, uint16_t mtid,
+ bool enabled)
+{
+ struct isis_circuit_mt_setting *setting;
+
+ setting = circuit_get_mt_setting(circuit, mtid);
+ if (setting->enabled != enabled) {
+ setting->enabled = enabled;
+ if (circuit->area)
+ lsp_regenerate_schedule(circuit->area,
+ IS_LEVEL_1 | IS_LEVEL_2, 0);
+ }
+
+ return CMD_SUCCESS;
+}
+
+int isis_if_new_hook(struct interface *ifp)
+{
+ return 0;
+}
+
+int isis_if_delete_hook(struct interface *ifp)
+{
+ if (ifp->info)
+ isis_circuit_del(ifp->info);
+
+ return 0;
+}
+
+static int isis_ifp_create(struct interface *ifp)
+{
+ struct isis_circuit *circuit = ifp->info;
+
+ if (circuit)
+ isis_circuit_enable(circuit);
+
+ hook_call(isis_if_new_hook, ifp);
+
+ return 0;
+}
+
+static int isis_ifp_up(struct interface *ifp)
+{
+ struct isis_circuit *circuit = ifp->info;
+
+ if (circuit) {
+ UNSET_FLAG(circuit->flags, ISIS_CIRCUIT_IF_DOWN_FROM_Z);
+ isis_csm_state_change(IF_UP_FROM_Z, circuit, ifp);
+ }
+
+ /* Notify SRv6 that the interface went up */
+ isis_srv6_ifp_up_notify(ifp);
+
+ return 0;
+}
+
+static int isis_ifp_down(struct interface *ifp)
+{
+ afi_t afi;
+ struct isis_circuit *circuit = ifp->info;
+
+ if (circuit &&
+ !CHECK_FLAG(circuit->flags, ISIS_CIRCUIT_IF_DOWN_FROM_Z)) {
+ SET_FLAG(circuit->flags, ISIS_CIRCUIT_IF_DOWN_FROM_Z);
+ for (afi = AFI_IP; afi <= AFI_IP6; afi++)
+ isis_circuit_switchover_routes(
+ circuit, afi == AFI_IP ? AF_INET : AF_INET6,
+ NULL, ifp->ifindex);
+ isis_csm_state_change(IF_DOWN_FROM_Z, circuit, ifp);
+
+ SET_FLAG(circuit->flags, ISIS_CIRCUIT_FLAPPED_AFTER_SPF);
+ }
+
+ return 0;
+}
+
+static int isis_ifp_destroy(struct interface *ifp)
+{
+ struct isis_circuit *circuit = ifp->info;
+
+ if (circuit)
+ isis_circuit_disable(circuit);
+
+ return 0;
+}
+
+void isis_circuit_init(void)
+{
+ /* Initialize Zebra interface data structure */
+ hook_register_prio(if_add, 0, isis_if_new_hook);
+ hook_register_prio(if_del, 0, isis_if_delete_hook);
+
+ /* Install interface node */
+#ifdef FABRICD
+ if_cmd_init(isis_interface_config_write);
+#else
+ if_cmd_init_default();
+#endif
+ if_zapi_callbacks(isis_ifp_create, isis_ifp_up,
+ isis_ifp_down, isis_ifp_destroy);
+}
diff --git a/isisd/isis_circuit.h b/isisd/isis_circuit.h
new file mode 100644
index 0000000..7acde41
--- /dev/null
+++ b/isisd/isis_circuit.h
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_circuit.h
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#ifndef ISIS_CIRCUIT_H
+#define ISIS_CIRCUIT_H
+
+#include "vty.h"
+#include "if.h"
+#include "qobj.h"
+#include "prefix.h"
+#include "ferr.h"
+#include "nexthop.h"
+
+#include "isis_constants.h"
+#include "isis_common.h"
+#include "isis_csm.h"
+
+DECLARE_HOOK(isis_if_new_hook, (struct interface *ifp), (ifp));
+
+struct isis_lsp;
+
+struct password {
+ struct password *next;
+ int len;
+ uint8_t *pass;
+};
+
+struct metric {
+ uint8_t metric_default;
+ uint8_t metric_error;
+ uint8_t metric_expense;
+ uint8_t metric_delay;
+};
+
+struct isis_bcast_info {
+ uint8_t snpa[ETH_ALEN]; /* SNPA of this circuit */
+ char run_dr_elect[ISIS_LEVELS]; /* Should we run dr election ? */
+ struct event *t_run_dr[ISIS_LEVELS]; /* DR election thread */
+ struct event *t_send_lan_hello[ISIS_LEVELS]; /* send LAN IIHs in this
+ thread */
+ struct list *adjdb[ISIS_LEVELS]; /* adjacency dbs */
+ struct list *lan_neighs[ISIS_LEVELS]; /* list of lx neigh snpa */
+ char is_dr[ISIS_LEVELS]; /* Are we level x DR ? */
+ uint8_t l1_desig_is[ISIS_SYS_ID_LEN + 1]; /* level-1 DR */
+ uint8_t l2_desig_is[ISIS_SYS_ID_LEN + 1]; /* level-2 DR */
+ struct event *t_refresh_pseudo_lsp[ISIS_LEVELS]; /* refresh pseudo-node
+ LSPs */
+};
+
+struct isis_p2p_info {
+ struct isis_adjacency *neighbor;
+ struct event *t_send_p2p_hello; /* send P2P IIHs in this thread */
+};
+
+struct isis_circuit_arg {
+ int level;
+ struct isis_circuit *circuit;
+};
+
+/*
+ * Hello padding types
+ */
+enum isis_hello_padding {
+ ISIS_HELLO_PADDING_ALWAYS,
+ ISIS_HELLO_PADDING_DISABLED,
+ ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION
+};
+
+struct isis_circuit {
+ enum isis_circuit_state state;
+ uint8_t circuit_id; /* l1/l2 bcast CircuitID */
+ time_t last_uptime;
+ struct isis *isis;
+ struct isis_area *area; /* back pointer to the area */
+ struct interface *interface; /* interface info from z */
+ int fd; /* IS-IS l1/2 socket */
+ int sap_length; /* SAP length for DLPI */
+ struct nlpids nlpids;
+ /*
+ * Threads
+ */
+ struct event *t_read;
+ struct event *t_send_csnp[ISIS_LEVELS];
+ struct event *t_send_psnp[ISIS_LEVELS];
+ struct isis_tx_queue *tx_queue;
+ struct isis_circuit_arg
+ level_arg[ISIS_LEVELS]; /* used as argument for threads */
+
+ /* there is no real point in two streams, just for programming kicker */
+ int (*rx)(struct isis_circuit *circuit, uint8_t *ssnpa);
+ struct stream *rcv_stream; /* Stream for receiving */
+ int (*tx)(struct isis_circuit *circuit, int level);
+ struct stream *snd_stream; /* Stream for sending */
+ int idx; /* idx in S[RM|SN] flags */
+#define CIRCUIT_T_UNKNOWN 0
+#define CIRCUIT_T_BROADCAST 1
+#define CIRCUIT_T_P2P 2
+#define CIRCUIT_T_LOOPBACK 3
+ int circ_type; /* type of the physical interface */
+ int circ_type_config; /* config type of the physical interface */
+ union {
+ struct isis_bcast_info bc;
+ struct isis_p2p_info p2p;
+ } u;
+ uint8_t priority[ISIS_LEVELS]; /* l1/2 IS configured priority */
+ enum isis_hello_padding pad_hellos; /* type of Hello PDUs padding */
+ char ext_domain; /* externalDomain (boolean) */
+ int lsp_regenerate_pending[ISIS_LEVELS];
+ uint64_t lsp_error_counter;
+
+ /*
+ * Configurables
+ */
+ char *tag; /* area tag */
+ struct isis_passwd passwd; /* Circuit rx/tx password */
+ int is_type_config; /* configured circuit is type */
+ int is_type; /* circuit is type == level of circuit
+ * differentiated from circuit type (media) */
+ uint32_t hello_interval[ISIS_LEVELS]; /* hello-interval in seconds */
+ uint16_t hello_multiplier[ISIS_LEVELS]; /* hello-multiplier */
+ uint16_t csnp_interval[ISIS_LEVELS]; /* csnp-interval in seconds */
+ uint16_t psnp_interval[ISIS_LEVELS]; /* psnp-interval in seconds */
+ uint8_t metric[ISIS_LEVELS];
+ uint32_t te_metric[ISIS_LEVELS];
+ struct isis_ext_subtlvs *ext; /* Extended parameters (TE + Adj SID */
+ int ip_router; /* Route IP ? */
+ int is_passive; /* Is Passive ? */
+ struct list *mt_settings; /* IS-IS MT Settings */
+ struct list *ip_addrs; /* our IP addresses */
+ int ipv6_router; /* Route IPv6 ? */
+ struct list *ipv6_link; /* our link local IPv6 addresses */
+ struct list *ipv6_non_link; /* our non-link local IPv6 addresses */
+ uint16_t upadjcount[ISIS_LEVELS];
+#define ISIS_CIRCUIT_FLAPPED_AFTER_SPF 0x01
+#define ISIS_CIRCUIT_IF_DOWN_FROM_Z 0x02
+ uint8_t flags;
+ bool disable_threeway_adj;
+ struct {
+ bool enabled;
+ char *profile;
+ } bfd_config;
+ struct ldp_sync_info *ldp_sync_info;
+ bool lfa_protection[ISIS_LEVELS];
+ bool rlfa_protection[ISIS_LEVELS];
+ uint32_t rlfa_max_metric[ISIS_LEVELS];
+ struct hash *lfa_excluded_ifaces[ISIS_LEVELS];
+ bool tilfa_protection[ISIS_LEVELS];
+ bool tilfa_node_protection[ISIS_LEVELS];
+ bool tilfa_link_fallback[ISIS_LEVELS];
+ /*
+ * Counters as in 10589--11.2.5.9
+ */
+ uint32_t adj_state_changes; /* changesInAdjacencyState */
+ uint32_t init_failures; /* intialisationFailures */
+ uint32_t ctrl_pdus_rxed; /* controlPDUsReceived */
+ uint32_t ctrl_pdus_txed; /* controlPDUsSent */
+ uint32_t desig_changes[ISIS_LEVELS]; /* lanLxDesignatedIntermediateSystemChanges
+ */
+ uint32_t rej_adjacencies; /* rejectedAdjacencies */
+ /*
+ * Counters as in ietf-isis@2019-09-09.yang
+ */
+ uint32_t id_len_mismatches; /* id-len-mismatch */
+ uint32_t max_area_addr_mismatches; /* max-area-addresses-mismatch */
+ uint32_t auth_type_failures; /*authentication-type-fails */
+ uint32_t auth_failures; /* authentication-fails */
+
+ uint32_t snmp_id; /* Circuit id in snmp */
+
+ uint32_t snmp_adj_idx_gen; /* Create unique id for adjacency on creation
+ */
+ struct list *snmp_adj_list; /* List in id order */
+
+ QOBJ_FIELDS;
+};
+DECLARE_QOBJ_TYPE(isis_circuit);
+
+void isis_circuit_init(void);
+struct isis_circuit *isis_circuit_new(struct interface *ifp, const char *tag);
+void isis_circuit_del(struct isis_circuit *circuit);
+struct isis_circuit *circuit_scan_by_ifp(struct interface *ifp);
+void isis_circuit_configure(struct isis_circuit *circuit,
+ struct isis_area *area);
+void isis_circuit_deconfigure(struct isis_circuit *circuit,
+ struct isis_area *area);
+void isis_circuit_if_add(struct isis_circuit *circuit, struct interface *ifp);
+void isis_circuit_if_del(struct isis_circuit *circuit, struct interface *ifp);
+void isis_circuit_if_bind(struct isis_circuit *circuit, struct interface *ifp);
+void isis_circuit_if_unbind(struct isis_circuit *circuit,
+ struct interface *ifp);
+void isis_circuit_add_addr(struct isis_circuit *circuit,
+ struct connected *conn);
+void isis_circuit_del_addr(struct isis_circuit *circuit,
+ struct connected *conn);
+void isis_circuit_prepare(struct isis_circuit *circuit);
+int isis_circuit_up(struct isis_circuit *circuit);
+void isis_circuit_down(struct isis_circuit *);
+void circuit_update_nlpids(struct isis_circuit *circuit);
+void isis_circuit_print_vty(struct isis_circuit *circuit, struct vty *vty,
+ char detail);
+void isis_circuit_print_json(struct isis_circuit *circuit,
+ struct json_object *json, char detail);
+size_t isis_circuit_pdu_size(struct isis_circuit *circuit);
+void isis_circuit_switchover_routes(struct isis_circuit *circuit, int family,
+ union g_addr *nexthop_ip,
+ ifindex_t ifindex);
+void isis_circuit_stream(struct isis_circuit *circuit, struct stream **stream);
+
+void isis_circuit_af_set(struct isis_circuit *circuit, bool ip_router,
+ bool ipv6_router);
+ferr_r isis_circuit_passive_set(struct isis_circuit *circuit, bool passive);
+void isis_circuit_is_type_set(struct isis_circuit *circuit, int is_type);
+void isis_circuit_circ_type_set(struct isis_circuit *circuit, int circ_type);
+
+ferr_r isis_circuit_metric_set(struct isis_circuit *circuit, int level,
+ int metric);
+
+ferr_r isis_circuit_passwd_unset(struct isis_circuit *circuit);
+ferr_r isis_circuit_passwd_set(struct isis_circuit *circuit,
+ uint8_t passwd_type, const char *passwd);
+ferr_r isis_circuit_passwd_cleartext_set(struct isis_circuit *circuit,
+ const char *passwd);
+ferr_r isis_circuit_passwd_hmac_md5_set(struct isis_circuit *circuit,
+ const char *passwd);
+
+int isis_circuit_mt_enabled_set(struct isis_circuit *circuit, uint16_t mtid,
+ bool enabled);
+
+#ifdef FABRICD
+DECLARE_HOOK(isis_circuit_config_write,
+ (struct isis_circuit *circuit, struct vty *vty),
+ (circuit, vty));
+#endif
+
+DECLARE_HOOK(isis_circuit_add_addr_hook, (struct isis_circuit *circuit),
+ (circuit));
+
+DECLARE_HOOK(isis_circuit_new_hook, (struct isis_circuit *circuit), (circuit));
+DECLARE_HOOK(isis_circuit_del_hook, (struct isis_circuit *circuit), (circuit));
+
+#endif /* _ZEBRA_ISIS_CIRCUIT_H */
diff --git a/isisd/isis_cli.c b/isisd/isis_cli.c
new file mode 100644
index 0000000..9718a45
--- /dev/null
+++ b/isisd/isis_cli.c
@@ -0,0 +1,4116 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ * Copyright (C) 2018 Volta Networks
+ * Emanuele Di Pascale
+ */
+
+#include <zebra.h>
+
+#include "if.h"
+#include "vrf.h"
+#include "log.h"
+#include "prefix.h"
+#include "command.h"
+#include "northbound_cli.h"
+#include "libfrr.h"
+#include "yang.h"
+#include "lib/linklist.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_nb.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isis_flex_algo.h"
+
+#include "isisd/isis_cli_clippy.c"
+
+#ifndef FABRICD
+
+/*
+ * XPath: /frr-isisd:isis/instance
+ */
+DEFPY_YANG_NOSH(router_isis, router_isis_cmd,
+ "router isis WORD$tag [vrf NAME$vrf_name]",
+ ROUTER_STR
+ "ISO IS-IS\n"
+ "ISO Routing area tag\n" VRF_CMD_HELP_STR)
+{
+ int ret;
+ char base_xpath[XPATH_MAXLEN];
+
+ if (!vrf_name)
+ vrf_name = VRF_DEFAULT_NAME;
+
+ snprintf(base_xpath, XPATH_MAXLEN,
+ "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']", tag,
+ vrf_name);
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+
+ ret = nb_cli_apply_changes(vty, "%s", base_xpath);
+ if (ret == CMD_SUCCESS)
+ VTY_PUSH_XPATH(ISIS_NODE, base_xpath);
+
+ return ret;
+}
+
+DEFPY_YANG(no_router_isis, no_router_isis_cmd,
+ "no router isis WORD$tag [vrf NAME$vrf_name]",
+ NO_STR ROUTER_STR
+ "ISO IS-IS\n"
+ "ISO Routing area tag\n" VRF_CMD_HELP_STR)
+{
+ if (!vrf_name)
+ vrf_name = VRF_DEFAULT_NAME;
+
+ if (!yang_dnode_existsf(
+ vty->candidate_config->dnode,
+ "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']", tag,
+ vrf_name)) {
+ vty_out(vty, "ISIS area %s not found.\n", tag);
+ return CMD_ERR_NOTHING_TODO;
+ }
+
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes_clear_pending(
+ vty, "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']", tag,
+ vrf_name);
+}
+
+void cli_show_router_isis(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *vrf = NULL;
+
+ vrf = yang_dnode_get_string(dnode, "./vrf");
+
+ vty_out(vty, "!\n");
+ vty_out(vty, "router isis %s",
+ yang_dnode_get_string(dnode, "./area-tag"));
+ if (!strmatch(vrf, VRF_DEFAULT_NAME))
+ vty_out(vty, " vrf %s", yang_dnode_get_string(dnode, "./vrf"));
+ vty_out(vty, "\n");
+}
+
+void cli_show_router_isis_end(struct vty *vty, const struct lyd_node *dnode)
+{
+ vty_out(vty, "exit\n");
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv4-routing
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv6-routing
+ * XPath: /frr-isisd:isis/instance
+ */
+DEFPY_YANG(ip_router_isis, ip_router_isis_cmd,
+ "ip router isis WORD$tag",
+ "Interface Internet Protocol config commands\n"
+ "IP router interface commands\n"
+ "IS-IS routing protocol\n"
+ "Routing process tag\n")
+{
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY,
+ tag);
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv4-routing",
+ NB_OP_MODIFY, "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+ALIAS_HIDDEN(ip_router_isis, ip_router_isis_vrf_cmd,
+ "ip router isis WORD$tag vrf NAME$vrf_name",
+ "Interface Internet Protocol config commands\n"
+ "IP router interface commands\n"
+ "IS-IS routing protocol\n"
+ "Routing process tag\n" VRF_CMD_HELP_STR)
+
+DEFPY_YANG(ip6_router_isis, ip6_router_isis_cmd,
+ "ipv6 router isis WORD$tag",
+ "Interface Internet Protocol config commands\n"
+ "IP router interface commands\n"
+ "IS-IS routing protocol\n"
+ "Routing process tag\n")
+{
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY,
+ tag);
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv6-routing",
+ NB_OP_MODIFY, "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+ALIAS_HIDDEN(ip6_router_isis, ip6_router_isis_vrf_cmd,
+ "ipv6 router isis WORD$tag vrf NAME$vrf_name",
+ "Interface Internet Protocol config commands\n"
+ "IP router interface commands\n"
+ "IS-IS routing protocol\n"
+ "Routing process tag\n" VRF_CMD_HELP_STR)
+
+DEFPY_YANG(no_ip_router_isis, no_ip_router_isis_cmd,
+ "no <ip|ipv6>$ip router isis [WORD]$tag",
+ NO_STR
+ "Interface Internet Protocol config commands\n"
+ "IP router interface commands\n"
+ "IP router interface commands\n"
+ "IS-IS routing protocol\n"
+ "Routing process tag\n")
+{
+ const struct lyd_node *dnode;
+
+ dnode = yang_dnode_getf(vty->candidate_config->dnode,
+ "%s/frr-isisd:isis", VTY_CURR_XPATH);
+ if (!dnode)
+ return CMD_SUCCESS;
+
+ /*
+ * If both ipv4 and ipv6 are off delete the interface isis container.
+ */
+ if (strmatch(ip, "ipv6")) {
+ if (!yang_dnode_get_bool(dnode, "./ipv4-routing"))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis",
+ NB_OP_DESTROY, NULL);
+ else
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/ipv6-routing",
+ NB_OP_MODIFY, "false");
+ } else {
+ if (!yang_dnode_get_bool(dnode, "./ipv6-routing"))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis",
+ NB_OP_DESTROY, NULL);
+ else
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/ipv4-routing",
+ NB_OP_MODIFY, "false");
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+ALIAS_HIDDEN(no_ip_router_isis, no_ip_router_isis_vrf_cmd,
+ "no <ip|ipv6>$ip router isis WORD$tag vrf NAME$vrf_name",
+ NO_STR
+ "Interface Internet Protocol config commands\n"
+ "IP router interface commands\n"
+ "IP router interface commands\n"
+ "IS-IS routing protocol\n"
+ "Routing process tag\n"
+ VRF_CMD_HELP_STR)
+
+void cli_show_ip_isis_ipv4(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " ip router isis %s\n",
+ yang_dnode_get_string(dnode, "../area-tag"));
+}
+
+void cli_show_ip_isis_ipv6(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " ipv6 router isis %s\n",
+ yang_dnode_get_string(dnode, "../area-tag"));
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring
+ */
+DEFPY_YANG(isis_bfd,
+ isis_bfd_cmd,
+ "[no] isis bfd",
+ NO_STR PROTO_HELP
+ "Enable BFD support\n")
+{
+ const struct lyd_node *dnode;
+
+ dnode = yang_dnode_getf(vty->candidate_config->dnode,
+ "%s/frr-isisd:isis", VTY_CURR_XPATH);
+ if (dnode == NULL) {
+ vty_out(vty, "ISIS is not enabled on this circuit\n");
+ return CMD_SUCCESS;
+ }
+
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/bfd-monitoring/enabled",
+ NB_OP_MODIFY, no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring/profile
+ */
+DEFPY_YANG(isis_bfd_profile,
+ isis_bfd_profile_cmd,
+ "[no] isis bfd profile BFDPROF$profile",
+ NO_STR PROTO_HELP
+ "Enable BFD support\n"
+ "Use a pre-configured profile\n"
+ "Profile name\n")
+{
+ const struct lyd_node *dnode;
+
+ dnode = yang_dnode_getf(vty->candidate_config->dnode,
+ "%s/frr-isisd:isis", VTY_CURR_XPATH);
+ if (dnode == NULL) {
+ vty_out(vty, "ISIS is not enabled on this circuit\n");
+ return CMD_SUCCESS;
+ }
+
+ if (no)
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/bfd-monitoring/profile",
+ NB_OP_DESTROY, NULL);
+ else
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/bfd-monitoring/profile",
+ NB_OP_MODIFY, profile);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_bfd_monitoring(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, "./enabled")) {
+ if (show_defaults)
+ vty_out(vty, " no isis bfd\n");
+ } else {
+ vty_out(vty, " isis bfd\n");
+ }
+
+ if (yang_dnode_exists(dnode, "./profile"))
+ vty_out(vty, " isis bfd profile %s\n",
+ yang_dnode_get_string(dnode, "./profile"));
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/area-address
+ */
+DEFPY_YANG(net, net_cmd, "[no] net WORD",
+ "Remove an existing Network Entity Title for this process\n"
+ "A Network Entity Title for this process (OSI only)\n"
+ "XX.XXXX. ... .XXX.XX Network entity title (NET)\n")
+{
+ nb_cli_enqueue_change(vty, "./area-address",
+ no ? NB_OP_DESTROY : NB_OP_CREATE, net);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_area_address(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " net %s\n", yang_dnode_get_string(dnode, NULL));
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/is-type
+ */
+DEFPY_YANG(is_type, is_type_cmd, "is-type <level-1|level-1-2|level-2-only>$level",
+ "IS Level for this routing process (OSI only)\n"
+ "Act as a station router only\n"
+ "Act as both a station router and an area router\n"
+ "Act as an area router only\n")
+{
+ nb_cli_enqueue_change(vty, "./is-type", NB_OP_MODIFY,
+ strmatch(level, "level-2-only") ? "level-2"
+ : level);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_is_type, no_is_type_cmd,
+ "no is-type [<level-1|level-1-2|level-2-only>]",
+ NO_STR
+ "IS Level for this routing process (OSI only)\n"
+ "Act as a station router only\n"
+ "Act as both a station router and an area router\n"
+ "Act as an area router only\n")
+{
+ nb_cli_enqueue_change(vty, "./is-type", NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_is_type(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ int is_type = yang_dnode_get_enum(dnode, NULL);
+
+ switch (is_type) {
+ case IS_LEVEL_1:
+ vty_out(vty, " is-type level-1\n");
+ break;
+ case IS_LEVEL_2:
+ vty_out(vty, " is-type level-2-only\n");
+ break;
+ case IS_LEVEL_1_AND_2:
+ vty_out(vty, " is-type level-1-2\n");
+ break;
+ }
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/dynamic-hostname
+ */
+DEFPY_YANG(dynamic_hostname, dynamic_hostname_cmd, "[no] hostname dynamic",
+ NO_STR
+ "Dynamic hostname for IS-IS\n"
+ "Dynamic hostname\n")
+{
+ nb_cli_enqueue_change(vty, "./dynamic-hostname", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_dynamic_hostname(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+
+ vty_out(vty, " hostname dynamic\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/overload
+ */
+DEFPY_YANG(set_overload_bit, set_overload_bit_cmd, "[no] set-overload-bit",
+ "Reset overload bit to accept transit traffic\n"
+ "Set overload bit to avoid any transit traffic\n")
+{
+ nb_cli_enqueue_change(vty, "./overload/enabled", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_overload(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " set-overload-bit\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/overload/on-startup
+ */
+DEFPY_YANG(set_overload_bit_on_startup, set_overload_bit_on_startup_cmd,
+ "set-overload-bit on-startup (0-86400)$val",
+ "Set overload bit to avoid any transit traffic\n"
+ "Set overload bit on startup\n"
+ "Set overload time in seconds\n")
+{
+ nb_cli_enqueue_change(vty, "./overload/on-startup", NB_OP_MODIFY,
+ val_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_set_overload_bit_on_startup, no_set_overload_bit_on_startup_cmd,
+ "no set-overload-bit on-startup [(0-86400)$val]",
+ NO_STR
+ "Reset overload bit to accept transit traffic\n"
+ "Set overload bit on startup\n"
+ "Set overload time in seconds\n")
+{
+ nb_cli_enqueue_change(vty, "./overload/on-startup", NB_OP_DESTROY,
+ NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_overload_on_startup(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " set-overload-bit on-startup %s\n",
+ yang_dnode_get_string(dnode, NULL));
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/advertise-high-metrics
+ */
+DEFPY_YANG(advertise_high_metrics, advertise_high_metrics_cmd,
+ "[no] advertise-high-metrics",
+ NO_STR "Advertise high metric value on all interfaces\n")
+{
+ nb_cli_enqueue_change(vty, "./advertise-high-metrics", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_advertise_high_metrics(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " advertise-high-metrics\n");
+ else if (show_defaults)
+ vty_out(vty, " no advertise-high-metrics\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/attach-send
+ */
+DEFPY_YANG(attached_bit_send, attached_bit_send_cmd, "[no] attached-bit send",
+ "Reset attached bit\n"
+ "Set attached bit for inter-area traffic\n"
+ "Set attached bit in LSP sent to L1 router\n")
+{
+ nb_cli_enqueue_change(vty, "./attach-send", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_attached_send(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " attached-bit send\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/attach-receive-ignore
+ */
+DEFPY_YANG(
+ attached_bit_receive_ignore, attached_bit_receive_ignore_cmd,
+ "[no] attached-bit receive ignore",
+ "Reset attached bit\n"
+ "Set attach bit for inter-area traffic\n"
+ "If LSP received with attached bit set, create default route to neighbor\n"
+ "Do not process attached bit\n")
+{
+ nb_cli_enqueue_change(vty, "./attach-receive-ignore", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_attached_receive(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " attached-bit receive ignore\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/metric-style
+ */
+DEFPY_YANG(metric_style, metric_style_cmd,
+ "metric-style <narrow|transition|wide>$style",
+ "Use old-style (ISO 10589) or new-style packet formats\n"
+ "Use old style of TLVs with narrow metric\n"
+ "Send and accept both styles of TLVs during transition\n"
+ "Use new style of TLVs to carry wider metric\n")
+{
+ nb_cli_enqueue_change(vty, "./metric-style", NB_OP_MODIFY, style);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_metric_style, no_metric_style_cmd,
+ "no metric-style [narrow|transition|wide]",
+ NO_STR
+ "Use old-style (ISO 10589) or new-style packet formats\n"
+ "Use old style of TLVs with narrow metric\n"
+ "Send and accept both styles of TLVs during transition\n"
+ "Use new style of TLVs to carry wider metric\n")
+{
+ nb_cli_enqueue_change(vty, "./metric-style", NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_metric_style(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ int metric = yang_dnode_get_enum(dnode, NULL);
+
+ switch (metric) {
+ case ISIS_NARROW_METRIC:
+ vty_out(vty, " metric-style narrow\n");
+ break;
+ case ISIS_WIDE_METRIC:
+ vty_out(vty, " metric-style wide\n");
+ break;
+ case ISIS_TRANSITION_METRIC:
+ vty_out(vty, " metric-style transition\n");
+ break;
+ }
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/area-password
+ */
+DEFPY_YANG(area_passwd, area_passwd_cmd,
+ "area-password <clear|md5>$pwd_type WORD$pwd [authenticate snp <send-only|validate>$snp]",
+ "Configure the authentication password for an area\n"
+ "Clear-text authentication type\n"
+ "MD5 authentication type\n"
+ "Level-wide password\n"
+ "Authentication\n"
+ "SNP PDUs\n"
+ "Send but do not check PDUs on receiving\n"
+ "Send and check PDUs on receiving\n")
+{
+ nb_cli_enqueue_change(vty, "./area-password", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./area-password/password", NB_OP_MODIFY,
+ pwd);
+ nb_cli_enqueue_change(vty, "./area-password/password-type",
+ NB_OP_MODIFY, pwd_type);
+ nb_cli_enqueue_change(vty, "./area-password/authenticate-snp",
+ NB_OP_MODIFY, snp ? snp : "none");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_area_pwd(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *snp;
+
+ vty_out(vty, " area-password %s %s",
+ yang_dnode_get_string(dnode, "./password-type"),
+ yang_dnode_get_string(dnode, "./password"));
+ snp = yang_dnode_get_string(dnode, "./authenticate-snp");
+ if (!strmatch("none", snp))
+ vty_out(vty, " authenticate snp %s", snp);
+ vty_out(vty, "\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/domain-password
+ */
+DEFPY_YANG(domain_passwd, domain_passwd_cmd,
+ "domain-password <clear|md5>$pwd_type WORD$pwd [authenticate snp <send-only|validate>$snp]",
+ "Set the authentication password for a routing domain\n"
+ "Clear-text authentication type\n"
+ "MD5 authentication type\n"
+ "Level-wide password\n"
+ "Authentication\n"
+ "SNP PDUs\n"
+ "Send but do not check PDUs on receiving\n"
+ "Send and check PDUs on receiving\n")
+{
+ nb_cli_enqueue_change(vty, "./domain-password", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./domain-password/password", NB_OP_MODIFY,
+ pwd);
+ nb_cli_enqueue_change(vty, "./domain-password/password-type",
+ NB_OP_MODIFY, pwd_type);
+ nb_cli_enqueue_change(vty, "./domain-password/authenticate-snp",
+ NB_OP_MODIFY, snp ? snp : "none");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_area_passwd, no_area_passwd_cmd,
+ "no <area-password|domain-password>$cmd",
+ NO_STR
+ "Configure the authentication password for an area\n"
+ "Set the authentication password for a routing domain\n")
+{
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, "./%s", cmd);
+}
+
+void cli_show_isis_domain_pwd(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *snp;
+
+ vty_out(vty, " domain-password %s %s",
+ yang_dnode_get_string(dnode, "./password-type"),
+ yang_dnode_get_string(dnode, "./password"));
+ snp = yang_dnode_get_string(dnode, "./authenticate-snp");
+ if (!strmatch("none", snp))
+ vty_out(vty, " authenticate snp %s", snp);
+ vty_out(vty, "\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/generation-interval
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-2/generation-interval
+ */
+DEFPY_YANG(lsp_gen_interval, lsp_gen_interval_cmd,
+ "lsp-gen-interval [level-1|level-2]$level (1-120)$val",
+ "Minimum interval between regenerating same LSP\n"
+ "Set interval for level 1 only\n"
+ "Set interval for level 2 only\n"
+ "Minimum interval in seconds\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(
+ vty, "./lsp/timers/level-1/generation-interval",
+ NB_OP_MODIFY, val_str);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(
+ vty, "./lsp/timers/level-2/generation-interval",
+ NB_OP_MODIFY, val_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_lsp_gen_interval, no_lsp_gen_interval_cmd,
+ "no lsp-gen-interval [level-1|level-2]$level [(1-120)]",
+ NO_STR
+ "Minimum interval between regenerating same LSP\n"
+ "Set interval for level 1 only\n"
+ "Set interval for level 2 only\n"
+ "Minimum interval in seconds\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(
+ vty, "./lsp/timers/level-1/generation-interval",
+ NB_OP_MODIFY, NULL);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(
+ vty, "./lsp/timers/level-2/generation-interval",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/refresh-interval
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-2/refresh-interval
+ */
+DEFPY_YANG(lsp_refresh_interval, lsp_refresh_interval_cmd,
+ "lsp-refresh-interval [level-1|level-2]$level (1-65235)$val",
+ "LSP refresh interval\n"
+ "LSP refresh interval for Level 1 only\n"
+ "LSP refresh interval for Level 2 only\n"
+ "LSP refresh interval in seconds\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-1/refresh-interval",
+ NB_OP_MODIFY, val_str);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-2/refresh-interval",
+ NB_OP_MODIFY, val_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_lsp_refresh_interval, no_lsp_refresh_interval_cmd,
+ "no lsp-refresh-interval [level-1|level-2]$level [(1-65235)]",
+ NO_STR
+ "LSP refresh interval\n"
+ "LSP refresh interval for Level 1 only\n"
+ "LSP refresh interval for Level 2 only\n"
+ "LSP refresh interval in seconds\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-1/refresh-interval",
+ NB_OP_MODIFY, NULL);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-2/refresh-interval",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/maximum-lifetime
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/maximum-lifetime
+ */
+
+DEFPY_YANG(max_lsp_lifetime, max_lsp_lifetime_cmd,
+ "max-lsp-lifetime [level-1|level-2]$level (350-65535)$val",
+ "Maximum LSP lifetime\n"
+ "Maximum LSP lifetime for Level 1 only\n"
+ "Maximum LSP lifetime for Level 2 only\n"
+ "LSP lifetime in seconds\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-1/maximum-lifetime",
+ NB_OP_MODIFY, val_str);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-2/maximum-lifetime",
+ NB_OP_MODIFY, val_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_max_lsp_lifetime, no_max_lsp_lifetime_cmd,
+ "no max-lsp-lifetime [level-1|level-2]$level [(350-65535)]",
+ NO_STR
+ "Maximum LSP lifetime\n"
+ "Maximum LSP lifetime for Level 1 only\n"
+ "Maximum LSP lifetime for Level 2 only\n"
+ "LSP lifetime in seconds\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-1/maximum-lifetime",
+ NB_OP_MODIFY, NULL);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-2/maximum-lifetime",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+/* unified LSP timers command
+ * XPath: /frr-isisd:isis/instance/lsp/timers
+ */
+
+DEFPY_YANG(lsp_timers, lsp_timers_cmd,
+ "lsp-timers [level-1|level-2]$level gen-interval (1-120)$gen refresh-interval (1-65235)$refresh max-lifetime (350-65535)$lifetime",
+ "LSP-related timers\n"
+ "LSP-related timers for Level 1 only\n"
+ "LSP-related timers for Level 2 only\n"
+ "Minimum interval between regenerating same LSP\n"
+ "Generation interval in seconds\n"
+ "LSP refresh interval\n"
+ "LSP refresh interval in seconds\n"
+ "Maximum LSP lifetime\n"
+ "Maximum LSP lifetime in seconds\n")
+{
+ if (!level || strmatch(level, "level-1")) {
+ nb_cli_enqueue_change(
+ vty, "./lsp/timers/level-1/generation-interval",
+ NB_OP_MODIFY, gen_str);
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-1/refresh-interval",
+ NB_OP_MODIFY, refresh_str);
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-1/maximum-lifetime",
+ NB_OP_MODIFY, lifetime_str);
+ }
+ if (!level || strmatch(level, "level-2")) {
+ nb_cli_enqueue_change(
+ vty, "./lsp/timers/level-2/generation-interval",
+ NB_OP_MODIFY, gen_str);
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-2/refresh-interval",
+ NB_OP_MODIFY, refresh_str);
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-2/maximum-lifetime",
+ NB_OP_MODIFY, lifetime_str);
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_lsp_timers, no_lsp_timers_cmd,
+ "no lsp-timers [level-1|level-2]$level [gen-interval (1-120) refresh-interval (1-65235) max-lifetime (350-65535)]",
+ NO_STR
+ "LSP-related timers\n"
+ "LSP-related timers for Level 1 only\n"
+ "LSP-related timers for Level 2 only\n"
+ "Minimum interval between regenerating same LSP\n"
+ "Generation interval in seconds\n"
+ "LSP refresh interval\n"
+ "LSP refresh interval in seconds\n"
+ "Maximum LSP lifetime\n"
+ "Maximum LSP lifetime in seconds\n")
+{
+ if (!level || strmatch(level, "level-1")) {
+ nb_cli_enqueue_change(
+ vty, "./lsp/timers/level-1/generation-interval",
+ NB_OP_MODIFY, NULL);
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-1/refresh-interval",
+ NB_OP_MODIFY, NULL);
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-1/maximum-lifetime",
+ NB_OP_MODIFY, NULL);
+ }
+ if (!level || strmatch(level, "level-2")) {
+ nb_cli_enqueue_change(
+ vty, "./lsp/timers/level-2/generation-interval",
+ NB_OP_MODIFY, NULL);
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-2/refresh-interval",
+ NB_OP_MODIFY, NULL);
+ nb_cli_enqueue_change(vty,
+ "./lsp/timers/level-2/maximum-lifetime",
+ NB_OP_MODIFY, NULL);
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_lsp_timers(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *l1_refresh =
+ yang_dnode_get_string(dnode, "./level-1/refresh-interval");
+ const char *l2_refresh =
+ yang_dnode_get_string(dnode, "./level-2/refresh-interval");
+ const char *l1_lifetime =
+ yang_dnode_get_string(dnode, "./level-1/maximum-lifetime");
+ const char *l2_lifetime =
+ yang_dnode_get_string(dnode, "./level-2/maximum-lifetime");
+ const char *l1_gen =
+ yang_dnode_get_string(dnode, "./level-1/generation-interval");
+ const char *l2_gen =
+ yang_dnode_get_string(dnode, "./level-2/generation-interval");
+ if (strmatch(l1_refresh, l2_refresh)
+ && strmatch(l1_lifetime, l2_lifetime) && strmatch(l1_gen, l2_gen))
+ vty_out(vty,
+ " lsp-timers gen-interval %s refresh-interval %s max-lifetime %s\n",
+ l1_gen, l1_refresh, l1_lifetime);
+ else {
+ vty_out(vty,
+ " lsp-timers level-1 gen-interval %s refresh-interval %s max-lifetime %s\n",
+ l1_gen, l1_refresh, l1_lifetime);
+ vty_out(vty,
+ " lsp-timers level-2 gen-interval %s refresh-interval %s max-lifetime %s\n",
+ l2_gen, l2_refresh, l2_lifetime);
+ }
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/lsp/mtu
+ */
+DEFPY_YANG(area_lsp_mtu, area_lsp_mtu_cmd, "lsp-mtu (128-4352)$val",
+ "Configure the maximum size of generated LSPs\n"
+ "Maximum size of generated LSPs\n")
+{
+ nb_cli_enqueue_change(vty, "./lsp/mtu", NB_OP_MODIFY, val_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_area_lsp_mtu, no_area_lsp_mtu_cmd, "no lsp-mtu [(128-4352)]",
+ NO_STR
+ "Configure the maximum size of generated LSPs\n"
+ "Maximum size of generated LSPs\n")
+{
+ nb_cli_enqueue_change(vty, "./lsp/mtu", NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_lsp_mtu(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " lsp-mtu %s\n", yang_dnode_get_string(dnode, NULL));
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/advertise-passive-only
+ */
+DEFPY_YANG(advertise_passive_only, advertise_passive_only_cmd,
+ "[no] advertise-passive-only",
+ NO_STR "Advertise prefixes of passive interfaces only\n")
+{
+ nb_cli_enqueue_change(vty, "./advertise-passive-only", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_advertise_passive_only(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+
+ vty_out(vty, " advertise-passive-only\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/minimum-interval
+ */
+DEFPY_YANG(spf_interval, spf_interval_cmd,
+ "spf-interval [level-1|level-2]$level (1-120)$val",
+ "Minimum interval between SPF calculations\n"
+ "Set interval for level 1 only\n"
+ "Set interval for level 2 only\n"
+ "Minimum interval between consecutive SPFs in seconds\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-1",
+ NB_OP_MODIFY, val_str);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-2",
+ NB_OP_MODIFY, val_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_spf_interval, no_spf_interval_cmd,
+ "no spf-interval [level-1|level-2]$level [(1-120)]",
+ NO_STR
+ "Minimum interval between SPF calculations\n"
+ "Set interval for level 1 only\n"
+ "Set interval for level 2 only\n"
+ "Minimum interval between consecutive SPFs in seconds\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-1",
+ NB_OP_MODIFY, NULL);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-2",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_spf_min_interval(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *l1 = yang_dnode_get_string(dnode, "./level-1");
+ const char *l2 = yang_dnode_get_string(dnode, "./level-2");
+
+ if (strmatch(l1, l2))
+ vty_out(vty, " spf-interval %s\n", l1);
+ else {
+ vty_out(vty, " spf-interval level-1 %s\n", l1);
+ vty_out(vty, " spf-interval level-2 %s\n", l2);
+ }
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay
+ */
+DEFPY_YANG(spf_delay_ietf, spf_delay_ietf_cmd,
+ "spf-delay-ietf init-delay (0-60000) short-delay (0-60000) long-delay (0-60000) holddown (0-60000) time-to-learn (0-60000)",
+ "IETF SPF delay algorithm\n"
+ "Delay used while in QUIET state\n"
+ "Delay used while in QUIET state in milliseconds\n"
+ "Delay used while in SHORT_WAIT state\n"
+ "Delay used while in SHORT_WAIT state in milliseconds\n"
+ "Delay used while in LONG_WAIT\n"
+ "Delay used while in LONG_WAIT state in milliseconds\n"
+ "Time with no received IGP events before considering IGP stable\n"
+ "Time with no received IGP events before considering IGP stable (in milliseconds)\n"
+ "Maximum duration needed to learn all the events related to a single failure\n"
+ "Maximum duration needed to learn all the events related to a single failure (in milliseconds)\n")
+{
+ nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay", NB_OP_CREATE,
+ NULL);
+ nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/init-delay",
+ NB_OP_MODIFY, init_delay_str);
+ nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/short-delay",
+ NB_OP_MODIFY, short_delay_str);
+ nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/long-delay",
+ NB_OP_MODIFY, long_delay_str);
+ nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/hold-down",
+ NB_OP_MODIFY, holddown_str);
+ nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/time-to-learn",
+ NB_OP_MODIFY, time_to_learn_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_spf_delay_ietf, no_spf_delay_ietf_cmd,
+ "no spf-delay-ietf [init-delay (0-60000) short-delay (0-60000) long-delay (0-60000) holddown (0-60000) time-to-learn (0-60000)]",
+ NO_STR
+ "IETF SPF delay algorithm\n"
+ "Delay used while in QUIET state\n"
+ "Delay used while in QUIET state in milliseconds\n"
+ "Delay used while in SHORT_WAIT state\n"
+ "Delay used while in SHORT_WAIT state in milliseconds\n"
+ "Delay used while in LONG_WAIT\n"
+ "Delay used while in LONG_WAIT state in milliseconds\n"
+ "Time with no received IGP events before considering IGP stable\n"
+ "Time with no received IGP events before considering IGP stable (in milliseconds)\n"
+ "Maximum duration needed to learn all the events related to a single failure\n"
+ "Maximum duration needed to learn all the events related to a single failure (in milliseconds)\n")
+{
+ nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay", NB_OP_DESTROY,
+ NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_spf_ietf_backoff(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty,
+ " spf-delay-ietf init-delay %s short-delay %s long-delay %s holddown %s time-to-learn %s\n",
+ yang_dnode_get_string(dnode, "./init-delay"),
+ yang_dnode_get_string(dnode, "./short-delay"),
+ yang_dnode_get_string(dnode, "./long-delay"),
+ yang_dnode_get_string(dnode, "./hold-down"),
+ yang_dnode_get_string(dnode, "./time-to-learn"));
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/prefix-priorities/medium/access-list-name
+ */
+DEFPY_YANG(spf_prefix_priority, spf_prefix_priority_cmd,
+ "spf prefix-priority <critical|high|medium>$priority ACCESSLIST_NAME$acl_name",
+ "SPF configuration\n"
+ "Configure a prefix priority list\n"
+ "Specify critical priority prefixes\n"
+ "Specify high priority prefixes\n"
+ "Specify medium priority prefixes\n"
+ "Access-list name\n")
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, XPATH_MAXLEN,
+ "./spf/prefix-priorities/%s/access-list-name", priority);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, acl_name);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_spf_prefix_priority, no_spf_prefix_priority_cmd,
+ "no spf prefix-priority <critical|high|medium>$priority [ACCESSLIST_NAME]",
+ NO_STR
+ "SPF configuration\n"
+ "Configure a prefix priority list\n"
+ "Specify critical priority prefixes\n"
+ "Specify high priority prefixes\n"
+ "Specify medium priority prefixes\n"
+ "Access-list name\n")
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, XPATH_MAXLEN,
+ "./spf/prefix-priorities/%s/access-list-name", priority);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_spf_prefix_priority(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " spf prefix-priority %s %s\n",
+ dnode->parent->schema->name,
+ yang_dnode_get_string(dnode, NULL));
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/purge-originator
+ */
+DEFPY_YANG(area_purge_originator, area_purge_originator_cmd, "[no] purge-originator",
+ NO_STR "Use the RFC 6232 purge-originator\n")
+{
+ nb_cli_enqueue_change(vty, "./purge-originator", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_purge_origin(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " purge-originator\n");
+}
+
+
+/*
+ * XPath: /frr-isisd:isis/instance/admin-group-send-zero
+ */
+DEFPY_YANG(isis_admin_group_send_zero, isis_admin_group_send_zero_cmd,
+ "[no] admin-group-send-zero",
+ NO_STR
+ "Allow sending the default admin-group value of 0x00000000.\n")
+{
+ nb_cli_enqueue_change(vty, "./admin-group-send-zero", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_admin_group_send_zero(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " admin-group-send-zero\n");
+}
+
+
+/*
+ * XPath: /frr-isisd:isis/instance/asla-legacy-flag
+ */
+DEFPY_HIDDEN(isis_asla_legacy_flag, isis_asla_legacy_flag_cmd,
+ "[no] asla-legacy-flag",
+ NO_STR "Set the legacy flag (aka. L-FLAG) in the ASLA Sub-TLV.\n")
+{
+ nb_cli_enqueue_change(vty, "./asla-legacy-flag", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_asla_legacy_flag(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " asla-legacy-flag\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/mpls-te
+ */
+DEFPY_YANG(isis_mpls_te_on, isis_mpls_te_on_cmd, "mpls-te on",
+ MPLS_TE_STR "Enable the MPLS-TE functionality\n")
+{
+ nb_cli_enqueue_change(vty, "./mpls-te", NB_OP_CREATE,
+ NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_isis_mpls_te_on, no_isis_mpls_te_on_cmd, "no mpls-te [on]",
+ NO_STR
+ "Disable the MPLS-TE functionality\n"
+ "Disable the MPLS-TE functionality\n")
+{
+ nb_cli_enqueue_change(vty, "./mpls-te", NB_OP_DESTROY,
+ NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_mpls_te(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " mpls-te on\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/mpls-te/router-address
+ */
+DEFPY_YANG(isis_mpls_te_router_addr, isis_mpls_te_router_addr_cmd,
+ "mpls-te router-address A.B.C.D",
+ MPLS_TE_STR
+ "Stable IP address of the advertising router\n"
+ "MPLS-TE router address in IPv4 address format\n")
+{
+ nb_cli_enqueue_change(vty, "./mpls-te/router-address",
+ NB_OP_MODIFY, router_address_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_isis_mpls_te_router_addr, no_isis_mpls_te_router_addr_cmd,
+ "no mpls-te router-address [A.B.C.D]",
+ NO_STR MPLS_TE_STR
+ "Delete IP address of the advertising router\n"
+ "MPLS-TE router address in IPv4 address format\n")
+{
+ nb_cli_enqueue_change(vty, "./mpls-te/router-address",
+ NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_mpls_te_router_addr(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " mpls-te router-address %s\n",
+ yang_dnode_get_string(dnode, NULL));
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/mpls-te/router-address-v6
+ */
+DEFPY_YANG(isis_mpls_te_router_addr_v6, isis_mpls_te_router_addr_v6_cmd,
+ "mpls-te router-address ipv6 X:X::X:X",
+ MPLS_TE_STR
+ "Stable IP address of the advertising router\n"
+ "IPv6 address\n"
+ "MPLS-TE router address in IPv6 address format\n")
+{
+ nb_cli_enqueue_change(vty, "./mpls-te/router-address-v6", NB_OP_MODIFY,
+ ipv6_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_isis_mpls_te_router_addr_v6, no_isis_mpls_te_router_addr_v6_cmd,
+ "no mpls-te router-address ipv6 [X:X::X:X]",
+ NO_STR MPLS_TE_STR
+ "Delete IP address of the advertising router\n"
+ "IPv6 address\n"
+ "MPLS-TE router address in IPv6 address format\n")
+{
+ nb_cli_enqueue_change(vty, "./mpls-te/router-address-v6", NB_OP_DESTROY,
+ NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_mpls_te_router_addr_ipv6(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " mpls-te router-address ipv6 %s\n",
+ yang_dnode_get_string(dnode, NULL));
+}
+
+DEFPY_YANG(isis_mpls_te_inter_as, isis_mpls_te_inter_as_cmd,
+ "[no] mpls-te inter-as [level-1|level-1-2|level-2-only]",
+ NO_STR MPLS_TE_STR
+ "Configure MPLS-TE Inter-AS support\n"
+ "AREA native mode self originate INTER-AS LSP with L1 only flooding scope\n"
+ "AREA native mode self originate INTER-AS LSP with L1 and L2 flooding scope\n"
+ "AS native mode self originate INTER-AS LSP with L2 only flooding scope\n")
+{
+ vty_out(vty, "MPLS-TE Inter-AS is not yet supported\n");
+ return CMD_SUCCESS;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/mpls-te/export
+ */
+DEFPY_YANG(isis_mpls_te_export, isis_mpls_te_export_cmd, "mpls-te export",
+ MPLS_TE_STR "Enable export of MPLS-TE Link State information\n")
+{
+ nb_cli_enqueue_change(vty, "./mpls-te/export", NB_OP_MODIFY, "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_isis_mpls_te_export, no_isis_mpls_te_export_cmd,
+ "no mpls-te export",
+ NO_STR MPLS_TE_STR
+ "Disable export of MPLS-TE Link State information\n")
+{
+ nb_cli_enqueue_change(vty, "./mpls-te/export", NB_OP_MODIFY, "false");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_mpls_te_export(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+
+ vty_out(vty, " mpls-te export\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/default-information-originate
+ */
+DEFPY_YANG(isis_default_originate, isis_default_originate_cmd,
+ "[no] default-information originate <ipv4|ipv6>$ip <level-1|level-2>$level [always]$always [{metric (0-16777215)$metric|route-map RMAP_NAME$rmap}]",
+ NO_STR
+ "Control distribution of default information\n"
+ "Distribute a default route\n"
+ "Distribute default route for IPv4\n"
+ "Distribute default route for IPv6\n"
+ "Distribute default route into level-1\n"
+ "Distribute default route into level-2\n"
+ "Always advertise default route\n"
+ "Metric for default route\n"
+ "IS-IS default metric\n"
+ "Route map reference\n"
+ "Pointer to route-map entries\n")
+{
+ if (no)
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+ else {
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./always", NB_OP_MODIFY,
+ always ? "true" : "false");
+ nb_cli_enqueue_change(vty, "./route-map",
+ rmap ? NB_OP_MODIFY : NB_OP_DESTROY,
+ rmap ? rmap : NULL);
+ nb_cli_enqueue_change(vty, "./metric", NB_OP_MODIFY,
+ metric_str ? metric_str : NULL);
+ if (strmatch(ip, "ipv6") && !always) {
+ vty_out(vty,
+ "Zebra doesn't implement default-originate for IPv6 yet\n");
+ vty_out(vty,
+ "so use with care or use default-originate always.\n");
+ }
+ }
+
+ return nb_cli_apply_changes(
+ vty, "./default-information-originate/%s[level='%s']", ip,
+ level);
+}
+
+static void vty_print_def_origin(struct vty *vty, const struct lyd_node *dnode,
+ const char *family, const char *level,
+ bool show_defaults)
+{
+ vty_out(vty, " default-information originate %s %s", family, level);
+ if (yang_dnode_get_bool(dnode, "./always"))
+ vty_out(vty, " always");
+
+ if (yang_dnode_exists(dnode, "./route-map"))
+ vty_out(vty, " route-map %s",
+ yang_dnode_get_string(dnode, "./route-map"));
+ if (show_defaults || !yang_dnode_is_default(dnode, "./metric"))
+ vty_out(vty, " metric %s",
+ yang_dnode_get_string(dnode, "./metric"));
+
+ vty_out(vty, "\n");
+}
+
+void cli_show_isis_def_origin_ipv4(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *level = yang_dnode_get_string(dnode, "./level");
+
+ vty_print_def_origin(vty, dnode, "ipv4", level, show_defaults);
+}
+
+void cli_show_isis_def_origin_ipv6(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *level = yang_dnode_get_string(dnode, "./level");
+
+ vty_print_def_origin(vty, dnode, "ipv6", level, show_defaults);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/redistribute
+ */
+DEFPY_YANG(isis_redistribute, isis_redistribute_cmd,
+ "[no] redistribute <ipv4$ip " PROTO_IP_REDIST_STR "$proto|ipv6$ip "
+ PROTO_IP6_REDIST_STR "$proto> <level-1|level-2>$level"
+ "[{metric (0-16777215)|route-map RMAP_NAME$route_map}]",
+ NO_STR REDIST_STR
+ "Redistribute IPv4 routes\n"
+ PROTO_IP_REDIST_HELP
+ "Redistribute IPv6 routes\n"
+ PROTO_IP6_REDIST_HELP
+ "Redistribute into level-1\n"
+ "Redistribute into level-2\n"
+ "Metric for redistributed routes\n"
+ "IS-IS default metric\n"
+ "Route map reference\n"
+ "Pointer to route-map entries\n")
+{
+ if (no)
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+ else {
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./route-map",
+ route_map ? NB_OP_MODIFY : NB_OP_DESTROY,
+ route_map ? route_map : NULL);
+ nb_cli_enqueue_change(vty, "./metric", NB_OP_MODIFY,
+ metric_str ? metric_str : NULL);
+ }
+
+ return nb_cli_apply_changes(
+ vty, "./redistribute/%s[protocol='%s'][level='%s']", ip, proto,
+ level);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/redistribute/table
+ */
+DEFPY_YANG(isis_redistribute_table, isis_redistribute_table_cmd,
+ "[no] redistribute <ipv4|ipv6>$ip table (1-65535)$table"
+ "<level-1|level-2>$level [{metric (0-16777215)|route-map WORD}]",
+ NO_STR REDIST_STR "Redistribute IPv4 routes\n"
+ "Redistribute IPv6 routes\n"
+ "Non-main Kernel Routing Table\n"
+ "Table Id\n"
+ "Redistribute into level-1\n"
+ "Redistribute into level-2\n"
+ "Metric for redistributed routes\n"
+ "IS-IS default metric\n"
+ "Route map reference\n"
+ "Pointer to route-map entries\n")
+{
+ struct isis_redist_table_present_args rtda = {};
+ char xpath[XPATH_MAXLEN];
+ char xpath_entry[XPATH_MAXLEN + 128];
+ int rv;
+
+ rtda.rtda_table = table_str;
+ rtda.rtda_ip = ip;
+ rtda.rtda_level = level;
+
+ if (no) {
+ if (!isis_redist_table_is_present(vty, &rtda))
+ return CMD_WARNING_CONFIG_FAILED;
+
+ snprintf(xpath, sizeof(xpath), "./table[table='%s']", table_str);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+ rv = nb_cli_apply_changes(vty,
+ "./redistribute/%s[protocol='table'][level='%s']",
+ ip, level);
+ if (rv == CMD_SUCCESS) {
+ if (isis_redist_table_get_first(vty, &rtda) > 0)
+ return CMD_SUCCESS;
+ nb_cli_enqueue_change(vty, "./table", NB_OP_DESTROY,
+ NULL);
+ nb_cli_apply_changes(vty,
+ "./redistribute/%s[protocol='table'][level='%s']",
+ ip, level);
+ }
+ return CMD_SUCCESS;
+ }
+ if (isis_redist_table_is_present(vty, &rtda))
+ return CMD_SUCCESS;
+
+ snprintf(xpath, sizeof(xpath), "./table[table='%s']", table_str);
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_entry, sizeof(xpath_entry), "%s/route-map", xpath);
+ nb_cli_enqueue_change(vty, xpath_entry,
+ route_map ? NB_OP_MODIFY : NB_OP_DESTROY,
+ route_map ? route_map : NULL);
+ snprintf(xpath_entry, sizeof(xpath_entry), "%s/metric", xpath);
+ nb_cli_enqueue_change(vty, xpath_entry, NB_OP_MODIFY,
+ metric_str ? metric_str : NULL);
+ return nb_cli_apply_changes(vty,
+ "./redistribute/%s[protocol='table'][level='%s']",
+ ip, level);
+}
+
+static void vty_print_redistribute(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults, const char *family,
+ bool table)
+{
+ const char *level;
+ const char *protocol = NULL;
+ const char *routemap = NULL;
+ uint16_t tableid;
+
+ if (table) {
+ level = yang_dnode_get_string(dnode, "../level");
+ tableid = yang_dnode_get_uint16(dnode, "./table");
+ vty_out(vty, " redistribute %s table %d ", family, tableid);
+ } else {
+ protocol = yang_dnode_get_string(dnode, "./protocol");
+ if (!table && strmatch(protocol, "table"))
+ return;
+ level = yang_dnode_get_string(dnode, "./level");
+ vty_out(vty, " redistribute %s %s ", family, protocol);
+ }
+ vty_out(vty, "%s", level);
+ if (show_defaults || !yang_dnode_is_default(dnode, "./metric"))
+ vty_out(vty, " metric %s",
+ yang_dnode_get_string(dnode, "%s", "./metric"));
+
+ if (yang_dnode_exists(dnode, "./route-map"))
+ routemap = yang_dnode_get_string(dnode, "./route-map");
+ if (routemap)
+ vty_out(vty, " route-map %s", routemap);
+ vty_out(vty, "\n");
+}
+
+void cli_show_isis_redistribute_ipv4(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_print_redistribute(vty, dnode, show_defaults, "ipv4", false);
+}
+
+void cli_show_isis_redistribute_ipv6(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_print_redistribute(vty, dnode, show_defaults, "ipv6", false);
+}
+
+void cli_show_isis_redistribute_ipv4_table(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_print_redistribute(vty, dnode, show_defaults, "ipv4", true);
+}
+
+void cli_show_isis_redistribute_ipv6_table(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_print_redistribute(vty, dnode, show_defaults, "ipv6", true);
+}
+
+int cli_cmp_isis_redistribute_table(const struct lyd_node *dnode1,
+ const struct lyd_node *dnode2)
+{
+ uint16_t table1 = yang_dnode_get_uint16(dnode1, "./table");
+ uint16_t table2 = yang_dnode_get_uint16(dnode2, "./table");
+
+ return table1 - table2;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology
+ */
+DEFPY_YANG(
+ isis_topology, isis_topology_cmd,
+ "[no] topology <standard|ipv4-unicast|ipv4-mgmt|ipv6-unicast|ipv4-multicast|ipv6-multicast|ipv6-mgmt|ipv6-dstsrc>$topology [overload]$overload",
+ NO_STR
+ "Configure IS-IS topologies\n"
+ "standard topology\n"
+ "IPv4 unicast topology\n"
+ "IPv4 management topology\n"
+ "IPv6 unicast topology\n"
+ "IPv4 multicast topology\n"
+ "IPv6 multicast topology\n"
+ "IPv6 management topology\n"
+ "IPv6 dst-src topology\n"
+ "Set overload bit for topology\n")
+{
+ char base_xpath[XPATH_MAXLEN];
+
+ /* Since standard is not configurable it is not present in the
+ * YANG model, so we need to validate it here
+ */
+ if (strmatch(topology, "standard") ||
+ strmatch(topology, "ipv4-unicast")) {
+ vty_out(vty,
+ "Cannot configure IPv4 unicast (Standard) topology\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (strmatch(topology, "ipv4-mgmt"))
+ snprintf(base_xpath, XPATH_MAXLEN,
+ "./multi-topology/ipv4-management");
+ else if (strmatch(topology, "ipv6-mgmt"))
+ snprintf(base_xpath, XPATH_MAXLEN,
+ "./multi-topology/ipv6-management");
+ else
+ snprintf(base_xpath, XPATH_MAXLEN, "./multi-topology/%s",
+ topology);
+
+ if (no)
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+ else {
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./overload", NB_OP_MODIFY,
+ overload ? "true" : "false");
+ }
+
+ return nb_cli_apply_changes(vty, "%s", base_xpath);
+}
+
+void cli_show_isis_mt_ipv4_multicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " topology ipv4-multicast");
+ if (yang_dnode_get_bool(dnode, "./overload"))
+ vty_out(vty, " overload");
+ vty_out(vty, "\n");
+}
+
+void cli_show_isis_mt_ipv4_mgmt(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " topology ipv4-mgmt");
+ if (yang_dnode_get_bool(dnode, "./overload"))
+ vty_out(vty, " overload");
+ vty_out(vty, "\n");
+}
+
+void cli_show_isis_mt_ipv6_unicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " topology ipv6-unicast");
+ if (yang_dnode_get_bool(dnode, "./overload"))
+ vty_out(vty, " overload");
+ vty_out(vty, "\n");
+}
+
+void cli_show_isis_mt_ipv6_multicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " topology ipv6-multicast");
+ if (yang_dnode_get_bool(dnode, "./overload"))
+ vty_out(vty, " overload");
+ vty_out(vty, "\n");
+}
+
+void cli_show_isis_mt_ipv6_mgmt(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " topology ipv6-mgmt");
+ if (yang_dnode_get_bool(dnode, "./overload"))
+ vty_out(vty, " overload");
+ vty_out(vty, "\n");
+}
+
+void cli_show_isis_mt_ipv6_dstsrc(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " topology ipv6-dstsrc");
+ if (yang_dnode_get_bool(dnode, "./overload"))
+ vty_out(vty, " overload");
+ vty_out(vty, "\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/enabled
+ */
+DEFPY_YANG (isis_sr_enable,
+ isis_sr_enable_cmd,
+ "segment-routing on",
+ SR_STR
+ "Enable Segment Routing\n")
+{
+ nb_cli_enqueue_change(vty, "./segment-routing/enabled", NB_OP_MODIFY,
+ "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG (no_isis_sr_enable,
+ no_isis_sr_enable_cmd,
+ "no segment-routing [on]",
+ NO_STR
+ SR_STR
+ "Disable Segment Routing\n")
+{
+ nb_cli_enqueue_change(vty, "./segment-routing/enabled", NB_OP_MODIFY,
+ "false");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_sr_enabled(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+
+ vty_out(vty, " segment-routing on\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/label-block
+ */
+
+DEFPY_YANG(
+ isis_sr_global_block_label_range, isis_sr_global_block_label_range_cmd,
+ "segment-routing global-block (16-1048575)$gb_lower_bound (16-1048575)$gb_upper_bound [local-block (16-1048575)$lb_lower_bound (16-1048575)$lb_upper_bound]",
+ SR_STR
+ "Segment Routing Global Block label range\n"
+ "The lower bound of the global block\n"
+ "The upper bound of the global block (block size may not exceed 65535)\n"
+ "Segment Routing Local Block label range\n"
+ "The lower bound of the local block\n"
+ "The upper bound of the local block (block size may not exceed 65535)\n")
+{
+ nb_cli_enqueue_change(vty,
+ "./segment-routing/label-blocks/srgb/lower-bound",
+ NB_OP_MODIFY, gb_lower_bound_str);
+ nb_cli_enqueue_change(vty,
+ "./segment-routing/label-blocks/srgb/upper-bound",
+ NB_OP_MODIFY, gb_upper_bound_str);
+
+ nb_cli_enqueue_change(
+ vty, "./segment-routing/label-blocks/srlb/lower-bound",
+ NB_OP_MODIFY, lb_lower_bound ? lb_lower_bound_str : NULL);
+ nb_cli_enqueue_change(
+ vty, "./segment-routing/label-blocks/srlb/upper-bound",
+ NB_OP_MODIFY, lb_upper_bound ? lb_upper_bound_str : NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_isis_sr_global_block_label_range,
+ no_isis_sr_global_block_label_range_cmd,
+ "no segment-routing global-block [(16-1048575) (16-1048575) local-block (16-1048575) (16-1048575)]",
+ NO_STR SR_STR
+ "Segment Routing Global Block label range\n"
+ "The lower bound of the global block\n"
+ "The upper bound of the global block (block size may not exceed 65535)\n"
+ "Segment Routing Local Block label range\n"
+ "The lower bound of the local block\n"
+ "The upper bound of the local block (block size may not exceed 65535)\n")
+{
+ nb_cli_enqueue_change(vty,
+ "./segment-routing/label-blocks/srgb/lower-bound",
+ NB_OP_MODIFY, NULL);
+ nb_cli_enqueue_change(vty,
+ "./segment-routing/label-blocks/srgb/upper-bound",
+ NB_OP_MODIFY, NULL);
+ nb_cli_enqueue_change(vty,
+ "./segment-routing/label-blocks/srlb/lower-bound",
+ NB_OP_MODIFY, NULL);
+ nb_cli_enqueue_change(vty,
+ "./segment-routing/label-blocks/srlb/upper-bound",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_label_blocks(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " segment-routing global-block %s %s",
+ yang_dnode_get_string(dnode, "./srgb/lower-bound"),
+ yang_dnode_get_string(dnode, "./srgb/upper-bound"));
+ if (!yang_dnode_is_default(dnode, "./srlb/lower-bound")
+ || !yang_dnode_is_default(dnode, "./srlb/upper-bound"))
+ vty_out(vty, " local-block %s %s",
+ yang_dnode_get_string(dnode, "./srlb/lower-bound"),
+ yang_dnode_get_string(dnode, "./srlb/upper-bound"));
+ vty_out(vty, "\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/msd/node-msd
+ */
+DEFPY_YANG (isis_sr_node_msd,
+ isis_sr_node_msd_cmd,
+ "segment-routing node-msd (1-16)$msd",
+ SR_STR
+ "Maximum Stack Depth for this router\n"
+ "Maximum number of label that can be stack (1-16)\n")
+{
+ nb_cli_enqueue_change(vty, "./segment-routing/msd/node-msd",
+ NB_OP_MODIFY, msd_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG (no_isis_sr_node_msd,
+ no_isis_sr_node_msd_cmd,
+ "no segment-routing node-msd [(1-16)]",
+ NO_STR
+ SR_STR
+ "Maximum Stack Depth for this router\n"
+ "Maximum number of label that can be stack (1-16)\n")
+{
+ nb_cli_enqueue_change(vty, "./segment-routing/msd/node-msd",
+ NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_node_msd(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " segment-routing node-msd %s\n",
+ yang_dnode_get_string(dnode, NULL));
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid
+ */
+DEFPY_YANG (isis_sr_prefix_sid,
+ isis_sr_prefix_sid_cmd,
+ "segment-routing prefix\
+ <A.B.C.D/M|X:X::X:X/M>$prefix\
+ <absolute$sid_type (16-1048575)$sid_value|index$sid_type (0-65535)$sid_value>\
+ [<no-php-flag|explicit-null>$lh_behavior] [n-flag-clear$n_flag_clear]",
+ SR_STR
+ "Prefix SID\n"
+ "IPv4 Prefix\n"
+ "IPv6 Prefix\n"
+ "Specify the absolute value of Prefix Segment ID\n"
+ "The Prefix Segment ID value\n"
+ "Specify the index of Prefix Segment ID\n"
+ "The Prefix Segment ID index\n"
+ "Don't request Penultimate Hop Popping (PHP)\n"
+ "Upstream neighbor must replace prefix-sid with explicit null label\n"
+ "Not a node SID\n")
+{
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./sid-value-type", NB_OP_MODIFY, sid_type);
+ nb_cli_enqueue_change(vty, "./sid-value", NB_OP_MODIFY, sid_value_str);
+ if (lh_behavior) {
+ const char *value;
+
+ if (strmatch(lh_behavior, "no-php-flag"))
+ value = "no-php";
+ else if (strmatch(lh_behavior, "explicit-null"))
+ value = "explicit-null";
+ else
+ value = "php";
+
+ nb_cli_enqueue_change(vty, "./last-hop-behavior", NB_OP_MODIFY,
+ value);
+ } else
+ nb_cli_enqueue_change(vty, "./last-hop-behavior", NB_OP_MODIFY,
+ NULL);
+ nb_cli_enqueue_change(vty, "./n-flag-clear", NB_OP_MODIFY,
+ n_flag_clear ? "true" : "false");
+
+ return nb_cli_apply_changes(
+ vty, "./segment-routing/prefix-sid-map/prefix-sid[prefix='%s']",
+ prefix_str);
+}
+
+DEFPY_YANG (no_isis_sr_prefix_sid,
+ no_isis_sr_prefix_sid_cmd,
+ "no segment-routing prefix <A.B.C.D/M|X:X::X:X/M>$prefix\
+ [<absolute$sid_type (16-1048575)|index (0-65535)> [<no-php-flag|explicit-null>]]\
+ [n-flag-clear]",
+ NO_STR
+ SR_STR
+ "Prefix SID\n"
+ "IPv4 Prefix\n"
+ "IPv6 Prefix\n"
+ "Specify the absolute value of Prefix Segment ID\n"
+ "The Prefix Segment ID value\n"
+ "Specify the index of Prefix Segment ID\n"
+ "The Prefix Segment ID index\n"
+ "Don't request Penultimate Hop Popping (PHP)\n"
+ "Upstream neighbor must replace prefix-sid with explicit null label\n"
+ "Not a node SID\n")
+{
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(
+ vty, "./segment-routing/prefix-sid-map/prefix-sid[prefix='%s']",
+ prefix_str);
+}
+
+void cli_show_isis_prefix_sid(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *prefix;
+ const char *lh_behavior;
+ const char *sid_value_type;
+ const char *sid_value;
+ bool n_flag_clear;
+
+ prefix = yang_dnode_get_string(dnode, "./prefix");
+ lh_behavior = yang_dnode_get_string(dnode, "./last-hop-behavior");
+ sid_value_type = yang_dnode_get_string(dnode, "./sid-value-type");
+ sid_value = yang_dnode_get_string(dnode, "./sid-value");
+ n_flag_clear = yang_dnode_get_bool(dnode, "./n-flag-clear");
+
+ vty_out(vty, " segment-routing prefix %s", prefix);
+ if (strmatch(sid_value_type, "absolute"))
+ vty_out(vty, " absolute");
+ else
+ vty_out(vty, " index");
+ vty_out(vty, " %s", sid_value);
+ if (strmatch(lh_behavior, "no-php"))
+ vty_out(vty, " no-php-flag");
+ else if (strmatch(lh_behavior, "explicit-null"))
+ vty_out(vty, " explicit-null");
+ if (n_flag_clear)
+ vty_out(vty, " n-flag-clear");
+ vty_out(vty, "\n");
+}
+
+#ifndef FABRICD
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid
+ */
+DEFPY_YANG(
+ isis_sr_prefix_sid_algorithm, isis_sr_prefix_sid_algorithm_cmd,
+ "segment-routing prefix <A.B.C.D/M|X:X::X:X/M>$prefix\
+ algorithm (128-255)$algorithm\
+ <absolute$sid_type (16-1048575)$sid_value|index$sid_type (0-65535)$sid_value>\
+ [<no-php-flag|explicit-null>$lh_behavior] [n-flag-clear$n_flag_clear]",
+ SR_STR
+ "Prefix SID\n"
+ "IPv4 Prefix\n"
+ "IPv6 Prefix\n"
+ "Algorithm Specific Prefix SID Configuration\n"
+ "Algorithm number\n"
+ "Specify the absolute value of Prefix Segment ID\n"
+ "The Prefix Segment ID value\n"
+ "Specify the index of Prefix Segment ID\n"
+ "The Prefix Segment ID index\n"
+ "Don't request Penultimate Hop Popping (PHP)\n"
+ "Upstream neighbor must replace prefix-sid with explicit null label\n"
+ "Not a node SID\n")
+{
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./sid-value-type", NB_OP_MODIFY, sid_type);
+ nb_cli_enqueue_change(vty, "./sid-value", NB_OP_MODIFY, sid_value_str);
+ if (lh_behavior) {
+ const char *value;
+
+ if (strmatch(lh_behavior, "no-php-flag"))
+ value = "no-php";
+ else if (strmatch(lh_behavior, "explicit-null"))
+ value = "explicit-null";
+ else
+ value = "php";
+
+ nb_cli_enqueue_change(vty, "./last-hop-behavior", NB_OP_MODIFY,
+ value);
+ } else
+ nb_cli_enqueue_change(vty, "./last-hop-behavior", NB_OP_MODIFY,
+ NULL);
+ nb_cli_enqueue_change(vty, "./n-flag-clear", NB_OP_MODIFY,
+ n_flag_clear ? "true" : "false");
+
+ return nb_cli_apply_changes(
+ vty,
+ "./segment-routing/algorithm-prefix-sids/algorithm-prefix-sid[prefix='%s'][algo='%s']",
+ prefix_str, algorithm_str);
+}
+
+DEFPY_YANG(
+ no_isis_sr_prefix_algorithm_sid, no_isis_sr_prefix_sid_algorithm_cmd,
+ "no segment-routing prefix <A.B.C.D/M|X:X::X:X/M>$prefix\
+ algorithm (128-255)$algorithm\
+ [<absolute$sid_type (16-1048575)|index (0-65535)> [<no-php-flag|explicit-null>]]\
+ [n-flag-clear]",
+ NO_STR SR_STR
+ "Prefix SID\n"
+ "IPv4 Prefix\n"
+ "IPv6 Prefix\n"
+ "Algorithm Specific Prefix SID Configuration\n"
+ "Algorithm number\n"
+ "Specify the absolute value of Prefix Segment ID\n"
+ "The Prefix Segment ID value\n"
+ "Specify the index of Prefix Segment ID\n"
+ "The Prefix Segment ID index\n"
+ "Don't request Penultimate Hop Popping (PHP)\n"
+ "Upstream neighbor must replace prefix-sid with explicit null label\n"
+ "Not a node SID\n")
+{
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(
+ vty,
+ "./segment-routing/algorithm-prefix-sids/algorithm-prefix-sid[prefix='%s'][algo='%s']",
+ prefix_str, algorithm_str);
+ return CMD_SUCCESS;
+}
+#endif /* ifndef FABRICD */
+
+void cli_show_isis_prefix_sid_algorithm(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *prefix;
+ const char *lh_behavior;
+ const char *sid_value_type;
+ const char *sid_value;
+ bool n_flag_clear;
+ uint32_t algorithm;
+
+ prefix = yang_dnode_get_string(dnode, "./prefix");
+ sid_value_type = yang_dnode_get_string(dnode, "./sid-value-type");
+ sid_value = yang_dnode_get_string(dnode, "./sid-value");
+ algorithm = yang_dnode_get_uint32(dnode, "./algo");
+ lh_behavior = yang_dnode_get_string(dnode, "./last-hop-behavior");
+ n_flag_clear = yang_dnode_get_bool(dnode, "./n-flag-clear");
+
+ vty_out(vty, " segment-routing prefix %s", prefix);
+ vty_out(vty, " algorithm %u", algorithm);
+ if (strmatch(sid_value_type, "absolute"))
+ vty_out(vty, " absolute");
+ else
+ vty_out(vty, " index");
+ vty_out(vty, " %s", sid_value);
+
+ if (strmatch(lh_behavior, "no-php"))
+ vty_out(vty, " no-php-flag");
+ else if (strmatch(lh_behavior, "explicit-null"))
+ vty_out(vty, " explicit-null");
+ if (n_flag_clear)
+ vty_out(vty, " n-flag-clear");
+ vty_out(vty, "\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/locator
+ */
+DEFPY (isis_srv6_locator,
+ isis_srv6_locator_cmd,
+ "[no] locator NAME$loc_name",
+ NO_STR
+ "Specify SRv6 locator\n"
+ "Specify SRv6 locator\n")
+{
+ if (no)
+ nb_cli_enqueue_change(vty, "./locator", NB_OP_DESTROY, loc_name);
+ else
+ nb_cli_enqueue_change(vty, "./locator", NB_OP_MODIFY, loc_name);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_srv6_locator(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " locator %s\n", yang_dnode_get_string(dnode, NULL));
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/enabled
+ */
+DEFPY_YANG_NOSH (isis_srv6_enable,
+ isis_srv6_enable_cmd,
+ "segment-routing srv6",
+ SR_STR
+ "Enable Segment Routing over IPv6 (SRv6)\n")
+{
+ int ret;
+ char xpath[XPATH_MAXLEN + 37];
+
+ snprintf(xpath, sizeof(xpath), "%s/segment-routing-srv6",
+ VTY_CURR_XPATH);
+
+ nb_cli_enqueue_change(vty, "./segment-routing-srv6/enabled",
+ NB_OP_MODIFY, "true");
+
+ ret = nb_cli_apply_changes(vty, NULL);
+ if (ret == CMD_SUCCESS)
+ VTY_PUSH_XPATH(ISIS_SRV6_NODE, xpath);
+
+ return ret;
+}
+
+DEFPY_YANG (no_isis_srv6_enable,
+ no_isis_srv6_enable_cmd,
+ "no segment-routing srv6",
+ NO_STR
+ SR_STR
+ "Disable Segment Routing over IPv6 (SRv6)\n")
+{
+ nb_cli_enqueue_change(vty, "./segment-routing-srv6", NB_OP_DESTROY,
+ NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_srv6_enabled(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+
+ vty_out(vty, " segment-routing srv6\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd
+ */
+DEFPY_YANG_NOSH (isis_srv6_node_msd,
+ isis_srv6_node_msd_cmd,
+ "[no] node-msd",
+ NO_STR
+ "Segment Routing over IPv6 (SRv6) Maximum SRv6 SID Depths\n")
+{
+ int ret = CMD_SUCCESS;
+ char xpath[XPATH_MAXLEN + 37];
+
+ snprintf(xpath, sizeof(xpath), "%s/msd/node-msd", VTY_CURR_XPATH);
+
+ if (no) {
+ nb_cli_enqueue_change(vty, "./msd/node_msd/max-segs-left",
+ NB_OP_DESTROY, NULL);
+ nb_cli_enqueue_change(vty, "./msd/node_msd/end-pop",
+ NB_OP_DESTROY, NULL);
+ nb_cli_enqueue_change(vty, "./msd/node_msd/h-encaps",
+ NB_OP_DESTROY, NULL);
+ nb_cli_enqueue_change(vty, "./msd/node_msd/end-d",
+ NB_OP_DESTROY, NULL);
+ ret = nb_cli_apply_changes(vty, NULL);
+ } else
+ VTY_PUSH_XPATH(ISIS_SRV6_NODE_MSD_NODE, xpath);
+
+ return ret;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-segs-left
+ */
+DEFPY (isis_srv6_node_msd_max_segs_left,
+ isis_srv6_node_msd_max_segs_left_cmd,
+ "[no] max-segs-left (0-255)$max_segs_left",
+ NO_STR
+ "Specify Maximum Segments Left MSD\n"
+ "Specify Maximum Segments Left MSD\n")
+{
+ if (no)
+ nb_cli_enqueue_change(vty, "./max-segs-left", NB_OP_DESTROY,
+ NULL);
+ else
+ nb_cli_enqueue_change(vty, "./max-segs-left", NB_OP_MODIFY,
+ max_segs_left_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-pop
+ */
+DEFPY (isis_srv6_node_msd_max_end_pop,
+ isis_srv6_node_msd_max_end_pop_cmd,
+ "[no] max-end-pop (0-255)$max_end_pop",
+ NO_STR
+ "Specify Maximum End Pop MSD\n"
+ "Specify Maximum End Pop MSD\n")
+{
+ if (no)
+ nb_cli_enqueue_change(vty, "./max-end-pop", NB_OP_DESTROY, NULL);
+ else
+ nb_cli_enqueue_change(vty, "./max-end-pop", NB_OP_MODIFY,
+ max_end_pop_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-h-encaps
+ */
+DEFPY (isis_srv6_node_msd_max_h_encaps,
+ isis_srv6_node_msd_max_h_encaps_cmd,
+ "[no] max-h-encaps (0-255)$max_h_encaps",
+ NO_STR
+ "Specify Maximum H.Encaps MSD\n"
+ "Specify Maximum H.Encaps MSD\n")
+{
+ if (no)
+ nb_cli_enqueue_change(vty, "./max-h-encaps", NB_OP_DESTROY,
+ NULL);
+ else
+ nb_cli_enqueue_change(vty, "./max-h-encaps", NB_OP_MODIFY,
+ max_h_encaps_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-d
+ */
+DEFPY (isis_srv6_node_msd_max_end_d,
+ isis_srv6_node_msd_max_end_d_cmd,
+ "[no] max-end-d (0-255)$max_end_d",
+ NO_STR
+ "Specify Maximum End D MSD\n"
+ "Specify Maximum End D MSD\n")
+{
+ if (no)
+ nb_cli_enqueue_change(vty, "./max-end-d", NB_OP_DESTROY, NULL);
+ else
+ nb_cli_enqueue_change(vty, "./max-end-d", NB_OP_MODIFY,
+ max_end_d_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_srv6_node_msd(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " node-msd\n");
+ if (yang_dnode_get_uint8(dnode, "./max-segs-left") !=
+ yang_get_default_uint8("%s/msd/node-msd/max-segs-left", ISIS_SRV6))
+ vty_out(vty, " max-segs-left %u\n",
+ yang_dnode_get_uint8(dnode, "./max-segs-left"));
+ if (yang_dnode_get_uint8(dnode, "./max-end-pop") !=
+ yang_get_default_uint8("%s/msd/node-msd/max-end-pop", ISIS_SRV6))
+ vty_out(vty, " max-end-pop %u\n",
+ yang_dnode_get_uint8(dnode, "./max-end-pop"));
+ if (yang_dnode_get_uint8(dnode, "./max-h-encaps") !=
+ yang_get_default_uint8("%s/msd/node-msd/max-h-encaps", ISIS_SRV6))
+ vty_out(vty, " max-h-encaps %u\n",
+ yang_dnode_get_uint8(dnode, "./max-h-encaps"));
+ if (yang_dnode_get_uint8(dnode, "./max-end-d") !=
+ yang_get_default_uint8("%s/msd/node-msd/max-end-d", ISIS_SRV6))
+ vty_out(vty, " max-end-d %u\n",
+ yang_dnode_get_uint8(dnode, "./max-end-d"));
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/interface
+ */
+DEFPY (isis_srv6_interface,
+ isis_srv6_interface_cmd,
+ "[no] interface WORD$interface",
+ NO_STR
+ "Interface for Segment Routing over IPv6 (SRv6)\n"
+ "Interface for Segment Routing over IPv6 (SRv6)\n")
+{
+ if (no) {
+ nb_cli_enqueue_change(vty, "./interface",
+ NB_OP_MODIFY, NULL);
+ } else {
+ nb_cli_enqueue_change(vty, "./interface",
+ NB_OP_MODIFY, interface);
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_srv6_interface(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " interface %s\n", yang_dnode_get_string(dnode, NULL));
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-{1,2}/lfa/priority-limit
+ */
+DEFPY_YANG (isis_frr_lfa_priority_limit,
+ isis_frr_lfa_priority_limit_cmd,
+ "[no] fast-reroute priority-limit <critical|high|medium>$priority [<level-1|level-2>$level]",
+ NO_STR
+ "Configure Fast ReRoute\n"
+ "Limit backup computation up to the prefix priority\n"
+ "Compute for critical priority prefixes only\n"
+ "Compute for critical & high priority prefixes\n"
+ "Compute for critical, high & medium priority prefixes\n"
+ "Set priority-limit for level-1 only\n"
+ "Set priority-limit for level-2 only\n")
+{
+ if (!level || strmatch(level, "level-1")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./fast-reroute/level-1/lfa/priority-limit",
+ NB_OP_DESTROY, NULL);
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./fast-reroute/level-1/lfa/priority-limit",
+ NB_OP_CREATE, priority);
+ }
+ }
+ if (!level || strmatch(level, "level-2")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./fast-reroute/level-2/lfa/priority-limit",
+ NB_OP_DESTROY, NULL);
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./fast-reroute/level-2/lfa/priority-limit",
+ NB_OP_CREATE, priority);
+ }
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_frr_lfa_priority_limit(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " fast-reroute priority-limit %s %s\n",
+ yang_dnode_get_string(dnode, NULL),
+ dnode->parent->parent->schema->name);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-{1,2}/lfa/tiebreaker
+ */
+DEFPY_YANG (isis_frr_lfa_tiebreaker,
+ isis_frr_lfa_tiebreaker_cmd,
+ "[no] fast-reroute lfa\
+ tiebreaker <downstream|lowest-backup-metric|node-protecting>$type\
+ index (1-255)$index\
+ [<level-1|level-2>$level]",
+ NO_STR
+ "Configure Fast ReRoute\n"
+ "LFA configuration\n"
+ "Configure tiebreaker for multiple backups\n"
+ "Prefer backup path via downstream node\n"
+ "Prefer backup path with lowest total metric\n"
+ "Prefer node protecting backup path\n"
+ "Set preference order among tiebreakers\n"
+ "Index\n"
+ "Configure tiebreaker for level-1 only\n"
+ "Configure tiebreaker for level-2 only\n")
+{
+ char xpath[XPATH_MAXLEN];
+
+ if (!level || strmatch(level, "level-1")) {
+ if (no) {
+ snprintf(
+ xpath, XPATH_MAXLEN,
+ "./fast-reroute/level-1/lfa/tiebreaker[index='%s']",
+ index_str);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+ } else {
+ snprintf(
+ xpath, XPATH_MAXLEN,
+ "./fast-reroute/level-1/lfa/tiebreaker[index='%s']/type",
+ index_str);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, type);
+ }
+ }
+ if (!level || strmatch(level, "level-2")) {
+ if (no) {
+ snprintf(
+ xpath, XPATH_MAXLEN,
+ "./fast-reroute/level-2/lfa/tiebreaker[index='%s']",
+ index_str);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+ } else {
+ snprintf(
+ xpath, XPATH_MAXLEN,
+ "./fast-reroute/level-2/lfa/tiebreaker[index='%s']/type",
+ index_str);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, type);
+ }
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_frr_lfa_tiebreaker(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " fast-reroute lfa tiebreaker %s index %s %s\n",
+ yang_dnode_get_string(dnode, "./type"),
+ yang_dnode_get_string(dnode, "./index"),
+ dnode->parent->parent->schema->name);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-{1,2}/lfa/load-sharing
+ */
+DEFPY_YANG (isis_frr_lfa_load_sharing,
+ isis_frr_lfa_load_sharing_cmd,
+ "[no] fast-reroute load-sharing disable [<level-1|level-2>$level]",
+ NO_STR
+ "Configure Fast ReRoute\n"
+ "Load share prefixes across multiple backups\n"
+ "Disable load sharing\n"
+ "Disable load sharing for level-1 only\n"
+ "Disable load sharing for level-2 only\n")
+{
+ if (!level || strmatch(level, "level-1")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty, "./fast-reroute/level-1/lfa/load-sharing",
+ NB_OP_MODIFY, "true");
+ } else {
+ nb_cli_enqueue_change(
+ vty, "./fast-reroute/level-1/lfa/load-sharing",
+ NB_OP_MODIFY, "false");
+ }
+ }
+ if (!level || strmatch(level, "level-2")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty, "./fast-reroute/level-2/lfa/load-sharing",
+ NB_OP_MODIFY, "true");
+ } else {
+ nb_cli_enqueue_change(
+ vty, "./fast-reroute/level-2/lfa/load-sharing",
+ NB_OP_MODIFY, "false");
+ }
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_frr_lfa_load_sharing(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+
+ vty_out(vty, " fast-reroute load-sharing disable %s\n",
+ dnode->parent->parent->schema->name);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-{1,2}/remote-lfa/prefix-list
+ */
+DEFPY_YANG (isis_frr_remote_lfa_plist,
+ isis_frr_remote_lfa_plist_cmd,
+ "fast-reroute remote-lfa prefix-list WORD$plist [<level-1|level-2>$level]",
+ "Configure Fast ReRoute\n"
+ "Enable remote LFA related configuration\n"
+ "Filter PQ node router ID based on prefix list\n"
+ "Prefix-list name\n"
+ "Enable router ID filtering for level-1 only\n"
+ "Enable router ID filtering for level-2 only\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(
+ vty, "./fast-reroute/level-1/remote-lfa/prefix-list",
+ NB_OP_MODIFY, plist);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(
+ vty, "./fast-reroute/level-2/remote-lfa/prefix-list",
+ NB_OP_MODIFY, plist);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG (no_isis_frr_remote_lfa_plist,
+ no_isis_frr_remote_lfa_plist_cmd,
+ "no fast-reroute remote-lfa prefix-list [WORD] [<level-1|level-2>$level]",
+ NO_STR
+ "Configure Fast ReRoute\n"
+ "Enable remote LFA related configuration\n"
+ "Filter PQ node router ID based on prefix list\n"
+ "Prefix-list name\n"
+ "Enable router ID filtering for level-1 only\n"
+ "Enable router ID filtering for level-2 only\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(
+ vty, "./fast-reroute/level-1/remote-lfa/prefix-list",
+ NB_OP_DESTROY, NULL);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(
+ vty, "./fast-reroute/level-2/remote-lfa/prefix-list",
+ NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_frr_remote_lfa_plist(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " fast-reroute remote-lfa prefix-list %s %s\n",
+ yang_dnode_get_string(dnode, NULL),
+ dnode->parent->parent->schema->name);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/passive
+ */
+DEFPY_YANG(isis_passive, isis_passive_cmd, "[no] isis passive",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Configure the passive mode for interface\n")
+{
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_passive(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " isis passive\n");
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/password
+ */
+
+DEFPY_YANG(isis_passwd, isis_passwd_cmd, "isis password <md5|clear>$type WORD$pwd",
+ "IS-IS routing protocol\n"
+ "Configure the authentication password for a circuit\n"
+ "HMAC-MD5 authentication\n"
+ "Cleartext password\n"
+ "Circuit password\n")
+{
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/password", NB_OP_CREATE,
+ NULL);
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/password/password",
+ NB_OP_MODIFY, pwd);
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/password/password-type",
+ NB_OP_MODIFY, type);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_isis_passwd, no_isis_passwd_cmd, "no isis password [<md5|clear> WORD]",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Configure the authentication password for a circuit\n"
+ "HMAC-MD5 authentication\n"
+ "Cleartext password\n"
+ "Circuit password\n")
+{
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/password", NB_OP_DESTROY,
+ NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_password(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " isis password %s %s\n",
+ yang_dnode_get_string(dnode, "./password-type"),
+ yang_dnode_get_string(dnode, "./password"));
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/metric
+ */
+DEFPY_YANG(isis_metric, isis_metric_cmd,
+ "isis metric [level-1|level-2]$level (0-16777215)$met",
+ "IS-IS routing protocol\n"
+ "Set default metric for circuit\n"
+ "Specify metric for level-1 routing\n"
+ "Specify metric for level-2 routing\n"
+ "Default metric value\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-1",
+ NB_OP_MODIFY, met_str);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-2",
+ NB_OP_MODIFY, met_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_isis_metric, no_isis_metric_cmd,
+ "no isis metric [level-1|level-2]$level [(0-16777215)]",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Set default metric for circuit\n"
+ "Specify metric for level-1 routing\n"
+ "Specify metric for level-2 routing\n"
+ "Default metric value\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-1",
+ NB_OP_MODIFY, NULL);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-2",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_metric(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *l1 = yang_dnode_get_string(dnode, "./level-1");
+ const char *l2 = yang_dnode_get_string(dnode, "./level-2");
+
+ if (strmatch(l1, l2))
+ vty_out(vty, " isis metric %s\n", l1);
+ else {
+ vty_out(vty, " isis metric level-1 %s\n", l1);
+ vty_out(vty, " isis metric level-2 %s\n", l2);
+ }
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/interval
+ */
+DEFPY_YANG(isis_hello_interval, isis_hello_interval_cmd,
+ "isis hello-interval [level-1|level-2]$level (1-600)$intv",
+ "IS-IS routing protocol\n"
+ "Set Hello interval\n"
+ "Specify hello-interval for level-1 IIHs\n"
+ "Specify hello-interval for level-2 IIHs\n"
+ "Holdtime 1 seconds, interval depends on multiplier\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/hello/interval/level-1",
+ NB_OP_MODIFY, intv_str);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/hello/interval/level-2",
+ NB_OP_MODIFY, intv_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_isis_hello_interval, no_isis_hello_interval_cmd,
+ "no isis hello-interval [level-1|level-2]$level [(1-600)]",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Set Hello interval\n"
+ "Specify hello-interval for level-1 IIHs\n"
+ "Specify hello-interval for level-2 IIHs\n"
+ "Holdtime 1 second, interval depends on multiplier\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/hello/interval/level-1",
+ NB_OP_MODIFY, NULL);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/hello/interval/level-2",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_hello_interval(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *l1 = yang_dnode_get_string(dnode, "./level-1");
+ const char *l2 = yang_dnode_get_string(dnode, "./level-2");
+
+ if (strmatch(l1, l2))
+ vty_out(vty, " isis hello-interval %s\n", l1);
+ else {
+ vty_out(vty, " isis hello-interval level-1 %s\n", l1);
+ vty_out(vty, " isis hello-interval level-2 %s\n", l2);
+ }
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/multiplier
+ */
+DEFPY_YANG(isis_hello_multiplier, isis_hello_multiplier_cmd,
+ "isis hello-multiplier [level-1|level-2]$level (2-100)$mult",
+ "IS-IS routing protocol\n"
+ "Set multiplier for Hello holding time\n"
+ "Specify hello multiplier for level-1 IIHs\n"
+ "Specify hello multiplier for level-2 IIHs\n"
+ "Hello multiplier value\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(
+ vty, "./frr-isisd:isis/hello/multiplier/level-1",
+ NB_OP_MODIFY, mult_str);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(
+ vty, "./frr-isisd:isis/hello/multiplier/level-2",
+ NB_OP_MODIFY, mult_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_isis_hello_multiplier, no_isis_hello_multiplier_cmd,
+ "no isis hello-multiplier [level-1|level-2]$level [(2-100)]",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Set multiplier for Hello holding time\n"
+ "Specify hello multiplier for level-1 IIHs\n"
+ "Specify hello multiplier for level-2 IIHs\n"
+ "Hello multiplier value\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(
+ vty, "./frr-isisd:isis/hello/multiplier/level-1",
+ NB_OP_MODIFY, NULL);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(
+ vty, "./frr-isisd:isis/hello/multiplier/level-2",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_hello_multi(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *l1 = yang_dnode_get_string(dnode, "./level-1");
+ const char *l2 = yang_dnode_get_string(dnode, "./level-2");
+
+ if (strmatch(l1, l2))
+ vty_out(vty, " isis hello-multiplier %s\n", l1);
+ else {
+ vty_out(vty, " isis hello-multiplier level-1 %s\n", l1);
+ vty_out(vty, " isis hello-multiplier level-2 %s\n", l2);
+ }
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/disable-three-way-handshake
+ */
+DEFPY_YANG(isis_threeway_adj, isis_threeway_adj_cmd, "[no] isis three-way-handshake",
+ NO_STR
+ "IS-IS commands\n"
+ "Enable/Disable three-way handshake\n")
+{
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/disable-three-way-handshake",
+ NB_OP_MODIFY, no ? "true" : "false");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_threeway_shake(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " isis three-way-handshake\n");
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/padding
+ */
+DEFPY_YANG(isis_hello_padding, isis_hello_padding_cmd,
+ "[no] isis hello padding [during-adjacency-formation]$padding_type",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Type of padding for IS-IS hello packets\n"
+ "Pad hello packets\n"
+ "Add padding to hello packets during adjacency formation only.\n")
+{
+ if (no) {
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/hello/padding",
+ NB_OP_MODIFY, "disabled");
+ } else {
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/hello/padding",
+ NB_OP_MODIFY,
+ padding_type ? padding_type : "always");
+ }
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_hello_padding(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ int hello_padding_type = yang_dnode_get_enum(dnode, NULL);
+ if (hello_padding_type == ISIS_HELLO_PADDING_DISABLED)
+ vty_out(vty, " no");
+ vty_out(vty, " isis hello padding");
+ if (hello_padding_type == ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION)
+ vty_out(vty, " during-adjacency-formation");
+ vty_out(vty, "\n");
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/csnp-interval
+ */
+DEFPY_YANG(csnp_interval, csnp_interval_cmd,
+ "isis csnp-interval (1-600)$intv [level-1|level-2]$level",
+ "IS-IS routing protocol\n"
+ "Set CSNP interval in seconds\n"
+ "CSNP interval value\n"
+ "Specify interval for level-1 CSNPs\n"
+ "Specify interval for level-2 CSNPs\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/csnp-interval/level-1",
+ NB_OP_MODIFY, intv_str);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/csnp-interval/level-2",
+ NB_OP_MODIFY, intv_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_csnp_interval, no_csnp_interval_cmd,
+ "no isis csnp-interval [(1-600)] [level-1|level-2]$level",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Set CSNP interval in seconds\n"
+ "CSNP interval value\n"
+ "Specify interval for level-1 CSNPs\n"
+ "Specify interval for level-2 CSNPs\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/csnp-interval/level-1",
+ NB_OP_MODIFY, NULL);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/csnp-interval/level-2",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_csnp_interval(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *l1 = yang_dnode_get_string(dnode, "./level-1");
+ const char *l2 = yang_dnode_get_string(dnode, "./level-2");
+
+ if (strmatch(l1, l2))
+ vty_out(vty, " isis csnp-interval %s\n", l1);
+ else {
+ vty_out(vty, " isis csnp-interval %s level-1\n", l1);
+ vty_out(vty, " isis csnp-interval %s level-2\n", l2);
+ }
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/psnp-interval
+ */
+DEFPY_YANG(psnp_interval, psnp_interval_cmd,
+ "isis psnp-interval (1-120)$intv [level-1|level-2]$level",
+ "IS-IS routing protocol\n"
+ "Set PSNP interval in seconds\n"
+ "PSNP interval value\n"
+ "Specify interval for level-1 PSNPs\n"
+ "Specify interval for level-2 PSNPs\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/psnp-interval/level-1",
+ NB_OP_MODIFY, intv_str);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/psnp-interval/level-2",
+ NB_OP_MODIFY, intv_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_psnp_interval, no_psnp_interval_cmd,
+ "no isis psnp-interval [(1-120)] [level-1|level-2]$level",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Set PSNP interval in seconds\n"
+ "PSNP interval value\n"
+ "Specify interval for level-1 PSNPs\n"
+ "Specify interval for level-2 PSNPs\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/psnp-interval/level-1",
+ NB_OP_MODIFY, NULL);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty,
+ "./frr-isisd:isis/psnp-interval/level-2",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_psnp_interval(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *l1 = yang_dnode_get_string(dnode, "./level-1");
+ const char *l2 = yang_dnode_get_string(dnode, "./level-2");
+
+ if (strmatch(l1, l2))
+ vty_out(vty, " isis psnp-interval %s\n", l1);
+ else {
+ vty_out(vty, " isis psnp-interval %s level-1\n", l1);
+ vty_out(vty, " isis psnp-interval %s level-2\n", l2);
+ }
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/multi-topology
+ */
+DEFPY_YANG(circuit_topology, circuit_topology_cmd,
+ "[no] isis topology<standard|ipv4-unicast|ipv4-mgmt|ipv6-unicast|ipv4-multicast|ipv6-multicast|ipv6-mgmt|ipv6-dstsrc>$topology",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Configure interface IS-IS topologies\n"
+ "Standard topology\n"
+ "IPv4 unicast topology\n"
+ "IPv4 management topology\n"
+ "IPv6 unicast topology\n"
+ "IPv4 multicast topology\n"
+ "IPv6 multicast topology\n"
+ "IPv6 management topology\n"
+ "IPv6 dst-src topology\n")
+{
+ nb_cli_enqueue_change(vty, ".", NB_OP_MODIFY, no ? "false" : "true");
+
+ if (strmatch(topology, "ipv4-mgmt"))
+ return nb_cli_apply_changes(
+ vty, "./frr-isisd:isis/multi-topology/ipv4-management");
+ else if (strmatch(topology, "ipv6-mgmt"))
+ return nb_cli_apply_changes(
+ vty, "./frr-isisd:isis/multi-topology/ipv6-management");
+ if (strmatch(topology, "ipv4-unicast"))
+ return nb_cli_apply_changes(
+ vty, "./frr-isisd:isis/multi-topology/standard");
+ else
+ return nb_cli_apply_changes(
+ vty, "./frr-isisd:isis/multi-topology/%s", topology);
+}
+
+void cli_show_ip_isis_mt_standard(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " isis topology standard\n");
+}
+
+void cli_show_ip_isis_mt_ipv4_multicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " isis topology ipv4-multicast\n");
+}
+
+void cli_show_ip_isis_mt_ipv4_mgmt(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " isis topology ipv4-mgmt\n");
+}
+
+void cli_show_ip_isis_mt_ipv6_unicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " isis topology ipv6-unicast\n");
+}
+
+void cli_show_ip_isis_mt_ipv6_multicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " isis topology ipv6-multicast\n");
+}
+
+void cli_show_ip_isis_mt_ipv6_mgmt(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " isis topology ipv6-mgmt\n");
+}
+
+void cli_show_ip_isis_mt_ipv6_dstsrc(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " isis topology ipv6-dstsrc\n");
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/circuit-type
+ */
+DEFPY_YANG(isis_circuit_type, isis_circuit_type_cmd,
+ "isis circuit-type <level-1|level-1-2|level-2-only>$type",
+ "IS-IS routing protocol\n"
+ "Configure circuit type for interface\n"
+ "Level-1 only adjacencies are formed\n"
+ "Level-1-2 adjacencies are formed\n"
+ "Level-2 only adjacencies are formed\n")
+{
+ nb_cli_enqueue_change(
+ vty, "./frr-isisd:isis/circuit-type", NB_OP_MODIFY,
+ strmatch(type, "level-2-only") ? "level-2" : type);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_isis_circuit_type, no_isis_circuit_type_cmd,
+ "no isis circuit-type [level-1|level-1-2|level-2-only]",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Configure circuit type for interface\n"
+ "Level-1 only adjacencies are formed\n"
+ "Level-1-2 adjacencies are formed\n"
+ "Level-2 only adjacencies are formed\n")
+{
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/circuit-type",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_circ_type(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ int level = yang_dnode_get_enum(dnode, NULL);
+
+ switch (level) {
+ case IS_LEVEL_1:
+ vty_out(vty, " isis circuit-type level-1\n");
+ break;
+ case IS_LEVEL_2:
+ vty_out(vty, " isis circuit-type level-2-only\n");
+ break;
+ case IS_LEVEL_1_AND_2:
+ vty_out(vty, " isis circuit-type level-1-2\n");
+ break;
+ }
+}
+
+static int ag_change(struct vty *vty, int argc, struct cmd_token **argv,
+ const char *xpath, bool no, int start_idx)
+{
+ for (int i = start_idx; i < argc; i++)
+ nb_cli_enqueue_change(vty, xpath,
+ no ? NB_OP_DESTROY : NB_OP_CREATE,
+ argv[i]->arg);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+static int ag_iter_cb(const struct lyd_node *dnode, void *arg)
+{
+ struct vty *vty = (struct vty *)arg;
+
+ vty_out(vty, " %s", yang_dnode_get_string(dnode, "."));
+ return YANG_ITER_CONTINUE;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/network-type
+ */
+DEFPY_YANG(isis_network, isis_network_cmd, "[no] isis network point-to-point",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Set network type\n"
+ "point-to-point network type\n")
+{
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/network-type",
+ NB_OP_MODIFY,
+ no ? "broadcast" : "point-to-point");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_network_type(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (yang_dnode_get_enum(dnode, NULL) != CIRCUIT_T_P2P)
+ vty_out(vty, " no");
+
+ vty_out(vty, " isis network point-to-point\n");
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/priority
+ */
+DEFPY_YANG(isis_priority, isis_priority_cmd,
+ "isis priority (0-127)$prio [level-1|level-2]$level",
+ "IS-IS routing protocol\n"
+ "Set priority for Designated Router election\n"
+ "Priority value\n"
+ "Specify priority for level-1 routing\n"
+ "Specify priority for level-2 routing\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-1",
+ NB_OP_MODIFY, prio_str);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-2",
+ NB_OP_MODIFY, prio_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(no_isis_priority, no_isis_priority_cmd,
+ "no isis priority [(0-127)] [level-1|level-2]$level",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Set priority for Designated Router election\n"
+ "Priority value\n"
+ "Specify priority for level-1 routing\n"
+ "Specify priority for level-2 routing\n")
+{
+ if (!level || strmatch(level, "level-1"))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-1",
+ NB_OP_MODIFY, NULL);
+ if (!level || strmatch(level, "level-2"))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-2",
+ NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_ip_isis_priority(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *l1 = yang_dnode_get_string(dnode, "./level-1");
+ const char *l2 = yang_dnode_get_string(dnode, "./level-2");
+
+ if (strmatch(l1, l2))
+ vty_out(vty, " isis priority %s\n", l1);
+ else {
+ vty_out(vty, " isis priority %s level-1\n", l1);
+ vty_out(vty, " isis priority %s level-2\n", l2);
+ }
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/fast-reroute
+ */
+void cli_show_ip_isis_frr(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ bool l1_enabled, l2_enabled;
+ bool l1_node_protection, l2_node_protection;
+ bool l1_link_fallback, l2_link_fallback;
+
+ /* Classic LFA */
+ l1_enabled = yang_dnode_get_bool(dnode, "./level-1/lfa/enable");
+ l2_enabled = yang_dnode_get_bool(dnode, "./level-2/lfa/enable");
+
+ if (l1_enabled || l2_enabled) {
+ if (l1_enabled == l2_enabled) {
+ vty_out(vty, " isis fast-reroute lfa\n");
+ vty_out(vty, "\n");
+ } else {
+ if (l1_enabled)
+ vty_out(vty,
+ " isis fast-reroute lfa level-1\n");
+ if (l2_enabled)
+ vty_out(vty,
+ " isis fast-reroute lfa level-2\n");
+ }
+ }
+
+ /* Remote LFA */
+ l1_enabled = yang_dnode_get_bool(dnode, "./level-1/remote-lfa/enable");
+ l2_enabled = yang_dnode_get_bool(dnode, "./level-2/remote-lfa/enable");
+
+ if (l1_enabled || l2_enabled) {
+ if (l1_enabled == l2_enabled) {
+ vty_out(vty,
+ " isis fast-reroute remote-lfa tunnel mpls-ldp\n");
+ vty_out(vty, "\n");
+ } else {
+ if (l1_enabled)
+ vty_out(vty,
+ " isis fast-reroute remote-lfa tunnel mpls-ldp level-1\n");
+ if (l2_enabled)
+ vty_out(vty,
+ " isis fast-reroute remote-lfa tunnel mpls-ldp level-2\n");
+ }
+ }
+
+ /* TI-LFA */
+ l1_enabled = yang_dnode_get_bool(dnode, "./level-1/ti-lfa/enable");
+ l2_enabled = yang_dnode_get_bool(dnode, "./level-2/ti-lfa/enable");
+ l1_node_protection =
+ yang_dnode_get_bool(dnode, "./level-1/ti-lfa/node-protection");
+ l2_node_protection =
+ yang_dnode_get_bool(dnode, "./level-2/ti-lfa/node-protection");
+ l1_link_fallback =
+ yang_dnode_get_bool(dnode, "./level-1/ti-lfa/link-fallback");
+ l2_link_fallback =
+ yang_dnode_get_bool(dnode, "./level-2/ti-lfa/link-fallback");
+
+
+ if (l1_enabled || l2_enabled) {
+ if (l1_enabled == l2_enabled
+ && l1_node_protection == l2_node_protection
+ && l1_link_fallback == l2_link_fallback) {
+ vty_out(vty, " isis fast-reroute ti-lfa");
+ if (l1_node_protection)
+ vty_out(vty, " node-protection");
+ if (l1_link_fallback)
+ vty_out(vty, " link-fallback");
+ vty_out(vty, "\n");
+ } else {
+ if (l1_enabled) {
+ vty_out(vty,
+ " isis fast-reroute ti-lfa level-1");
+ if (l1_node_protection)
+ vty_out(vty, " node-protection");
+ if (l1_link_fallback)
+ vty_out(vty, " link-fallback");
+ vty_out(vty, "\n");
+ }
+ if (l2_enabled) {
+ vty_out(vty,
+ " isis fast-reroute ti-lfa level-2");
+ if (l2_node_protection)
+ vty_out(vty, " node-protection");
+ if (l2_link_fallback)
+ vty_out(vty, " link-fallback");
+ vty_out(vty, "\n");
+ }
+ }
+ }
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-{1,2}/lfa/enable
+ */
+DEFPY(isis_lfa, isis_lfa_cmd,
+ "[no] isis fast-reroute lfa [level-1|level-2]$level",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Interface IP Fast-reroute configuration\n"
+ "Enable LFA computation\n"
+ "Enable LFA computation for Level 1 only\n"
+ "Enable LFA computation for Level 2 only\n")
+{
+ if (!level || strmatch(level, "level-1")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/lfa/enable",
+ NB_OP_MODIFY, "false");
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/lfa/enable",
+ NB_OP_MODIFY, "true");
+ }
+ }
+ if (!level || strmatch(level, "level-2")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/lfa/enable",
+ NB_OP_MODIFY, "false");
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/lfa/enable",
+ NB_OP_MODIFY, "true");
+ }
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-{1,2}/lfa/exclude-interface
+ */
+DEFPY(isis_lfa_exclude_interface, isis_lfa_exclude_interface_cmd,
+ "[no] isis fast-reroute lfa [level-1|level-2]$level exclude interface IFNAME$ifname",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Interface IP Fast-reroute configuration\n"
+ "Enable LFA computation\n"
+ "Enable LFA computation for Level 1 only\n"
+ "Enable LFA computation for Level 2 only\n"
+ "FRR exclusion information\n"
+ "Exclude an interface from computation\n"
+ "Interface name\n")
+{
+ if (!level || strmatch(level, "level-1")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/lfa/exclude-interface",
+ NB_OP_DESTROY, ifname);
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/lfa/exclude-interface",
+ NB_OP_CREATE, ifname);
+ }
+ }
+ if (!level || strmatch(level, "level-2")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/lfa/exclude-interface",
+ NB_OP_DESTROY, ifname);
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/lfa/exclude-interface",
+ NB_OP_CREATE, ifname);
+ }
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_frr_lfa_exclude_interface(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " isis fast-reroute lfa %s exclude interface %s\n",
+ dnode->parent->parent->schema->name,
+ yang_dnode_get_string(dnode, NULL));
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-{1,2}/remote-lfa/enable
+ */
+DEFPY(isis_remote_lfa, isis_remote_lfa_cmd,
+ "[no] isis fast-reroute remote-lfa tunnel mpls-ldp [level-1|level-2]$level",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Interface IP Fast-reroute configuration\n"
+ "Enable remote LFA computation\n"
+ "Enable remote LFA computation using tunnels\n"
+ "Use MPLS LDP tunnel to reach the remote LFA node\n"
+ "Enable LFA computation for Level 1 only\n"
+ "Enable LFA computation for Level 2 only\n")
+{
+ if (!level || strmatch(level, "level-1")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/remote-lfa/enable",
+ NB_OP_MODIFY, "false");
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/remote-lfa/enable",
+ NB_OP_MODIFY, "true");
+ }
+ }
+ if (!level || strmatch(level, "level-2")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/remote-lfa/enable",
+ NB_OP_MODIFY, "false");
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/remote-lfa/enable",
+ NB_OP_MODIFY, "true");
+ }
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-{1,2}/remote-lfa/maximum-metric
+ */
+DEFPY(isis_remote_lfa_max_metric, isis_remote_lfa_max_metric_cmd,
+ "[no] isis fast-reroute remote-lfa maximum-metric (1-16777215)$metric [level-1|level-2]$level",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Interface IP Fast-reroute configuration\n"
+ "Enable remote LFA computation\n"
+ "Limit remote LFA node selection within the metric\n"
+ "Value of the metric\n"
+ "Enable LFA computation for Level 1 only\n"
+ "Enable LFA computation for Level 2 only\n")
+{
+ if (!level || strmatch(level, "level-1")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/remote-lfa/maximum-metric",
+ NB_OP_DESTROY, NULL);
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/remote-lfa/maximum-metric",
+ NB_OP_MODIFY, metric_str);
+ }
+ }
+ if (!level || strmatch(level, "level-2")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/remote-lfa/maximum-metric",
+ NB_OP_DESTROY, NULL);
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/remote-lfa/maximum-metric",
+ NB_OP_MODIFY, metric_str);
+ }
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_frr_remote_lfa_max_metric(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " isis fast-reroute remote-lfa maximum-metric %s %s\n",
+ yang_dnode_get_string(dnode, NULL),
+ dnode->parent->parent->schema->name);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-{1,2}/ti-lfa/enable
+ */
+DEFPY(isis_ti_lfa, isis_ti_lfa_cmd,
+ "[no] isis fast-reroute ti-lfa [level-1|level-2]$level [node-protection$node_protection [link-fallback$link_fallback]]",
+ NO_STR
+ "IS-IS routing protocol\n"
+ "Interface IP Fast-reroute configuration\n"
+ "Enable TI-LFA computation\n"
+ "Enable TI-LFA computation for Level 1 only\n"
+ "Enable TI-LFA computation for Level 2 only\n"
+ "Protect against node failures\n"
+ "Enable link-protection fallback\n")
+{
+ if (!level || strmatch(level, "level-1")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/enable",
+ NB_OP_MODIFY, "false");
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/node-protection",
+ NB_OP_MODIFY, "false");
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/link-fallback",
+ NB_OP_MODIFY, "false");
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/enable",
+ NB_OP_MODIFY, "true");
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/node-protection",
+ NB_OP_MODIFY,
+ node_protection ? "true" : "false");
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/link-fallback",
+ NB_OP_MODIFY, link_fallback ? "true" : "false");
+ }
+ }
+ if (!level || strmatch(level, "level-2")) {
+ if (no) {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/enable",
+ NB_OP_MODIFY, "false");
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/node-protection",
+ NB_OP_MODIFY, "false");
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/link-fallback",
+ NB_OP_MODIFY, "false");
+ } else {
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/enable",
+ NB_OP_MODIFY, "true");
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/node-protection",
+ NB_OP_MODIFY,
+ node_protection ? "true" : "false");
+ nb_cli_enqueue_change(
+ vty,
+ "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/link-fallback",
+ NB_OP_MODIFY, link_fallback ? "true" : "false");
+ }
+ }
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/log-adjacency-changes
+ */
+DEFPY_YANG(log_adj_changes, log_adj_changes_cmd, "[no] log-adjacency-changes",
+ NO_STR "Log changes in adjacency state\n")
+{
+ nb_cli_enqueue_change(vty, "./log-adjacency-changes", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_log_adjacency(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " log-adjacency-changes\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/log-pdu-drops
+ */
+DEFPY_YANG(log_pdu_drops, log_pdu_drops_cmd, "[no] log-pdu-drops",
+ NO_STR "Log any dropped PDUs\n")
+{
+ nb_cli_enqueue_change(vty, "./log-pdu-drops", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_log_pdu_drops(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+ vty_out(vty, " log-pdu-drops\n");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/mpls/ldp-sync
+ */
+DEFPY(isis_mpls_ldp_sync, isis_mpls_ldp_sync_cmd, "mpls ldp-sync",
+ MPLS_STR MPLS_LDP_SYNC_STR)
+{
+ nb_cli_enqueue_change(vty, "./mpls/ldp-sync", NB_OP_CREATE, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY(no_isis_mpls_ldp_sync, no_isis_mpls_ldp_sync_cmd, "no mpls ldp-sync",
+ NO_STR MPLS_STR NO_MPLS_LDP_SYNC_STR)
+{
+ nb_cli_enqueue_change(vty, "./mpls/ldp-sync", NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_mpls_ldp_sync(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " mpls ldp-sync\n");
+}
+
+DEFPY(isis_mpls_ldp_sync_holddown, isis_mpls_ldp_sync_holddown_cmd,
+ "mpls ldp-sync holddown (0-10000)",
+ MPLS_STR MPLS_LDP_SYNC_STR
+ "Time to wait for LDP-SYNC to occur before restoring interface metric\n"
+ "Time in seconds\n")
+{
+ nb_cli_enqueue_change(vty, "./mpls/ldp-sync/holddown", NB_OP_MODIFY,
+ holddown_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY(no_isis_mpls_ldp_sync_holddown, no_isis_mpls_ldp_sync_holddown_cmd,
+ "no mpls ldp-sync holddown [<(1-10000)>]",
+ NO_STR MPLS_STR MPLS_LDP_SYNC_STR NO_MPLS_LDP_SYNC_HOLDDOWN_STR "Time in seconds\n")
+{
+ nb_cli_enqueue_change(vty, "./mpls/ldp-sync/holddown", NB_OP_DESTROY,
+ NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_mpls_ldp_sync_holddown(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " mpls ldp-sync holddown %s\n",
+ yang_dnode_get_string(dnode, NULL));
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/mpls/ldp-sync
+ */
+DEFPY(isis_mpls_if_ldp_sync, isis_mpls_if_ldp_sync_cmd,
+ "[no] isis mpls ldp-sync",
+ NO_STR "IS-IS routing protocol\n" MPLS_STR MPLS_LDP_SYNC_STR)
+{
+ const struct lyd_node *dnode;
+
+ dnode = yang_dnode_getf(vty->candidate_config->dnode,
+ "%s/frr-isisd:isis", VTY_CURR_XPATH);
+ if (dnode == NULL) {
+ vty_out(vty, "ISIS is not enabled on this circuit\n");
+ return CMD_SUCCESS;
+ }
+
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/mpls/ldp-sync",
+ NB_OP_MODIFY, no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+
+void cli_show_isis_mpls_if_ldp_sync(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ if (!yang_dnode_get_bool(dnode, NULL))
+ vty_out(vty, " no");
+
+ vty_out(vty, " isis mpls ldp-sync\n");
+}
+
+DEFPY(isis_mpls_if_ldp_sync_holddown, isis_mpls_if_ldp_sync_holddown_cmd,
+ "isis mpls ldp-sync holddown (0-10000)",
+ "IS-IS routing protocol\n" MPLS_STR MPLS_LDP_SYNC_STR
+ "Time to wait for LDP-SYNC to occur before restoring interface metric\n"
+ "Time in seconds\n")
+{
+ const struct lyd_node *dnode;
+
+ dnode = yang_dnode_getf(vty->candidate_config->dnode,
+ "%s/frr-isisd:isis", VTY_CURR_XPATH);
+ if (dnode == NULL) {
+ vty_out(vty, "ISIS is not enabled on this circuit\n");
+ return CMD_SUCCESS;
+ }
+
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/mpls/holddown",
+ NB_OP_MODIFY, holddown_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY(no_isis_mpls_if_ldp_sync_holddown, no_isis_mpls_if_ldp_sync_holddown_cmd,
+ "no isis mpls ldp-sync holddown [<(1-10000)>]",
+ NO_STR "IS-IS routing protocol\n" MPLS_STR NO_MPLS_LDP_SYNC_STR
+ NO_MPLS_LDP_SYNC_HOLDDOWN_STR "Time in seconds\n")
+{
+ const struct lyd_node *dnode;
+
+ dnode = yang_dnode_getf(vty->candidate_config->dnode,
+ "%s/frr-isisd:isis", VTY_CURR_XPATH);
+ if (dnode == NULL) {
+ vty_out(vty, "ISIS is not enabled on this circuit\n");
+ return CMD_SUCCESS;
+ }
+
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/mpls/holddown",
+ NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_mpls_if_ldp_sync_holddown(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " isis mpls ldp-sync holddown %s\n",
+ yang_dnode_get_string(dnode, NULL));
+}
+
+DEFPY_YANG_NOSH(flex_algo, flex_algo_cmd, "flex-algo (128-255)$algorithm",
+ "Flexible Algorithm\n"
+ "Flexible Algorithm Number\n")
+{
+ int ret;
+ char xpath[XPATH_MAXLEN + 37];
+
+ snprintf(xpath, sizeof(xpath),
+ "%s/flex-algos/flex-algo[flex-algo='%ld']", VTY_CURR_XPATH,
+ algorithm);
+
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+
+ ret = nb_cli_apply_changes(
+ vty, "./flex-algos/flex-algo[flex-algo='%ld']", algorithm);
+ if (ret == CMD_SUCCESS)
+ VTY_PUSH_XPATH(ISIS_FLEX_ALGO_NODE, xpath);
+
+ return ret;
+}
+
+DEFPY_YANG(no_flex_algo, no_flex_algo_cmd, "no flex-algo (128-255)$algorithm",
+ NO_STR
+ "Flexible Algorithm\n"
+ "Flexible Algorithm Number\n")
+{
+ char xpath[XPATH_MAXLEN + 37];
+
+ snprintf(xpath, sizeof(xpath),
+ "%s/flex-algos/flex-algo[flex-algo='%ld']", VTY_CURR_XPATH,
+ algorithm);
+
+ if (!yang_dnode_exists(vty->candidate_config->dnode, xpath)) {
+ vty_out(vty, "ISIS flex-algo %ld isn't exist.\n", algorithm);
+ return CMD_ERR_NO_MATCH;
+ }
+
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+ return nb_cli_apply_changes_clear_pending(
+ vty, "./flex-algos/flex-algo[flex-algo='%ld']", algorithm);
+}
+
+DEFPY_YANG(advertise_definition, advertise_definition_cmd,
+ "[no] advertise-definition",
+ NO_STR "Advertise Local Flexible Algorithm\n")
+{
+ nb_cli_enqueue_change(vty, "./advertise-definition",
+ no ? NB_OP_DESTROY : NB_OP_CREATE,
+ no ? NULL : "true");
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(affinity_include_any, affinity_include_any_cmd,
+ "[no] affinity include-any NAME...",
+ NO_STR
+ "Affinity configuration\n"
+ "Any Include with\n"
+ "Include NAME list\n")
+{
+ const char *xpath = "./affinity-include-anies/affinity-include-any";
+
+ return ag_change(vty, argc, argv, xpath, no, no ? 3 : 2);
+}
+
+DEFPY_YANG(affinity_include_all, affinity_include_all_cmd,
+ "[no] affinity include-all NAME...",
+ NO_STR
+ "Affinity configuration\n"
+ "All Include with\n"
+ "Include NAME list\n")
+{
+ const char *xpath = "./affinity-include-alls/affinity-include-all";
+
+ return ag_change(vty, argc, argv, xpath, no, no ? 3 : 2);
+}
+
+DEFPY_YANG(affinity_exclude_any, affinity_exclude_any_cmd,
+ "[no] affinity exclude-any NAME...",
+ NO_STR
+ "Affinity configuration\n"
+ "Any Exclude with\n"
+ "Exclude NAME list\n")
+{
+ const char *xpath = "./affinity-exclude-anies/affinity-exclude-any";
+
+ return ag_change(vty, argc, argv, xpath, no, no ? 3 : 2);
+}
+
+DEFPY_YANG(prefix_metric, prefix_metric_cmd, "[no] prefix-metric",
+ NO_STR "Use Flex-Algo Prefix Metric\n")
+{
+ nb_cli_enqueue_change(vty, "./prefix-metric",
+ no ? NB_OP_DESTROY : NB_OP_CREATE, NULL);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(dplane_sr_mpls, dplane_sr_mpls_cmd, "[no] dataplane sr-mpls",
+ NO_STR
+ "Advertise and participate in the specified Data-Planes\n"
+ "Advertise and participate in SR-MPLS data-plane\n")
+{
+ nb_cli_enqueue_change(vty, "./dplane-sr-mpls",
+ no ? NB_OP_DESTROY : NB_OP_CREATE, NULL);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_HIDDEN(dplane_srv6, dplane_srv6_cmd, "[no] dataplane srv6",
+ NO_STR
+ "Advertise and participate in the specified Data-Planes\n"
+ "Advertise and participate in SRv6 data-plane\n")
+{
+
+ nb_cli_enqueue_change(vty, "./dplane-srv6",
+ no ? NB_OP_DESTROY : NB_OP_CREATE, NULL);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_HIDDEN(dplane_ip, dplane_ip_cmd, "[no] dataplane ip",
+ NO_STR
+ "Advertise and participate in the specified Data-Planes\n"
+ "Advertise and participate in IP data-plane\n")
+{
+ nb_cli_enqueue_change(vty, "./dplane-ip",
+ no ? NB_OP_DESTROY : NB_OP_CREATE, NULL);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(metric_type, metric_type_cmd,
+ "[no] metric-type [igp$igp|te$te|delay$delay]",
+ NO_STR
+ "Metric-type used by flex-algo calculation\n"
+ "Use IGP metric (default)\n"
+ "Use Delay as metric\n"
+ "Use Traffic Engineering metric\n")
+{
+ const char *type = NULL;
+
+ if (igp) {
+ type = "igp";
+ } else if (te) {
+ type = "te-default";
+ } else if (delay) {
+ type = "min-uni-link-delay";
+ } else {
+ vty_out(vty, "Error: unknown metric type\n");
+ return CMD_SUCCESS;
+ }
+
+ if (!igp)
+ vty_out(vty,
+ "Warning: this version can advertise a Flex-Algorithm Definition (FAD) with the %s metric.\n"
+ "However, participation in a Flex-Algorithm with such a metric is not yet supported.\n",
+ type);
+
+ nb_cli_enqueue_change(vty, "./metric-type",
+ no ? NB_OP_DESTROY : NB_OP_MODIFY,
+ no ? NULL : type);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(priority, priority_cmd, "[no] priority (0-255)$priority",
+ NO_STR
+ "Flex-Algo definition priority\n"
+ "Priority value\n")
+{
+ nb_cli_enqueue_change(vty, "./priority",
+ no ? NB_OP_DESTROY : NB_OP_MODIFY,
+ no ? NULL : priority_str);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void cli_show_isis_flex_algo(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ uint32_t algorithm;
+ enum flex_algo_metric_type metric_type;
+ uint32_t priority;
+ char type_str[10];
+
+ algorithm = yang_dnode_get_uint32(dnode, "./flex-algo");
+ vty_out(vty, " flex-algo %u\n", algorithm);
+
+ if (yang_dnode_exists(dnode, "./advertise-definition"))
+ vty_out(vty, " advertise-definition\n");
+
+ if (yang_dnode_exists(dnode, "./dplane-sr-mpls"))
+ vty_out(vty, " dataplane sr-mpls\n");
+ if (yang_dnode_exists(dnode, "./dplane-srv6"))
+ vty_out(vty, " dataplane srv6\n");
+ if (yang_dnode_exists(dnode, "./dplane-ip"))
+ vty_out(vty, " dataplane ip\n");
+
+ if (yang_dnode_exists(dnode, "./prefix-metric"))
+ vty_out(vty, " prefix-metric\n");
+
+ if (yang_dnode_exists(dnode, "./metric-type")) {
+ metric_type = yang_dnode_get_enum(dnode, "./metric-type");
+ if (metric_type != MT_IGP) {
+ flex_algo_metric_type_print(type_str, sizeof(type_str),
+ metric_type);
+ vty_out(vty, " metric-type %s\n", type_str);
+ }
+ }
+
+ if (yang_dnode_exists(dnode, "./priority")) {
+ priority = yang_dnode_get_uint32(dnode, "./priority");
+ if (priority != FLEX_ALGO_PRIO_DEFAULT)
+ vty_out(vty, " priority %u\n", priority);
+ }
+
+ if (yang_dnode_exists(dnode,
+ "./affinity-include-alls/affinity-include-all")) {
+ vty_out(vty, " affinity include-all");
+ yang_dnode_iterate(
+ ag_iter_cb, vty, dnode,
+ "./affinity-include-alls/affinity-include-all");
+ vty_out(vty, "\n");
+ }
+
+ if (yang_dnode_exists(
+ dnode, "./affinity-include-anies/affinity-include-any")) {
+ vty_out(vty, " affinity include-any");
+ yang_dnode_iterate(
+ ag_iter_cb, vty, dnode,
+ "./affinity-include-anies/affinity-include-any");
+ vty_out(vty, "\n");
+ }
+
+ if (yang_dnode_exists(
+ dnode, "./affinity-exclude-anies/affinity-exclude-any")) {
+ vty_out(vty, " affinity exclude-any");
+ yang_dnode_iterate(
+ ag_iter_cb, vty, dnode,
+ "./affinity-exclude-anies/affinity-exclude-any");
+ vty_out(vty, "\n");
+ }
+}
+
+void cli_show_isis_flex_algo_end(struct vty *vty, const struct lyd_node *dnode)
+{
+ vty_out(vty, " !\n");
+}
+
+
+void isis_cli_init(void)
+{
+ install_element(CONFIG_NODE, &router_isis_cmd);
+ install_element(CONFIG_NODE, &no_router_isis_cmd);
+
+ install_element(INTERFACE_NODE, &ip_router_isis_cmd);
+ install_element(INTERFACE_NODE, &ip_router_isis_vrf_cmd);
+ install_element(INTERFACE_NODE, &ip6_router_isis_cmd);
+ install_element(INTERFACE_NODE, &ip6_router_isis_vrf_cmd);
+ install_element(INTERFACE_NODE, &no_ip_router_isis_cmd);
+ install_element(INTERFACE_NODE, &no_ip_router_isis_vrf_cmd);
+ install_element(INTERFACE_NODE, &isis_bfd_cmd);
+ install_element(INTERFACE_NODE, &isis_bfd_profile_cmd);
+
+ install_element(ISIS_NODE, &net_cmd);
+
+ install_element(ISIS_NODE, &is_type_cmd);
+ install_element(ISIS_NODE, &no_is_type_cmd);
+
+ install_element(ISIS_NODE, &dynamic_hostname_cmd);
+
+ install_element(ISIS_NODE, &set_overload_bit_cmd);
+ install_element(ISIS_NODE, &set_overload_bit_on_startup_cmd);
+ install_element(ISIS_NODE, &no_set_overload_bit_on_startup_cmd);
+
+ install_element(ISIS_NODE, &attached_bit_send_cmd);
+ install_element(ISIS_NODE, &attached_bit_receive_ignore_cmd);
+
+ install_element(ISIS_NODE, &metric_style_cmd);
+ install_element(ISIS_NODE, &no_metric_style_cmd);
+
+ install_element(ISIS_NODE, &advertise_high_metrics_cmd);
+
+ install_element(ISIS_NODE, &area_passwd_cmd);
+ install_element(ISIS_NODE, &domain_passwd_cmd);
+ install_element(ISIS_NODE, &no_area_passwd_cmd);
+
+ install_element(ISIS_NODE, &lsp_gen_interval_cmd);
+ install_element(ISIS_NODE, &no_lsp_gen_interval_cmd);
+ install_element(ISIS_NODE, &lsp_refresh_interval_cmd);
+ install_element(ISIS_NODE, &no_lsp_refresh_interval_cmd);
+ install_element(ISIS_NODE, &max_lsp_lifetime_cmd);
+ install_element(ISIS_NODE, &no_max_lsp_lifetime_cmd);
+ install_element(ISIS_NODE, &lsp_timers_cmd);
+ install_element(ISIS_NODE, &no_lsp_timers_cmd);
+ install_element(ISIS_NODE, &area_lsp_mtu_cmd);
+ install_element(ISIS_NODE, &no_area_lsp_mtu_cmd);
+ install_element(ISIS_NODE, &advertise_passive_only_cmd);
+
+ install_element(ISIS_NODE, &spf_interval_cmd);
+ install_element(ISIS_NODE, &no_spf_interval_cmd);
+ install_element(ISIS_NODE, &spf_prefix_priority_cmd);
+ install_element(ISIS_NODE, &no_spf_prefix_priority_cmd);
+ install_element(ISIS_NODE, &spf_delay_ietf_cmd);
+ install_element(ISIS_NODE, &no_spf_delay_ietf_cmd);
+
+ install_element(ISIS_NODE, &area_purge_originator_cmd);
+
+ install_element(ISIS_NODE, &isis_admin_group_send_zero_cmd);
+ install_element(ISIS_NODE, &isis_asla_legacy_flag_cmd);
+
+ install_element(ISIS_NODE, &isis_mpls_te_on_cmd);
+ install_element(ISIS_NODE, &no_isis_mpls_te_on_cmd);
+ install_element(ISIS_NODE, &isis_mpls_te_router_addr_cmd);
+ install_element(ISIS_NODE, &no_isis_mpls_te_router_addr_cmd);
+ install_element(ISIS_NODE, &isis_mpls_te_router_addr_v6_cmd);
+ install_element(ISIS_NODE, &no_isis_mpls_te_router_addr_v6_cmd);
+ install_element(ISIS_NODE, &isis_mpls_te_inter_as_cmd);
+ install_element(ISIS_NODE, &isis_mpls_te_export_cmd);
+ install_element(ISIS_NODE, &no_isis_mpls_te_export_cmd);
+
+ install_element(ISIS_NODE, &isis_default_originate_cmd);
+ install_element(ISIS_NODE, &isis_redistribute_cmd);
+ install_element(ISIS_NODE, &isis_redistribute_table_cmd);
+
+ install_element(ISIS_NODE, &isis_topology_cmd);
+
+ install_element(ISIS_NODE, &isis_sr_enable_cmd);
+ install_element(ISIS_NODE, &no_isis_sr_enable_cmd);
+ install_element(ISIS_NODE, &isis_sr_global_block_label_range_cmd);
+ install_element(ISIS_NODE, &no_isis_sr_global_block_label_range_cmd);
+ install_element(ISIS_NODE, &isis_sr_node_msd_cmd);
+ install_element(ISIS_NODE, &no_isis_sr_node_msd_cmd);
+ install_element(ISIS_NODE, &isis_sr_prefix_sid_cmd);
+ install_element(ISIS_NODE, &no_isis_sr_prefix_sid_cmd);
+#ifndef FABRICD
+ install_element(ISIS_NODE, &isis_sr_prefix_sid_algorithm_cmd);
+ install_element(ISIS_NODE, &no_isis_sr_prefix_sid_algorithm_cmd);
+#endif /* ifndef FABRICD */
+ install_element(ISIS_NODE, &isis_frr_lfa_priority_limit_cmd);
+ install_element(ISIS_NODE, &isis_frr_lfa_tiebreaker_cmd);
+ install_element(ISIS_NODE, &isis_frr_lfa_load_sharing_cmd);
+ install_element(ISIS_NODE, &isis_frr_remote_lfa_plist_cmd);
+ install_element(ISIS_NODE, &no_isis_frr_remote_lfa_plist_cmd);
+
+ install_element(ISIS_NODE, &isis_srv6_enable_cmd);
+ install_element(ISIS_NODE, &no_isis_srv6_enable_cmd);
+ install_element(ISIS_SRV6_NODE, &isis_srv6_locator_cmd);
+ install_element(ISIS_SRV6_NODE, &isis_srv6_node_msd_cmd);
+ install_element(ISIS_SRV6_NODE, &isis_srv6_interface_cmd);
+ install_element(ISIS_SRV6_NODE_MSD_NODE,
+ &isis_srv6_node_msd_max_segs_left_cmd);
+ install_element(ISIS_SRV6_NODE_MSD_NODE,
+ &isis_srv6_node_msd_max_end_pop_cmd);
+ install_element(ISIS_SRV6_NODE_MSD_NODE,
+ &isis_srv6_node_msd_max_h_encaps_cmd);
+ install_element(ISIS_SRV6_NODE_MSD_NODE,
+ &isis_srv6_node_msd_max_end_d_cmd);
+
+ install_element(INTERFACE_NODE, &isis_passive_cmd);
+
+ install_element(INTERFACE_NODE, &isis_passwd_cmd);
+ install_element(INTERFACE_NODE, &no_isis_passwd_cmd);
+
+ install_element(INTERFACE_NODE, &isis_metric_cmd);
+ install_element(INTERFACE_NODE, &no_isis_metric_cmd);
+
+ install_element(INTERFACE_NODE, &isis_hello_interval_cmd);
+ install_element(INTERFACE_NODE, &no_isis_hello_interval_cmd);
+
+ install_element(INTERFACE_NODE, &isis_hello_multiplier_cmd);
+ install_element(INTERFACE_NODE, &no_isis_hello_multiplier_cmd);
+
+ install_element(INTERFACE_NODE, &isis_threeway_adj_cmd);
+
+ install_element(INTERFACE_NODE, &isis_hello_padding_cmd);
+
+ install_element(INTERFACE_NODE, &csnp_interval_cmd);
+ install_element(INTERFACE_NODE, &no_csnp_interval_cmd);
+
+ install_element(INTERFACE_NODE, &psnp_interval_cmd);
+ install_element(INTERFACE_NODE, &no_psnp_interval_cmd);
+
+ install_element(INTERFACE_NODE, &circuit_topology_cmd);
+
+ install_element(INTERFACE_NODE, &isis_circuit_type_cmd);
+ install_element(INTERFACE_NODE, &no_isis_circuit_type_cmd);
+
+ install_element(INTERFACE_NODE, &isis_network_cmd);
+
+ install_element(INTERFACE_NODE, &isis_priority_cmd);
+ install_element(INTERFACE_NODE, &no_isis_priority_cmd);
+
+ install_element(INTERFACE_NODE, &isis_lfa_cmd);
+ install_element(INTERFACE_NODE, &isis_lfa_exclude_interface_cmd);
+ install_element(INTERFACE_NODE, &isis_remote_lfa_cmd);
+ install_element(INTERFACE_NODE, &isis_remote_lfa_max_metric_cmd);
+ install_element(INTERFACE_NODE, &isis_ti_lfa_cmd);
+
+ install_element(ISIS_NODE, &log_adj_changes_cmd);
+ install_element(ISIS_NODE, &log_pdu_drops_cmd);
+
+ install_element(ISIS_NODE, &isis_mpls_ldp_sync_cmd);
+ install_element(ISIS_NODE, &no_isis_mpls_ldp_sync_cmd);
+ install_element(ISIS_NODE, &isis_mpls_ldp_sync_holddown_cmd);
+ install_element(ISIS_NODE, &no_isis_mpls_ldp_sync_holddown_cmd);
+ install_element(INTERFACE_NODE, &isis_mpls_if_ldp_sync_cmd);
+ install_element(INTERFACE_NODE, &isis_mpls_if_ldp_sync_holddown_cmd);
+ install_element(INTERFACE_NODE, &no_isis_mpls_if_ldp_sync_holddown_cmd);
+
+ install_element(ISIS_NODE, &flex_algo_cmd);
+ install_element(ISIS_NODE, &no_flex_algo_cmd);
+ install_element(ISIS_FLEX_ALGO_NODE, &advertise_definition_cmd);
+ install_element(ISIS_FLEX_ALGO_NODE, &affinity_include_any_cmd);
+ install_element(ISIS_FLEX_ALGO_NODE, &affinity_include_all_cmd);
+ install_element(ISIS_FLEX_ALGO_NODE, &affinity_exclude_any_cmd);
+ install_element(ISIS_FLEX_ALGO_NODE, &dplane_sr_mpls_cmd);
+ install_element(ISIS_FLEX_ALGO_NODE, &dplane_srv6_cmd);
+ install_element(ISIS_FLEX_ALGO_NODE, &dplane_ip_cmd);
+ install_element(ISIS_FLEX_ALGO_NODE, &prefix_metric_cmd);
+ install_element(ISIS_FLEX_ALGO_NODE, &metric_type_cmd);
+ install_element(ISIS_FLEX_ALGO_NODE, &priority_cmd);
+}
+
+#endif /* ifndef FABRICD */
diff --git a/isisd/isis_common.h b/isisd/isis_common.h
new file mode 100644
index 0000000..2ded68f
--- /dev/null
+++ b/isisd/isis_common.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_common.h
+ * some common data structures
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#ifndef ISIS_COMMON_H
+#define ISIS_COMMON_H
+
+struct isis_passwd {
+ uint8_t len;
+#define ISIS_PASSWD_TYPE_UNUSED 0
+#define ISIS_PASSWD_TYPE_CLEARTXT 1
+#define ISIS_PASSWD_TYPE_HMAC_MD5 54
+#define ISIS_PASSWD_TYPE_PRIVATE 255
+ uint8_t type;
+/* Authenticate SNPs? */
+#define SNP_AUTH_SEND 0x01
+#define SNP_AUTH_RECV 0x02
+ uint8_t snp_auth;
+ uint8_t passwd[255];
+};
+
+/*
+ * Supported Protocol IDs
+ */
+struct nlpids {
+ uint8_t count;
+ uint8_t nlpids[4]; /* FIXME: enough ? */
+};
+
+#endif
diff --git a/isisd/isis_constants.h b/isisd/isis_constants.h
new file mode 100644
index 0000000..b2be282
--- /dev/null
+++ b/isisd/isis_constants.h
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_constants.h
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#ifndef ISIS_CONSTANTS_H
+#define ISIS_CONSTANTS_H
+
+/*
+ * Architectural constant values from p. 35 of ISO/IEC 10589
+ */
+
+#define MAX_NARROW_LINK_METRIC 63
+#define MAX_NARROW_PATH_METRIC 1023
+#define MAX_WIDE_LINK_METRIC 0x00FFFFFF /* RFC4444 */
+#define MAX_WIDE_PATH_METRIC 0xFE000000 /* RFC3787 */
+#define ISO_SAP 0xFE
+#define INTRADOMAIN_ROUTEING_SELECTOR 0
+#define SEQUENCE_MODULUS 4294967296
+
+#define ISO9542_ESIS 0x82
+#define ISO10589_ISIS 0x83
+
+/*
+ * implementation specific jitter values
+ */
+
+#define IIH_JITTER 10 /* % */
+#define MAX_AGE_JITTER 5 /* % */
+#define MAX_LSP_GEN_JITTER 5 /* % */
+#define CSNP_JITTER 10 /* % */
+#define PSNP_JITTER 10 /* % */
+
+#define RANDOM_SPREAD 100000.0
+
+#define ISIS_LEVELS 2
+#define ISIS_LEVEL1 1
+#define ISIS_LEVEL2 2
+
+/*
+ * Default values
+ * ISO - 10589 Section 7.3.21 - Parameters
+ * RFC 4444
+ */
+#define MAX_AGE 1200
+#define ZERO_AGE_LIFETIME 60
+#define MIN_LSP_LIFETIME 350
+#define MAX_LSP_LIFETIME 65535
+#define DEFAULT_LSP_LIFETIME 1200
+
+#define MIN_MAX_LSP_GEN_INTERVAL 1
+#define MAX_MAX_LSP_GEN_INTERVAL 65235
+#define DEFAULT_MAX_LSP_GEN_INTERVAL 900
+
+#define MIN_MIN_LSP_GEN_INTERVAL 1
+#define MAX_MIN_LSP_GEN_INTERVAL 120 /* RFC 4444 says 65535 */
+#define DEFAULT_MIN_LSP_GEN_INTERVAL 30
+
+#define MIN_LSP_RETRANS_INTERVAL 5 /* Seconds */
+
+#define TRIGGERED_IIH_DELAY 50 /* msec */
+
+#define MIN_CSNP_INTERVAL 1
+#define MAX_CSNP_INTERVAL 600
+#define DEFAULT_CSNP_INTERVAL 10
+
+#define MIN_PSNP_INTERVAL 1
+#define MAX_PSNP_INTERVAL 120
+#define DEFAULT_PSNP_INTERVAL 2
+
+#define MIN_HELLO_INTERVAL 1
+#define MAX_HELLO_INTERVAL 600
+#define DEFAULT_HELLO_INTERVAL 3
+
+#define MIN_HELLO_MULTIPLIER 2
+#define MAX_HELLO_MULTIPLIER 100
+#define DEFAULT_HELLO_MULTIPLIER 10
+
+#define MIN_PRIORITY 0
+#define MAX_PRIORITY 127
+#define DEFAULT_PRIORITY 64
+
+/* min and max metric varies by new vs old metric types */
+#define DEFAULT_CIRCUIT_METRIC 10
+
+#define METRICS_UNSUPPORTED 0x80
+
+#define MINIMUM_SPF_INTERVAL 1
+
+#define ISIS_MAX_PATH_SPLITS 64
+
+/*
+ * NLPID values
+ */
+#define NLPID_IP 204
+#define NLPID_IPV6 142
+#define NLPID_SNAP 128
+#define NLPID_CLNP 129
+#define NLPID_ESIS 130
+
+/*
+ * Return values for functions
+ */
+#define ISIS_OK 0
+#define ISIS_WARNING 1
+#define ISIS_ERROR 2
+#define ISIS_CRITICAL 3
+
+/*
+ * IS-IS Circuit Types
+ */
+
+#define IS_LEVEL_1 1
+#define IS_LEVEL_2 2
+#define IS_LEVEL_1_AND_2 3
+
+#define SNPA_ADDRSTRLEN 18
+#define ISIS_SYS_ID_LEN 6
+#define ISIS_NSEL_LEN 1
+#define SYSID_STRLEN 24
+
+/*
+ * LSP bit masks
+ */
+#define LSPBIT_P 0x80
+#define LSPBIT_ATT 0x08 /* only use the Default ATT bit */
+#define LSPBIT_OL 0x04
+#define LSPBIT_IST 0x03
+
+/*
+ * LSP bit masking macros
+ * taken from tcpdumps
+ * print-isoclns.c
+ */
+
+#define ISIS_MASK_LSP_OL_BIT(x) ((x)&0x4)
+#define ISIS_MASK_LSP_IS_L1_BIT(x) ((x)&0x1)
+#define ISIS_MASK_LSP_IS_L2_BIT(x) ((x)&0x2)
+#define ISIS_MASK_LSP_PARTITION_BIT(x) ((x)&0x80)
+#define ISIS_MASK_LSP_ATT_BITS(x) ((x)&0x78)
+#define ISIS_MASK_LSP_ATT_ERROR_BIT(x) ((x)&0x40)
+#define ISIS_MASK_LSP_ATT_EXPENSE_BIT(x) ((x)&0x20)
+#define ISIS_MASK_LSP_ATT_DELAY_BIT(x) ((x)&0x10)
+
+#define LLC_LEN 3
+
+/* we need to be aware of the fact we are using ISO sized
+ * packets, using isomtu = mtu - LLC_LEN
+ */
+#define ISO_MTU(C) \
+ ((if_is_broadcast((C)->interface)) ? (C->interface->mtu - LLC_LEN) \
+ : (C->interface->mtu))
+
+#define MAX_LLC_LEN 0x5ff
+#define ETHERTYPE_EXT_LLC 0x8870
+
+static inline uint16_t isis_ethertype(size_t len)
+{
+ if (len > MAX_LLC_LEN)
+ return ETHERTYPE_EXT_LLC;
+ return len;
+}
+
+#endif /* ISIS_CONSTANTS_H */
diff --git a/isisd/isis_csm.c b/isisd/isis_csm.c
new file mode 100644
index 0000000..9e278d4
--- /dev/null
+++ b/isisd/isis_csm.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_csm.c
+ * IS-IS circuit state machine
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+
+#include "log.h"
+#include "memory.h"
+#include "if.h"
+#include "linklist.h"
+#include "command.h"
+#include "frrevent.h"
+#include "hash.h"
+#include "prefix.h"
+#include "stream.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_network.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_dr.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isis_events.h"
+#include "isisd/isis_errors.h"
+
+static const char *const csm_statestr[] = {"C_STATE_NA", "C_STATE_INIT",
+ "C_STATE_CONF", "C_STATE_UP"};
+
+#define STATE2STR(S) csm_statestr[S]
+
+static const char *const csm_eventstr[] = {
+ "NO_STATE", "ISIS_ENABLE", "IF_UP_FROM_Z",
+ "ISIS_DISABLE", "IF_DOWN_FROM_Z",
+};
+
+#define EVENT2STR(E) csm_eventstr[E]
+
+struct isis_circuit *isis_csm_state_change(enum isis_circuit_event event,
+ struct isis_circuit *circuit,
+ void *arg)
+{
+ enum isis_circuit_state old_state;
+ struct isis_area *area = NULL;
+ struct interface *ifp;
+
+ assert(circuit);
+
+ old_state = circuit->state;
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("CSM_EVENT for %s: %s", circuit->interface->name,
+ EVENT2STR(event));
+
+ switch (old_state) {
+ case C_STATE_NA:
+ switch (event) {
+ case ISIS_ENABLE:
+ area = arg;
+
+ isis_circuit_configure(circuit, area);
+ circuit->state = C_STATE_CONF;
+ break;
+ case IF_UP_FROM_Z:
+ ifp = arg;
+
+ isis_circuit_if_add(circuit, ifp);
+ circuit->state = C_STATE_INIT;
+ break;
+ case ISIS_DISABLE:
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("circuit %s already disabled",
+ circuit->interface->name);
+ break;
+ case IF_DOWN_FROM_Z:
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("circuit %s already disconnected",
+ circuit->interface->name);
+ break;
+ }
+ break;
+ case C_STATE_INIT:
+ switch (event) {
+ case ISIS_ENABLE:
+ area = arg;
+
+ isis_circuit_configure(circuit, area);
+ if (isis_circuit_up(circuit) != ISIS_OK) {
+ isis_circuit_deconfigure(circuit, area);
+ break;
+ }
+ circuit->state = C_STATE_UP;
+ isis_event_circuit_state_change(circuit, circuit->area,
+ 1);
+ break;
+ case IF_UP_FROM_Z:
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("circuit %s already connected",
+ circuit->interface->name);
+ break;
+ case ISIS_DISABLE:
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("circuit %s already disabled",
+ circuit->interface->name);
+ break;
+ case IF_DOWN_FROM_Z:
+ ifp = arg;
+
+ isis_circuit_if_del(circuit, ifp);
+ circuit->state = C_STATE_NA;
+ break;
+ }
+ break;
+ case C_STATE_CONF:
+ switch (event) {
+ case ISIS_ENABLE:
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("circuit %s is already enabled",
+ circuit->interface->name);
+ break;
+ case IF_UP_FROM_Z:
+ ifp = arg;
+
+ isis_circuit_if_add(circuit, ifp);
+ if (isis_circuit_up(circuit) != ISIS_OK) {
+ isis_circuit_if_del(circuit, ifp);
+ flog_err(
+ EC_ISIS_CONFIG,
+ "Could not bring up %s because of invalid config.",
+ circuit->interface->name);
+ break;
+ }
+ circuit->state = C_STATE_UP;
+ isis_event_circuit_state_change(circuit, circuit->area,
+ 1);
+ break;
+ case ISIS_DISABLE:
+ area = arg;
+
+ isis_circuit_deconfigure(circuit, area);
+ circuit->state = C_STATE_NA;
+ break;
+ case IF_DOWN_FROM_Z:
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("circuit %s already disconnected",
+ circuit->interface->name);
+ break;
+ }
+ break;
+ case C_STATE_UP:
+ switch (event) {
+ case ISIS_ENABLE:
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("circuit %s already enabled",
+ circuit->interface->name);
+ break;
+ case IF_UP_FROM_Z:
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("circuit %s already connected",
+ circuit->interface->name);
+ break;
+ case ISIS_DISABLE:
+ area = arg;
+
+ isis_circuit_down(circuit);
+ isis_circuit_deconfigure(circuit, area);
+ circuit->state = C_STATE_INIT;
+ isis_event_circuit_state_change(circuit, area, 0);
+ break;
+ case IF_DOWN_FROM_Z:
+ ifp = arg;
+
+ isis_circuit_down(circuit);
+ isis_circuit_if_del(circuit, ifp);
+ circuit->state = C_STATE_CONF;
+ isis_event_circuit_state_change(circuit, circuit->area,
+ 0);
+ break;
+ }
+ break;
+ }
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("CSM_STATE_CHANGE: %s -> %s ", STATE2STR(old_state),
+ STATE2STR(circuit->state));
+
+ return circuit;
+}
diff --git a/isisd/isis_csm.h b/isisd/isis_csm.h
new file mode 100644
index 0000000..80e02b8
--- /dev/null
+++ b/isisd/isis_csm.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_csm.h
+ * IS-IS circuit state machine
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ *
+ */
+#ifndef _ZEBRA_ISIS_CSM_H
+#define _ZEBRA_ISIS_CSM_H
+
+/*
+ * Circuit states
+ */
+enum isis_circuit_state {
+ C_STATE_NA,
+ C_STATE_INIT, /* Connected to interface */
+ C_STATE_CONF, /* Configured for ISIS */
+ C_STATE_UP, /* CONN | CONF */
+};
+
+/*
+ * Circuit events
+ */
+enum isis_circuit_event {
+ ISIS_ENABLE = 1,
+ IF_UP_FROM_Z,
+ ISIS_DISABLE,
+ IF_DOWN_FROM_Z,
+};
+
+struct isis_circuit *isis_csm_state_change(enum isis_circuit_event event,
+ struct isis_circuit *circuit,
+ void *arg);
+
+#endif /* _ZEBRA_ISIS_CSM_H */
diff --git a/isisd/isis_dlpi.c b/isisd/isis_dlpi.c
new file mode 100644
index 0000000..1f09152
--- /dev/null
+++ b/isisd/isis_dlpi.c
@@ -0,0 +1,593 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_dlpi.c
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+#if ISIS_METHOD == ISIS_METHOD_DLPI
+#include <net/if.h>
+#include <netinet/if_ether.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stropts.h>
+#include <poll.h>
+#include <sys/dlpi.h>
+#include <sys/pfmod.h>
+
+#include "log.h"
+#include "network.h"
+#include "stream.h"
+#include "if.h"
+#include "lib_errors.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_network.h"
+
+#include "privs.h"
+
+static t_uscalar_t dlpi_ctl[1024]; /* DLPI control messages */
+
+/*
+ * Table 9 - Architectural constants for use with ISO 8802 subnetworks
+ * ISO 10589 - 8.4.8
+ */
+
+static const uint8_t ALL_L1_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x14};
+static const uint8_t ALL_L2_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x15};
+static const uint8_t ALL_ISS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x05};
+static uint8_t sock_buff[16384];
+
+static unsigned short pf_filter[] = {
+ ENF_PUSHWORD + 0, /* Get the SSAP/DSAP values */
+ ENF_PUSHLIT | ENF_CAND, /* Check them */
+ ISO_SAP | (ISO_SAP << 8),
+ ENF_PUSHWORD + 1, /* Get the control value */
+ ENF_PUSHLIT | ENF_AND, /* Isolate it */
+#ifdef _BIG_ENDIAN
+ 0xFF00,
+#else
+ 0x00FF,
+#endif
+ ENF_PUSHLIT | ENF_CAND, /* Test for expected value */
+#ifdef _BIG_ENDIAN
+ 0x0300
+#else
+ 0x0003
+#endif
+};
+
+/*
+ * We would like to use something like libdlpi here, but that's not present on
+ * all versions of Solaris or on any non-Solaris system, so it's nowhere near
+ * as portable as we'd like. Thus, we use the standards-conformant DLPI
+ * interfaces plus the (optional; not needed) Solaris packet filter module.
+ */
+
+static int dlpisend(int fd, const void *cbuf, size_t cbuflen, const void *dbuf,
+ size_t dbuflen, int flags)
+{
+ const struct strbuf *ctlptr = NULL;
+ const struct strbuf *dataptr = NULL;
+ struct strbuf ctlbuf, databuf;
+ int rv;
+
+ if (cbuf != NULL) {
+ memset(&ctlbuf, 0, sizeof(ctlbuf));
+ ctlbuf.len = cbuflen;
+ ctlbuf.buf = (void *)cbuf;
+ ctlptr = &ctlbuf;
+ }
+
+ if (dbuf != NULL) {
+ memset(&databuf, 0, sizeof(databuf));
+ databuf.len = dbuflen;
+ databuf.buf = (void *)dbuf;
+ dataptr = &databuf;
+ }
+
+ /* We assume this doesn't happen often and isn't operationally
+ * significant */
+ rv = putmsg(fd, ctlptr, dataptr, flags);
+ if (rv == -1 && dbuf == NULL) {
+ /*
+ * For actual PDU transmission - recognizable buf dbuf != NULL,
+ * the error is passed upwards and should not be printed here.
+ */
+ zlog_debug("%s: putmsg: %s", __func__, safe_strerror(errno));
+ }
+ return rv;
+}
+
+static ssize_t dlpirctl(int fd)
+{
+ struct pollfd fds[1];
+ struct strbuf ctlbuf, databuf;
+ int flags, retv;
+
+ do {
+ /* Poll is used here in case the device doesn't speak DLPI
+ * correctly */
+ memset(fds, 0, sizeof(fds));
+ fds[0].fd = fd;
+ fds[0].events = POLLIN | POLLPRI;
+ if (poll(fds, 1, 1000) <= 0)
+ return -1;
+
+ memset(&ctlbuf, 0, sizeof(ctlbuf));
+ memset(&databuf, 0, sizeof(databuf));
+ ctlbuf.maxlen = sizeof(dlpi_ctl);
+ ctlbuf.buf = (void *)dlpi_ctl;
+ databuf.maxlen = sizeof(sock_buff);
+ databuf.buf = (void *)sock_buff;
+ flags = 0;
+ retv = getmsg(fd, &ctlbuf, &databuf, &flags);
+
+ if (retv < 0)
+ return -1;
+ } while (ctlbuf.len == 0);
+
+ if (!(retv & MORECTL)) {
+ while (retv & MOREDATA) {
+ flags = 0;
+ retv = getmsg(fd, NULL, &databuf, &flags);
+ }
+ return ctlbuf.len;
+ }
+
+ while (retv & MORECTL) {
+ flags = 0;
+ retv = getmsg(fd, &ctlbuf, &databuf, &flags);
+ }
+ return -1;
+}
+
+static int dlpiok(int fd, t_uscalar_t oprim)
+{
+ int retv;
+ dl_ok_ack_t *doa = (dl_ok_ack_t *)dlpi_ctl;
+
+ retv = dlpirctl(fd);
+ if (retv < (ssize_t)DL_OK_ACK_SIZE || doa->dl_primitive != DL_OK_ACK
+ || doa->dl_correct_primitive != oprim) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+static int dlpiinfo(int fd)
+{
+ dl_info_req_t dir;
+ ssize_t retv;
+
+ memset(&dir, 0, sizeof(dir));
+ dir.dl_primitive = DL_INFO_REQ;
+ /* Info_req uses M_PCPROTO. */
+ dlpisend(fd, &dir, sizeof(dir), NULL, 0, RS_HIPRI);
+ retv = dlpirctl(fd);
+ if (retv < (ssize_t)DL_INFO_ACK_SIZE || dlpi_ctl[0] != DL_INFO_ACK)
+ return -1;
+ else
+ return retv;
+}
+
+static int dlpiopen(const char *devpath, ssize_t *acklen)
+{
+ int fd, flags;
+
+ fd = open(devpath, O_RDWR | O_NONBLOCK | O_NOCTTY);
+ if (fd == -1)
+ return -1;
+
+ /* All that we want is for the open itself to be non-blocking, not I/O.
+ */
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags != -1)
+ fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
+
+ /* After opening, ask for information */
+ if ((*acklen = dlpiinfo(fd)) == -1) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static int dlpiattach(int fd, int unit)
+{
+ dl_attach_req_t dar;
+
+ memset(&dar, 0, sizeof(dar));
+ dar.dl_primitive = DL_ATTACH_REQ;
+ dar.dl_ppa = unit;
+ dlpisend(fd, &dar, sizeof(dar), NULL, 0, 0);
+ return dlpiok(fd, dar.dl_primitive);
+}
+
+static int dlpibind(int fd)
+{
+ dl_bind_req_t dbr;
+ int retv;
+ dl_bind_ack_t *dba = (dl_bind_ack_t *)dlpi_ctl;
+
+ memset(&dbr, 0, sizeof(dbr));
+ dbr.dl_primitive = DL_BIND_REQ;
+ dbr.dl_service_mode = DL_CLDLS;
+ dlpisend(fd, &dbr, sizeof(dbr), NULL, 0, 0);
+
+ retv = dlpirctl(fd);
+ if (retv < (ssize_t)DL_BIND_ACK_SIZE
+ || dba->dl_primitive != DL_BIND_ACK)
+ return -1;
+ else
+ return 0;
+}
+
+static int dlpimcast(int fd, const uint8_t *mcaddr)
+{
+ struct {
+ dl_enabmulti_req_t der;
+ uint8_t addr[ETHERADDRL];
+ } dler;
+
+ memset(&dler, 0, sizeof(dler));
+ dler.der.dl_primitive = DL_ENABMULTI_REQ;
+ dler.der.dl_addr_length = sizeof(dler.addr);
+ dler.der.dl_addr_offset = dler.addr - (uint8_t *)&dler;
+ memcpy(dler.addr, mcaddr, sizeof(dler.addr));
+ dlpisend(fd, &dler, sizeof(dler), NULL, 0, 0);
+ return dlpiok(fd, dler.der.dl_primitive);
+}
+
+static int dlpiaddr(int fd, uint8_t *addr)
+{
+ dl_phys_addr_req_t dpar;
+ dl_phys_addr_ack_t *dpaa = (dl_phys_addr_ack_t *)dlpi_ctl;
+ int retv;
+
+ memset(&dpar, 0, sizeof(dpar));
+ dpar.dl_primitive = DL_PHYS_ADDR_REQ;
+ dpar.dl_addr_type = DL_CURR_PHYS_ADDR;
+ dlpisend(fd, &dpar, sizeof(dpar), NULL, 0, 0);
+
+ retv = dlpirctl(fd);
+ if (retv < (ssize_t)DL_PHYS_ADDR_ACK_SIZE
+ || dpaa->dl_primitive != DL_PHYS_ADDR_ACK)
+ return -1;
+
+ if (dpaa->dl_addr_offset < DL_PHYS_ADDR_ACK_SIZE
+ || dpaa->dl_addr_length != ETHERADDRL
+ || dpaa->dl_addr_offset + dpaa->dl_addr_length > (size_t)retv)
+ return -1;
+
+ bcopy((char *)dpaa + dpaa->dl_addr_offset, addr, ETHERADDRL);
+ return 0;
+}
+
+static int open_dlpi_dev(struct isis_circuit *circuit)
+{
+ int fd = -1, unit, retval;
+ char devpath[MAXPATHLEN];
+ dl_info_ack_t *dia = (dl_info_ack_t *)dlpi_ctl;
+ ssize_t acklen;
+
+ /* Only broadcast-type are supported at the moment */
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST) {
+ zlog_warn("%s: non-broadcast interface %s", __func__,
+ circuit->interface->name);
+ return ISIS_WARNING;
+ }
+
+ /* Try the vanity node first, if permitted */
+ if (getenv("DLPI_DEVONLY") == NULL) {
+ (void)snprintf(devpath, sizeof(devpath), "/dev/net/%s",
+ circuit->interface->name);
+ fd = dlpiopen(devpath, &acklen);
+ }
+
+ /* Now try as an ordinary Style 1 node */
+ if (fd == -1) {
+ (void)snprintf(devpath, sizeof(devpath), "/dev/%s",
+ circuit->interface->name);
+ unit = -1;
+ fd = dlpiopen(devpath, &acklen);
+ }
+
+ /* If that fails, try again as Style 2 */
+ if (fd == -1) {
+ char *cp;
+
+ cp = devpath + strlen(devpath);
+ while (--cp >= devpath && isdigit(*cp))
+ ;
+ unit = strtol(cp, NULL, 0);
+ *cp = '\0';
+ fd = dlpiopen(devpath, &acklen);
+
+ /* If that too fails, then the device really doesn't exist */
+ if (fd == -1) {
+ zlog_warn("%s: unknown interface %s", __func__,
+ circuit->interface->name);
+ return ISIS_WARNING;
+ }
+
+ /* Double check the DLPI style */
+ if (dia->dl_provider_style != DL_STYLE2) {
+ zlog_warn("%s: interface %s: %s is not style 2",
+ __func__, circuit->interface->name, devpath);
+ close(fd);
+ return ISIS_WARNING;
+ }
+
+ /* If it succeeds, then we need to attach to the unit specified
+ */
+ dlpiattach(fd, unit);
+
+ /* Reget the information, as it may be different per node */
+ if ((acklen = dlpiinfo(fd)) == -1) {
+ close(fd);
+ return ISIS_WARNING;
+ }
+ } else {
+ /* Double check the DLPI style */
+ if (dia->dl_provider_style != DL_STYLE1) {
+ zlog_warn("%s: interface %s: %s is not style 1",
+ __func__, circuit->interface->name, devpath);
+ close(fd);
+ return ISIS_WARNING;
+ }
+ }
+
+ /* Check that the interface we've got is the kind we expect */
+ if ((dia->dl_sap_length != 2 && dia->dl_sap_length != -2)
+ || dia->dl_service_mode != DL_CLDLS
+ || dia->dl_addr_length != ETHERADDRL + 2
+ || dia->dl_brdcst_addr_length != ETHERADDRL) {
+ zlog_warn("%s: unsupported interface type for %s", __func__,
+ circuit->interface->name);
+ close(fd);
+ return ISIS_WARNING;
+ }
+ switch (dia->dl_mac_type) {
+ case DL_CSMACD:
+ case DL_ETHER:
+ case DL_100VG:
+ case DL_100VGTPR:
+ case DL_ETH_CSMA:
+ case DL_100BT:
+ break;
+ default:
+ zlog_warn("%s: unexpected mac type on %s: %lld", __func__,
+ circuit->interface->name,
+ (long long)dia->dl_mac_type);
+ close(fd);
+ return ISIS_WARNING;
+ }
+
+ circuit->sap_length = dia->dl_sap_length;
+
+ /*
+ * The local hardware address is something that should be provided by
+ * way of
+ * sockaddr_dl for the interface, but isn't on Solaris. We set it here
+ * based
+ * on DLPI's reported address to avoid roto-tilling the world.
+ * (Note that isis_circuit_if_add on Solaris doesn't set the snpa.)
+ *
+ * Unfortunately, GLD is broken and doesn't provide the address after
+ * attach,
+ * so we need to be careful and use DL_PHYS_ADDR_REQ instead.
+ */
+ if (dlpiaddr(fd, circuit->u.bc.snpa) == -1) {
+ zlog_warn("%s: interface %s: unable to get MAC address",
+ __func__, circuit->interface->name);
+ close(fd);
+ return ISIS_WARNING;
+ }
+
+ /* Now bind to SAP 0. This gives us 802-type traffic. */
+ if (dlpibind(fd) == -1) {
+ zlog_warn("%s: cannot bind SAP 0 on %s", __func__,
+ circuit->interface->name);
+ close(fd);
+ return ISIS_WARNING;
+ }
+
+ /*
+ * Join to multicast groups according to
+ * 8.4.2 - Broadcast subnetwork IIH PDUs
+ */
+ retval = 0;
+ retval |= dlpimcast(fd, ALL_L1_ISS);
+ retval |= dlpimcast(fd, ALL_ISS);
+ retval |= dlpimcast(fd, ALL_L2_ISS);
+
+ if (retval != 0) {
+ zlog_warn("%s: unable to join multicast on %s", __func__,
+ circuit->interface->name);
+ close(fd);
+ return ISIS_WARNING;
+ }
+
+ /* Push on the packet filter to avoid stray 802 packets */
+ if (ioctl(fd, I_PUSH, "pfmod") == 0) {
+ struct packetfilt pfil;
+ struct strioctl sioc;
+
+ pfil.Pf_Priority = 0;
+ pfil.Pf_FilterLen = array_size(pf_filter);
+ memcpy(pfil.Pf_Filter, pf_filter, sizeof(pf_filter));
+ /* pfmod does not support transparent ioctls */
+ sioc.ic_cmd = PFIOCSETF;
+ sioc.ic_timout = 5;
+ sioc.ic_len = sizeof(struct packetfilt);
+ sioc.ic_dp = (char *)&pfil;
+ if (ioctl(fd, I_STR, &sioc) == -1)
+ zlog_warn("%s: could not perform PF_IOCSETF on %s",
+ __func__, circuit->interface->name);
+ }
+
+ circuit->fd = fd;
+
+ return ISIS_OK;
+}
+
+/*
+ * Create the socket and set the tx/rx funcs
+ */
+int isis_sock_init(struct isis_circuit *circuit)
+{
+ int retval = ISIS_OK;
+
+ frr_with_privs(&isisd_privs) {
+
+ retval = open_dlpi_dev(circuit);
+
+ if (retval != ISIS_OK) {
+ zlog_warn("%s: could not initialize the socket",
+ __func__);
+ break;
+ }
+
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ circuit->tx = isis_send_pdu_bcast;
+ circuit->rx = isis_recv_pdu_bcast;
+ } else {
+ zlog_warn("%s: unknown circuit type", __func__);
+ retval = ISIS_WARNING;
+ break;
+ }
+ }
+
+ return retval;
+}
+
+int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa)
+{
+ struct pollfd fds[1];
+ struct strbuf ctlbuf, databuf;
+ int flags, retv;
+ dl_unitdata_ind_t *dui = (dl_unitdata_ind_t *)dlpi_ctl;
+
+ memset(fds, 0, sizeof(fds));
+ fds[0].fd = circuit->fd;
+ fds[0].events = POLLIN | POLLPRI;
+ if (poll(fds, 1, 0) <= 0)
+ return ISIS_WARNING;
+
+ memset(&ctlbuf, 0, sizeof(ctlbuf));
+ memset(&databuf, 0, sizeof(databuf));
+ ctlbuf.maxlen = sizeof(dlpi_ctl);
+ ctlbuf.buf = (void *)dlpi_ctl;
+ databuf.maxlen = sizeof(sock_buff);
+ databuf.buf = (void *)sock_buff;
+ flags = 0;
+ retv = getmsg(circuit->fd, &ctlbuf, &databuf, &flags);
+
+ if (retv < 0) {
+ zlog_warn("%s: getmsg failed: %s", __func__,
+ safe_strerror(errno));
+ return ISIS_WARNING;
+ }
+
+ if (retv & (MORECTL | MOREDATA)) {
+ while (retv & (MORECTL | MOREDATA)) {
+ flags = 0;
+ retv = getmsg(circuit->fd, &ctlbuf, &databuf, &flags);
+ }
+ return ISIS_WARNING;
+ }
+
+ if (ctlbuf.len < (ssize_t)DL_UNITDATA_IND_SIZE
+ || dui->dl_primitive != DL_UNITDATA_IND)
+ return ISIS_WARNING;
+
+ if (dui->dl_src_addr_length != ETHERADDRL + 2
+ || dui->dl_src_addr_offset < DL_UNITDATA_IND_SIZE
+ || dui->dl_src_addr_offset + dui->dl_src_addr_length
+ > (size_t)ctlbuf.len)
+ return ISIS_WARNING;
+
+ memcpy(ssnpa,
+ (char *)dui + dui->dl_src_addr_offset
+ + (circuit->sap_length > 0 ? circuit->sap_length : 0),
+ ETHERADDRL);
+
+ if (databuf.len < LLC_LEN || sock_buff[0] != ISO_SAP
+ || sock_buff[1] != ISO_SAP || sock_buff[2] != 3)
+ return ISIS_WARNING;
+
+ stream_write(circuit->rcv_stream, sock_buff + LLC_LEN,
+ databuf.len - LLC_LEN);
+ stream_set_getp(circuit->rcv_stream, 0);
+
+ return ISIS_OK;
+}
+
+int isis_send_pdu_bcast(struct isis_circuit *circuit, int level)
+{
+ dl_unitdata_req_t *dur = (dl_unitdata_req_t *)dlpi_ctl;
+ char *dstaddr;
+ unsigned short *dstsap;
+ int buflen;
+ int rv;
+
+ buflen = stream_get_endp(circuit->snd_stream) + LLC_LEN;
+ if ((size_t)buflen > sizeof(sock_buff)) {
+ zlog_warn(
+ "%s: sock_buff size %zu is less than output pdu size %d on circuit %s",
+ __func__, sizeof(sock_buff), buflen,
+ circuit->interface->name);
+ return ISIS_WARNING;
+ }
+
+ stream_set_getp(circuit->snd_stream, 0);
+
+ memset(dur, 0, sizeof(*dur));
+ dur->dl_primitive = DL_UNITDATA_REQ;
+ dur->dl_dest_addr_length = ETHERADDRL + 2;
+ dur->dl_dest_addr_offset = sizeof(*dur);
+
+ dstaddr = (char *)(dur + 1);
+ if (circuit->sap_length < 0) {
+ dstsap = (unsigned short *)(dstaddr + ETHERADDRL);
+ } else {
+ dstsap = (unsigned short *)dstaddr;
+ dstaddr += circuit->sap_length;
+ }
+ if (level == 1)
+ memcpy(dstaddr, ALL_L1_ISS, ETHERADDRL);
+ else
+ memcpy(dstaddr, ALL_L2_ISS, ETHERADDRL);
+ /* Note: DLPI SAP values are in host byte order */
+ *dstsap = buflen;
+
+ sock_buff[0] = ISO_SAP;
+ sock_buff[1] = ISO_SAP;
+ sock_buff[2] = 0x03;
+ memcpy(sock_buff + LLC_LEN, circuit->snd_stream->data,
+ stream_get_endp(circuit->snd_stream));
+ rv = dlpisend(circuit->fd, dur, sizeof(*dur) + dur->dl_dest_addr_length,
+ sock_buff, buflen, 0);
+ if (rv < 0) {
+ zlog_warn("IS-IS dlpi: could not transmit packet on %s: %s",
+ circuit->interface->name, safe_strerror(errno));
+ if (ERRNO_IO_RETRY(errno))
+ return ISIS_WARNING;
+ return ISIS_ERROR;
+ }
+
+ return ISIS_OK;
+}
+
+#endif /* ISIS_METHOD == ISIS_METHOD_DLPI */
diff --git a/isisd/isis_dr.c b/isisd/isis_dr.c
new file mode 100644
index 0000000..3b92160
--- /dev/null
+++ b/isisd/isis_dr.c
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_dr.c
+ * IS-IS designated router related routines
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+
+#include <zebra.h>
+
+#include "log.h"
+#include "hash.h"
+#include "frrevent.h"
+#include "linklist.h"
+#include "vty.h"
+#include "stream.h"
+#include "if.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_dr.h"
+#include "isisd/isis_events.h"
+
+const char *isis_disflag2string(int disflag)
+{
+
+ switch (disflag) {
+ case ISIS_IS_NOT_DIS:
+ return "is not DIS";
+ case ISIS_IS_DIS:
+ return "is DIS";
+ case ISIS_WAS_DIS:
+ return "was DIS";
+ default:
+ return "unknown DIS state";
+ }
+ return NULL; /* not reached */
+}
+
+void isis_run_dr(struct event *thread)
+{
+ struct isis_circuit_arg *arg = EVENT_ARG(thread);
+
+ assert(arg);
+
+ struct isis_circuit *circuit = arg->circuit;
+ int level = arg->level;
+
+ assert(circuit);
+
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST) {
+ zlog_warn("%s: scheduled for non broadcast circuit from %s:%d",
+ __func__, thread->xref->xref.file,
+ thread->xref->xref.line);
+ return;
+ }
+
+ if (circuit->u.bc.run_dr_elect[level - 1])
+ zlog_warn("%s: run_dr_elect already set for l%d", __func__,
+ level);
+
+ circuit->u.bc.t_run_dr[level - 1] = NULL;
+ circuit->u.bc.run_dr_elect[level - 1] = 1;
+}
+
+static int isis_check_dr_change(struct isis_adjacency *adj, int level)
+{
+ int i;
+
+ if (adj->dis_record[level - 1].dis
+ != adj->dis_record[(1 * ISIS_LEVELS) + level - 1].dis)
+ /* was there a DIS state transition ? */
+ {
+ adj->dischanges[level - 1]++;
+ adj->circuit->desig_changes[level - 1]++;
+ /* ok rotate the history list through */
+ for (i = DIS_RECORDS - 1; i > 0; i--) {
+ adj->dis_record[(i * ISIS_LEVELS) + level - 1].dis =
+ adj->dis_record[((i - 1) * ISIS_LEVELS) + level
+ - 1]
+ .dis;
+ adj->dis_record[(i * ISIS_LEVELS) + level - 1]
+ .last_dis_change =
+ adj->dis_record[((i - 1) * ISIS_LEVELS) + level
+ - 1]
+ .last_dis_change;
+ }
+ }
+ return ISIS_OK;
+}
+
+int isis_dr_elect(struct isis_circuit *circuit, int level)
+{
+ struct list *adjdb;
+ struct listnode *node;
+ struct isis_adjacency *adj, *adj_dr = NULL;
+ struct list *list = list_new();
+ uint8_t own_prio;
+ int biggest_prio = -1;
+ int cmp_res, retval = ISIS_OK;
+
+ own_prio = circuit->priority[level - 1];
+ adjdb = circuit->u.bc.adjdb[level - 1];
+
+ if (!adjdb) {
+ zlog_warn("%s adjdb == NULL", __func__);
+ list_delete(&list);
+ return ISIS_WARNING;
+ }
+ isis_adj_build_up_list(adjdb, list);
+
+ /*
+ * Loop the adjacencies and find the one with the biggest priority
+ */
+ for (ALL_LIST_ELEMENTS_RO(list, node, adj)) {
+ /* clear flag for show output */
+ adj->dis_record[level - 1].dis = ISIS_IS_NOT_DIS;
+ adj->dis_record[level - 1].last_dis_change = time(NULL);
+
+ if (adj->prio[level - 1] > biggest_prio) {
+ biggest_prio = adj->prio[level - 1];
+ adj_dr = adj;
+ } else if (adj->prio[level - 1] == biggest_prio) {
+ /*
+ * Comparison of MACs breaks a tie
+ */
+ if (adj_dr) {
+ cmp_res = memcmp(adj_dr->snpa, adj->snpa,
+ ETH_ALEN);
+ if (cmp_res < 0) {
+ adj_dr = adj;
+ }
+ if (cmp_res == 0)
+ zlog_warn(
+ "%s: multiple adjacencies with same SNPA",
+ __func__);
+ } else {
+ adj_dr = adj;
+ }
+ }
+ }
+
+ if (!adj_dr) {
+ /*
+ * Could not find the DR - means we are alone. Resign if we were
+ * DR.
+ */
+ if (circuit->u.bc.is_dr[level - 1])
+ retval = isis_dr_resign(circuit, level);
+ list_delete(&list);
+ return retval;
+ }
+
+ /*
+ * Now we have the DR adjacency, compare it to self
+ */
+ if (adj_dr->prio[level - 1] < own_prio
+ || (adj_dr->prio[level - 1] == own_prio
+ && memcmp(adj_dr->snpa, circuit->u.bc.snpa, ETH_ALEN) < 0)) {
+ adj_dr->dis_record[level - 1].dis = ISIS_IS_NOT_DIS;
+ adj_dr->dis_record[level - 1].last_dis_change = time(NULL);
+
+ /* rotate the history log */
+ for (ALL_LIST_ELEMENTS_RO(list, node, adj))
+ isis_check_dr_change(adj, level);
+
+ /* We are the DR, commence DR */
+ if (circuit->u.bc.is_dr[level - 1] == 0 && listcount(list) > 0)
+ retval = isis_dr_commence(circuit, level);
+ } else {
+ /* ok we have found the DIS - lets mark the adjacency */
+ /* set flag for show output */
+ adj_dr->dis_record[level - 1].dis = ISIS_IS_DIS;
+ adj_dr->dis_record[level - 1].last_dis_change = time(NULL);
+
+ /* now loop through a second time to check if there has been a
+ * DIS change
+ * if yes rotate the history log
+ */
+
+ for (ALL_LIST_ELEMENTS_RO(list, node, adj))
+ isis_check_dr_change(adj, level);
+
+ /*
+ * We are not DR - if we were -> resign
+ */
+ if (circuit->u.bc.is_dr[level - 1])
+ retval = isis_dr_resign(circuit, level);
+ }
+ list_delete(&list);
+ return retval;
+}
+
+int isis_dr_resign(struct isis_circuit *circuit, int level)
+{
+ uint8_t id[ISIS_SYS_ID_LEN + 2];
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("%s l%d", __func__, level);
+
+ circuit->u.bc.is_dr[level - 1] = 0;
+ circuit->u.bc.run_dr_elect[level - 1] = 0;
+ EVENT_OFF(circuit->u.bc.t_run_dr[level - 1]);
+ EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[level - 1]);
+ circuit->lsp_regenerate_pending[level - 1] = 0;
+
+ memcpy(id, circuit->isis->sysid, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(id) = circuit->circuit_id;
+ LSP_FRAGMENT(id) = 0;
+ lsp_purge_pseudo(id, circuit, level);
+
+ if (level == 1) {
+ memset(circuit->u.bc.l1_desig_is, 0, ISIS_SYS_ID_LEN + 1);
+
+ event_add_timer(master, send_l1_psnp, circuit,
+ isis_jitter(circuit->psnp_interval[level - 1],
+ PSNP_JITTER),
+ &circuit->t_send_psnp[0]);
+ } else {
+ memset(circuit->u.bc.l2_desig_is, 0, ISIS_SYS_ID_LEN + 1);
+
+ event_add_timer(master, send_l2_psnp, circuit,
+ isis_jitter(circuit->psnp_interval[level - 1],
+ PSNP_JITTER),
+ &circuit->t_send_psnp[1]);
+ }
+
+ EVENT_OFF(circuit->t_send_csnp[level - 1]);
+
+ event_add_timer(master, isis_run_dr, &circuit->level_arg[level - 1],
+ 2 * circuit->hello_interval[level - 1],
+ &circuit->u.bc.t_run_dr[level - 1]);
+
+
+ event_add_event(master, isis_event_dis_status_change, circuit, 0, NULL);
+
+ return ISIS_OK;
+}
+
+int isis_dr_commence(struct isis_circuit *circuit, int level)
+{
+ uint8_t old_dr[ISIS_SYS_ID_LEN + 2];
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("%s l%d", __func__, level);
+
+ /* Lets keep a pause in DR election */
+ circuit->u.bc.run_dr_elect[level - 1] = 0;
+ circuit->u.bc.is_dr[level - 1] = 1;
+
+ if (level == 1) {
+ memcpy(old_dr, circuit->u.bc.l1_desig_is, ISIS_SYS_ID_LEN + 1);
+ LSP_FRAGMENT(old_dr) = 0;
+ if (LSP_PSEUDO_ID(old_dr)) {
+ /* there was a dr elected, purge its LSPs from the db */
+ lsp_purge_pseudo(old_dr, circuit, level);
+ }
+ memcpy(circuit->u.bc.l1_desig_is, circuit->isis->sysid,
+ ISIS_SYS_ID_LEN);
+ *(circuit->u.bc.l1_desig_is + ISIS_SYS_ID_LEN) =
+ circuit->circuit_id;
+
+ assert(circuit->circuit_id); /* must be non-zero */
+ lsp_generate_pseudo(circuit, 1);
+
+ event_add_timer(master, send_l1_csnp, circuit,
+ isis_jitter(circuit->csnp_interval[level - 1],
+ CSNP_JITTER),
+ &circuit->t_send_csnp[0]);
+
+ } else {
+ memcpy(old_dr, circuit->u.bc.l2_desig_is, ISIS_SYS_ID_LEN + 1);
+ LSP_FRAGMENT(old_dr) = 0;
+ if (LSP_PSEUDO_ID(old_dr)) {
+ /* there was a dr elected, purge its LSPs from the db */
+ lsp_purge_pseudo(old_dr, circuit, level);
+ }
+ memcpy(circuit->u.bc.l2_desig_is, circuit->isis->sysid,
+ ISIS_SYS_ID_LEN);
+ *(circuit->u.bc.l2_desig_is + ISIS_SYS_ID_LEN) =
+ circuit->circuit_id;
+
+ assert(circuit->circuit_id); /* must be non-zero */
+ lsp_generate_pseudo(circuit, 2);
+
+ event_add_timer(master, send_l2_csnp, circuit,
+ isis_jitter(circuit->csnp_interval[level - 1],
+ CSNP_JITTER),
+ &circuit->t_send_csnp[1]);
+ }
+
+ event_add_timer(master, isis_run_dr, &circuit->level_arg[level - 1],
+ 2 * circuit->hello_interval[level - 1],
+ &circuit->u.bc.t_run_dr[level - 1]);
+ event_add_event(master, isis_event_dis_status_change, circuit, 0, NULL);
+
+ return ISIS_OK;
+}
diff --git a/isisd/isis_dr.h b/isisd/isis_dr.h
new file mode 100644
index 0000000..135916a
--- /dev/null
+++ b/isisd/isis_dr.h
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_dr.h
+ * IS-IS designated router related routines
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#ifndef _ZEBRA_ISIS_DR_H
+#define _ZEBRA_ISIS_DR_H
+
+void isis_run_dr(struct event *thread);
+int isis_dr_elect(struct isis_circuit *circuit, int level);
+int isis_dr_resign(struct isis_circuit *circuit, int level);
+int isis_dr_commence(struct isis_circuit *circuit, int level);
+const char *isis_disflag2string(int disflag);
+
+enum isis_dis_state {
+ ISIS_IS_NOT_DIS,
+ ISIS_IS_DIS,
+ ISIS_WAS_DIS,
+ ISIS_UNKNOWN_DIS
+};
+
+#endif /* _ZEBRA_ISIS_DR_H */
diff --git a/isisd/isis_dynhn.c b/isisd/isis_dynhn.c
new file mode 100644
index 0000000..61c49d0
--- /dev/null
+++ b/isisd/isis_dynhn.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_dynhn.c
+ * Dynamic hostname cache
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+
+#include "vty.h"
+#include "linklist.h"
+#include "memory.h"
+#include "log.h"
+#include "stream.h"
+#include "command.h"
+#include "if.h"
+#include "frrevent.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_dynhn.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_constants.h"
+
+DEFINE_MTYPE_STATIC(ISISD, ISIS_DYNHN, "ISIS dyn hostname");
+
+static void dyn_cache_cleanup(struct event *);
+
+void dyn_cache_init(struct isis *isis)
+{
+ isis->dyn_cache = list_new();
+ if (!CHECK_FLAG(im->options, F_ISIS_UNIT_TEST))
+ event_add_timer(master, dyn_cache_cleanup, isis, 120,
+ &isis->t_dync_clean);
+}
+
+void dyn_cache_finish(struct isis *isis)
+{
+ struct listnode *node, *nnode;
+ struct isis_dynhn *dyn;
+
+ EVENT_OFF(isis->t_dync_clean);
+
+ for (ALL_LIST_ELEMENTS(isis->dyn_cache, node, nnode, dyn)) {
+ list_delete_node(isis->dyn_cache, node);
+ XFREE(MTYPE_ISIS_DYNHN, dyn);
+ }
+
+ list_delete(&isis->dyn_cache);
+}
+
+static void dyn_cache_cleanup(struct event *thread)
+{
+ struct listnode *node, *nnode;
+ struct isis_dynhn *dyn;
+ time_t now = time(NULL);
+ struct isis *isis = NULL;
+
+ isis = EVENT_ARG(thread);
+
+ isis->t_dync_clean = NULL;
+
+ for (ALL_LIST_ELEMENTS(isis->dyn_cache, node, nnode, dyn)) {
+ if ((now - dyn->refresh) < MAX_LSP_LIFETIME)
+ continue;
+ list_delete_node(isis->dyn_cache, node);
+ XFREE(MTYPE_ISIS_DYNHN, dyn);
+ }
+
+ event_add_timer(master, dyn_cache_cleanup, isis, 120,
+ &isis->t_dync_clean);
+}
+
+struct isis_dynhn *dynhn_find_by_id(struct isis *isis, const uint8_t *id)
+{
+ struct listnode *node = NULL;
+ struct isis_dynhn *dyn = NULL;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->dyn_cache, node, dyn))
+ if (memcmp(dyn->id, id, ISIS_SYS_ID_LEN) == 0)
+ return dyn;
+
+ return NULL;
+}
+
+struct isis_dynhn *dynhn_find_by_name(struct isis *isis, const char *hostname)
+{
+ struct listnode *node = NULL;
+ struct isis_dynhn *dyn = NULL;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->dyn_cache, node, dyn))
+ if (strncmp(dyn->hostname, hostname, 255) == 0)
+ return dyn;
+
+ return NULL;
+}
+
+void isis_dynhn_insert(struct isis *isis, const uint8_t *id,
+ const char *hostname, int level)
+{
+ struct isis_dynhn *dyn;
+
+ dyn = dynhn_find_by_id(isis, id);
+ if (!dyn) {
+ dyn = XCALLOC(MTYPE_ISIS_DYNHN, sizeof(struct isis_dynhn));
+ memcpy(dyn->id, id, ISIS_SYS_ID_LEN);
+ dyn->level = level;
+ listnode_add(isis->dyn_cache, dyn);
+ }
+
+ snprintf(dyn->hostname, sizeof(dyn->hostname), "%s", hostname);
+ dyn->refresh = time(NULL);
+}
+
+void isis_dynhn_remove(struct isis *isis, const uint8_t *id)
+{
+ struct isis_dynhn *dyn;
+
+ dyn = dynhn_find_by_id(isis, id);
+ if (!dyn)
+ return;
+ listnode_delete(isis->dyn_cache, dyn);
+ XFREE(MTYPE_ISIS_DYNHN, dyn);
+}
+
+/*
+ * Level System ID Dynamic Hostname (notag)
+ * 2 0000.0000.0001 foo-gw
+ * 2 0000.0000.0002 bar-gw
+ * * 0000.0000.0004 this-gw
+ */
+void dynhn_print_all(struct vty *vty, struct isis *isis)
+{
+ struct listnode *node;
+ struct isis_dynhn *dyn;
+
+ vty_out(vty, "vrf : %s\n", isis->name);
+ if (!isis->sysid_set)
+ return;
+ vty_out(vty, "Level System ID Dynamic Hostname\n");
+ for (ALL_LIST_ELEMENTS_RO(isis->dyn_cache, node, dyn)) {
+ vty_out(vty, "%-7d", dyn->level);
+ vty_out(vty, "%pSY %-15s\n", dyn->id, dyn->hostname);
+ }
+
+ vty_out(vty, " * %pSY %s\n", isis->sysid, cmd_hostname_get());
+ return;
+}
+
+struct isis_dynhn *dynhn_snmp_next(struct isis *isis, const uint8_t *id,
+ int level)
+{
+ struct listnode *node = NULL;
+ struct isis_dynhn *dyn = NULL;
+ struct isis_dynhn *found_dyn = NULL;
+ int res;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->dyn_cache, node, dyn)) {
+ res = memcmp(dyn->id, id, ISIS_SYS_ID_LEN);
+
+ if (res < 0)
+ continue;
+
+ if (res == 0 && dyn->level <= level)
+ continue;
+
+ if (res == 0) {
+ /*
+ * This is the best match, we can stop
+ * searching
+ */
+
+ found_dyn = dyn;
+ break;
+ }
+
+ if (found_dyn == NULL
+ || memcmp(dyn->id, found_dyn->id, ISIS_SYS_ID_LEN) < 0) {
+ found_dyn = dyn;
+ }
+ }
+
+ return found_dyn;
+}
diff --git a/isisd/isis_dynhn.h b/isisd/isis_dynhn.h
new file mode 100644
index 0000000..d4deb55
--- /dev/null
+++ b/isisd/isis_dynhn.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_dynhn.h
+ * Dynamic hostname cache
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+#ifndef _ZEBRA_ISIS_DYNHN_H
+#define _ZEBRA_ISIS_DYNHN_H
+
+struct isis_dynhn {
+ uint8_t id[ISIS_SYS_ID_LEN];
+ char hostname[256];
+ time_t refresh;
+ int level;
+};
+
+void dyn_cache_init(struct isis *isis);
+void dyn_cache_finish(struct isis *isis);
+void isis_dynhn_insert(struct isis *isis, const uint8_t *id,
+ const char *hostname, int level);
+void isis_dynhn_remove(struct isis *isis, const uint8_t *id);
+struct isis_dynhn *dynhn_find_by_id(struct isis *isis, const uint8_t *id);
+struct isis_dynhn *dynhn_find_by_name(struct isis *isis, const char *hostname);
+void dynhn_print_all(struct vty *vty, struct isis *isis);
+
+/* Snmp support */
+struct isis_dynhn *dynhn_snmp_next(struct isis *isis, const uint8_t *id,
+ int level);
+
+#endif /* _ZEBRA_ISIS_DYNHN_H */
diff --git a/isisd/isis_errors.c b/isisd/isis_errors.c
new file mode 100644
index 0000000..0bdff19
--- /dev/null
+++ b/isisd/isis_errors.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ISIS-specific error messages.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ */
+
+#include <zebra.h>
+
+#include "lib/ferr.h"
+#include "isis_errors.h"
+
+/* clang-format off */
+static struct log_ref ferr_isis_err[] = {
+ {
+ .code = EC_ISIS_PACKET,
+ .title = "ISIS Packet Error",
+ .description = "Isis has detected an error with a packet from a peer",
+ .suggestion = "Gather log information and open an issue then restart FRR"
+ },
+ {
+ .code = EC_ISIS_CONFIG,
+ .title = "ISIS Configuration Error",
+ .description = "Isis has detected an error within configuration for the router",
+ .suggestion = "Ensure configuration is correct"
+ },
+ {
+ .code = EC_ISIS_SID_OVERFLOW,
+ .title = "SID index overflow",
+ .description = "Isis has detected that a SID index falls outside of its associated SRGB range",
+ .suggestion = "Configure a larger SRGB"
+ },
+ {
+ .code = EC_ISIS_SID_COLLISION,
+ .title = "SID collision",
+ .description = "Isis has detected that two different prefixes share the same SID index",
+ .suggestion = "Identify the routers that are advertising the same SID index and fix the collision accordingly"
+ },
+ {
+ .code = END_FERR,
+ }
+};
+/* clang-format on */
+
+void isis_error_init(void)
+{
+ log_ref_add(ferr_isis_err);
+}
diff --git a/isisd/isis_errors.h b/isisd/isis_errors.h
new file mode 100644
index 0000000..9a21c79
--- /dev/null
+++ b/isisd/isis_errors.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ISIS-specific error messages.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ */
+
+#ifndef __ISIS_ERRORS_H__
+#define __ISIS_ERRORS_H__
+
+#include "lib/ferr.h"
+
+enum isis_log_refs {
+ EC_ISIS_PACKET = ISIS_FERR_START,
+ EC_ISIS_CONFIG,
+ EC_ISIS_SID_OVERFLOW,
+ EC_ISIS_SID_COLLISION,
+};
+
+extern void isis_error_init(void);
+
+#endif
diff --git a/isisd/isis_events.c b/isisd/isis_events.c
new file mode 100644
index 0000000..32231a0
--- /dev/null
+++ b/isisd/isis_events.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_events.h
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+#include <zebra.h>
+
+#include "log.h"
+#include "memory.h"
+#include "if.h"
+#include "linklist.h"
+#include "command.h"
+#include "frrevent.h"
+#include "hash.h"
+#include "prefix.h"
+#include "stream.h"
+#include "table.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_network.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_dr.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isis_events.h"
+#include "isisd/isis_spf.h"
+#include "isisd/isis_errors.h"
+
+void isis_event_circuit_state_change(struct isis_circuit *circuit,
+ struct isis_area *area, int up)
+{
+ area->circuit_state_changes++;
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("ISIS-Evt (%s) circuit %s", area->area_tag,
+ up ? "up" : "down");
+
+ /*
+ * Regenerate LSPs this affects
+ */
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0);
+
+ return;
+}
+
+static void circuit_commence_level(struct isis_circuit *circuit, int level)
+{
+ if (IS_DEBUG_EVENTS)
+ zlog_debug(
+ "ISIS-Evt (%s) circuit %u on iface %s commencing on L%d",
+ circuit->area->area_tag, circuit->circuit_id,
+ circuit->interface->name, level);
+
+ if (!circuit->is_passive) {
+ if (level == 1) {
+ event_add_timer(master, send_l1_psnp, circuit,
+ isis_jitter(circuit->psnp_interval[0],
+ PSNP_JITTER),
+ &circuit->t_send_psnp[0]);
+ } else {
+ event_add_timer(master, send_l2_psnp, circuit,
+ isis_jitter(circuit->psnp_interval[1],
+ PSNP_JITTER),
+ &circuit->t_send_psnp[1]);
+ }
+ }
+
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ event_add_timer(master, isis_run_dr,
+ &circuit->level_arg[level - 1],
+ 2 * circuit->hello_interval[level - 1],
+ &circuit->u.bc.t_run_dr[level - 1]);
+
+ send_hello_sched(circuit, level, TRIGGERED_IIH_DELAY);
+ circuit->u.bc.lan_neighs[level - 1] = list_new();
+ }
+}
+
+static void circuit_resign_level(struct isis_circuit *circuit, int level)
+{
+ int idx = level - 1;
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug(
+ "ISIS-Evt (%s) circuit %u on iface %s resigning on L%d",
+ circuit->area->area_tag, circuit->circuit_id,
+ circuit->interface->name, level);
+
+ EVENT_OFF(circuit->t_send_csnp[idx]);
+ EVENT_OFF(circuit->t_send_psnp[idx]);
+
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ EVENT_OFF(circuit->u.bc.t_send_lan_hello[idx]);
+ EVENT_OFF(circuit->u.bc.t_run_dr[idx]);
+ EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[idx]);
+ circuit->lsp_regenerate_pending[idx] = 0;
+ circuit->u.bc.run_dr_elect[idx] = 0;
+ circuit->u.bc.is_dr[idx] = 0;
+ if (circuit->u.bc.lan_neighs[idx] != NULL)
+ list_delete(&circuit->u.bc.lan_neighs[idx]);
+ }
+
+ return;
+}
+
+void isis_circuit_is_type_set(struct isis_circuit *circuit, int newtype)
+{
+ if (!circuit->area) {
+ circuit->is_type = newtype;
+ return;
+ }
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("ISIS-Evt (%s) circuit type change %s -> %s",
+ circuit->area->area_tag,
+ circuit_t2string(circuit->is_type),
+ circuit_t2string(newtype));
+
+ if (circuit->is_type == newtype)
+ return; /* No change */
+
+ if (!(newtype & circuit->area->is_type)) {
+ flog_err(
+ EC_ISIS_CONFIG,
+ "ISIS-Evt (%s) circuit type change - invalid level %s because area is %s",
+ circuit->area->area_tag, circuit_t2string(newtype),
+ circuit_t2string(circuit->area->is_type));
+ return;
+ }
+
+ if (circuit->state != C_STATE_UP) {
+ circuit->is_type = newtype;
+ return;
+ }
+
+ if (!circuit->is_passive) {
+ switch (circuit->is_type) {
+ case IS_LEVEL_1:
+ if (newtype == IS_LEVEL_2)
+ circuit_resign_level(circuit, 1);
+ circuit_commence_level(circuit, 2);
+ break;
+ case IS_LEVEL_1_AND_2:
+ if (newtype == IS_LEVEL_1)
+ circuit_resign_level(circuit, 2);
+ else
+ circuit_resign_level(circuit, 1);
+ break;
+ case IS_LEVEL_2:
+ if (newtype == IS_LEVEL_1)
+ circuit_resign_level(circuit, 2);
+ circuit_commence_level(circuit, 1);
+ break;
+ default:
+ break;
+ }
+ }
+
+ circuit->is_type = newtype;
+ lsp_regenerate_schedule(circuit->area, IS_LEVEL_1 | IS_LEVEL_2, 0);
+
+ return;
+}
+
+/* 04/18/2002 by Gwak. */
+/**************************************************************************
+ *
+ * EVENTS for LSP generation
+ *
+ * 1) an Adajacency or Circuit Up/Down event
+ * 2) a chnage in Circuit metric
+ * 3) a change in Reachable Address metric
+ * 4) a change in manualAreaAddresses
+ * 5) a change in systemID
+ * 6) a change in DIS status
+ * 7) a chnage in the waiting status
+ *
+ * ***********************************************************************
+ *
+ * current support event
+ *
+ * 1) Adjacency Up/Down event
+ * 6) a change in DIS status
+ *
+ * ***********************************************************************/
+
+/* events supporting code */
+
+void isis_event_dis_status_change(struct event *thread)
+{
+ struct isis_circuit *circuit;
+
+ circuit = EVENT_ARG(thread);
+
+ /* invalid arguments */
+ if (!circuit || !circuit->area)
+ return;
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("ISIS-Evt (%s) DIS status change",
+ circuit->area->area_tag);
+
+ /* LSP generation again */
+ lsp_regenerate_schedule(circuit->area, IS_LEVEL_1 | IS_LEVEL_2, 0);
+}
+
+void isis_event_auth_failure(char *area_tag, const char *error_string,
+ uint8_t *sysid)
+{
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("ISIS-Evt (%s) Authentication failure %s from %pSY",
+ area_tag, error_string, sysid);
+
+ return;
+}
diff --git a/isisd/isis_events.h b/isisd/isis_events.h
new file mode 100644
index 0000000..a0ac964
--- /dev/null
+++ b/isisd/isis_events.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_events.h
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+#ifndef _ZEBRA_ISIS_EVENTS_H
+#define _ZEBRA_ISIS_EVENTS_H
+
+/*
+ * Events related to circuit
+ */
+void isis_event_circuit_state_change(struct isis_circuit *circuit,
+ struct isis_area *area, int state);
+void isis_event_circuit_type_change(struct isis_circuit *circuit, int newtype);
+/*
+ * Events related to adjacencies
+ */
+void isis_event_dis_status_change(struct event *thread);
+
+/*
+ * Error events
+ */
+#define AUTH_ERROR_TYPE_LSP 3
+#define AUTH_ERROR_TYPE_SNP 2
+#define AUTH_ERROR_TYPE_HELLO 1
+void isis_event_auth_failure(char *area_tag, const char *error_string,
+ uint8_t *sysid);
+
+#endif /* _ZEBRA_ISIS_EVENTS_H */
diff --git a/isisd/isis_flags.c b/isisd/isis_flags.c
new file mode 100644
index 0000000..a621b4b
--- /dev/null
+++ b/isisd/isis_flags.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_flags.c
+ * Routines for manipulation of SSN and SRM flags
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+#include "log.h"
+#include "linklist.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+
+void flags_initialize(struct flags *flags)
+{
+ flags->maxindex = 0;
+ flags->free_idcs = NULL;
+}
+
+long int flags_get_index(struct flags *flags)
+{
+ struct listnode *node;
+ long int index;
+
+ if (flags->free_idcs == NULL || flags->free_idcs->count == 0) {
+ index = flags->maxindex++;
+ } else {
+ node = listhead(flags->free_idcs);
+ index = (long int)listgetdata(node);
+ listnode_delete(flags->free_idcs, (void *)index);
+ index--;
+ }
+
+ return index;
+}
+
+void flags_free_index(struct flags *flags, long int index)
+{
+ if (index + 1 == flags->maxindex) {
+ flags->maxindex--;
+ return;
+ }
+
+ if (flags->free_idcs == NULL) {
+ flags->free_idcs = list_new();
+ }
+
+ listnode_add(flags->free_idcs, (void *)(index + 1));
+
+ return;
+}
+
+int flags_any_set(uint32_t *flags)
+{
+ uint32_t zero[ISIS_MAX_CIRCUITS];
+ memset(zero, 0x00, ISIS_MAX_CIRCUITS * 4);
+
+ return bcmp(flags, zero, ISIS_MAX_CIRCUITS * 4);
+}
diff --git a/isisd/isis_flags.h b/isisd/isis_flags.h
new file mode 100644
index 0000000..47a1356
--- /dev/null
+++ b/isisd/isis_flags.h
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_flags.h
+ * Routines for manipulation of SSN and SRM flags
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#ifndef _ZEBRA_ISIS_FLAGS_H
+#define _ZEBRA_ISIS_FLAGS_H
+
+/* The grand plan is to support 1024 circuits so we have 32*32 bit flags
+ * the support will be achived using the newest drafts */
+#define ISIS_MAX_CIRCUITS 32 /* = 1024 */
+
+/*
+ * Flags structure for SSN and SRM flags
+ */
+struct flags {
+ int maxindex;
+ struct list *free_idcs;
+};
+
+void flags_initialize(struct flags *flags);
+long int flags_get_index(struct flags *flags);
+void flags_free_index(struct flags *flags, long int index);
+int flags_any_set(uint32_t *flags);
+
+#define _ISIS_SET_FLAG(F, C) \
+ { \
+ F[(C) >> 5] |= (1 << ((C)&0x1F)); \
+ }
+#define ISIS_SET_FLAG(F, C) _ISIS_SET_FLAG(F, C->idx)
+
+#define _ISIS_CLEAR_FLAG(F, C) \
+ { \
+ F[(C) >> 5] &= ~(1 << ((C)&0x1F)); \
+ }
+#define ISIS_CLEAR_FLAG(F, C) _ISIS_CLEAR_FLAG(F, C->idx)
+
+#define _ISIS_CHECK_FLAG(F, C) (F[(C)>>5] & (1<<((C) & 0x1F)))
+#define ISIS_CHECK_FLAG(F, C) _ISIS_CHECK_FLAG(F, C->idx)
+
+/* sets all u_32int_t flags to 1 */
+#define ISIS_FLAGS_SET_ALL(FLAGS) \
+ { \
+ memset(FLAGS, 0xFF, ISIS_MAX_CIRCUITS * 4); \
+ }
+
+#define ISIS_FLAGS_CLEAR_ALL(FLAGS) \
+ { \
+ memset(FLAGS, 0x00, ISIS_MAX_CIRCUITS * 4); \
+ }
+
+#endif /* _ZEBRA_ISIS_FLAGS_H */
diff --git a/isisd/isis_flex_algo.c b/isisd/isis_flex_algo.c
new file mode 100644
index 0000000..fbe249a
--- /dev/null
+++ b/isisd/isis_flex_algo.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*********************************************************************
+ * Copyright 2022 Hiroki Shirokura, LINE Corporation
+ * Copyright 2022 Masakazu Asama
+ * Copyright 2022 6WIND S.A.
+ *
+ * isis_flex_algo.c: IS-IS Flexible Algorithm
+ *
+ * Authors
+ * -------
+ * Hiroki Shirokura
+ * Masakazu Asama
+ * Louis Scalbert
+ */
+
+#include <zebra.h>
+
+#include "memory.h"
+#include "stream.h"
+#include "sbuf.h"
+#include "network.h"
+#include "command.h"
+#include "bitfield.h"
+
+#include "isisd/isisd.h"
+#include "isisd/isis_tlvs.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_spf.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_sr.h"
+#include "isisd/isis_spf_private.h"
+#include "isisd/isis_flex_algo.h"
+
+#ifndef FABRICD
+DEFINE_MTYPE_STATIC(ISISD, FLEX_ALGO, "ISIS Flex Algo");
+
+void *isis_flex_algo_data_alloc(void *voidarg)
+{
+ struct isis_flex_algo_alloc_arg *arg = voidarg;
+ struct isis_flex_algo_data *data;
+
+ data = XCALLOC(MTYPE_FLEX_ALGO, sizeof(struct isis_flex_algo_data));
+
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+ if (!(arg->area->is_type & level))
+ continue;
+ data->spftree[tree][level - 1] = isis_spftree_new(
+ arg->area, &arg->area->lspdb[level - 1],
+ arg->area->isis->sysid, level, tree,
+ SPF_TYPE_FORWARD, 0, arg->algorithm);
+ }
+ }
+
+ return data;
+}
+
+void isis_flex_algo_data_free(void *voiddata)
+{
+ struct isis_flex_algo_data *data = voiddata;
+
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++)
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++)
+ if (data->spftree[tree][level - 1])
+ isis_spftree_del(
+ data->spftree[tree][level - 1]);
+ XFREE(MTYPE_FLEX_ALGO, data);
+}
+
+static struct isis_router_cap_fad *
+isis_flex_algo_definition_cmp(struct isis_router_cap_fad *elected,
+ struct isis_router_cap_fad *fa)
+{
+ if (!elected || fa->fad.priority > elected->fad.priority ||
+ (fa->fad.priority == elected->fad.priority &&
+ lsp_id_cmp(fa->sysid, elected->sysid) > 0))
+ return fa;
+
+ return elected;
+}
+
+/**
+ * @brief Look up the flex-algo definition with the highest priority in the LSP
+ * Database (LSDB). If the value of priority is the same, the flex-algo
+ * definition with the highest sysid will be selected.
+ * @param algorithm flex-algo algorithm number
+ * @param area pointer
+ * @param local router capability Flex-Algo Definition (FAD) double pointer.
+ * - fad is NULL: use the local router capability FAD from LSDB for the
+ * election.
+ * - fad is not NULL and *fad is NULL: use no local router capability FAD for
+ * the election.
+ * - fad and *fad are not NULL: uses the *fad local definition instead of the
+ * local definition from LSDB for the election.
+ * @return elected flex-algo-definition object if exist, else NULL
+ */
+static struct isis_router_cap_fad *
+_isis_flex_algo_elected(int algorithm, const struct isis_area *area,
+ struct isis_router_cap_fad **fad)
+{
+ struct flex_algo *flex_ago;
+ const struct isis_lsp *lsp;
+ struct isis_router_cap_fad *fa, *elected = NULL;
+
+ if (!flex_algo_id_valid(algorithm))
+ return NULL;
+
+ /* No elected FAD if the algorithm is not locally configured */
+ flex_ago = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!flex_ago)
+ return NULL;
+
+ /* No elected FAD if no data-plane is enabled
+ * Currently, only Segment-Routing MPLS is supported.
+ * Segment-Routing SRv6 and IP will be configured in the future.
+ */
+ if (!CHECK_FLAG(flex_ago->dataplanes, FLEX_ALGO_SR_MPLS))
+ return NULL;
+
+ /*
+ * Perform FAD comparison. First, compare the priority, and if they are
+ * the same, compare the sys-id.
+ */
+ frr_each (lspdb_const, &area->lspdb[ISIS_LEVEL1 - 1], lsp) {
+ if (!lsp->tlvs || !lsp->tlvs->router_cap)
+ continue;
+
+ if (lsp->own_lsp && fad)
+ continue;
+
+ fa = lsp->tlvs->router_cap->fads[algorithm];
+
+ if (!fa)
+ continue;
+
+ assert(algorithm == fa->fad.algorithm);
+
+ memcpy(fa->sysid, lsp->hdr.lsp_id, ISIS_SYS_ID_LEN + 2);
+
+ elected = isis_flex_algo_definition_cmp(elected, fa);
+ }
+
+ if (fad && *fad)
+ elected = isis_flex_algo_definition_cmp(elected, *fad);
+
+ return elected;
+}
+
+struct isis_router_cap_fad *isis_flex_algo_elected(int algorithm,
+ const struct isis_area *area)
+{
+ return _isis_flex_algo_elected(algorithm, area, NULL);
+}
+
+/**
+ * @brief Check the Flex-Algo Definition is supported by the current FRR version
+ * @param flex-algo
+ * @return true if supported else false
+ */
+bool isis_flex_algo_supported(struct flex_algo *fad)
+{
+ if (fad->calc_type != CALC_TYPE_SPF)
+ return false;
+ if (fad->metric_type != MT_IGP)
+ return false;
+ if (fad->flags != 0)
+ return false;
+ if (fad->exclude_srlg)
+ return false;
+ if (fad->unsupported_subtlv)
+ return false;
+
+ return true;
+}
+
+/**
+ * @brief Look for the elected Flex-Algo Definition and check that it is
+ * supported by the current FRR version
+ * @param algorithm flex-algo algorithm number
+ * @param area pointer
+ * @param local router capability Flex-Algo Definition (FAD) double pointer.
+ * @return elected flex-algo-definition object if exist and supported, else NULL
+ */
+static struct isis_router_cap_fad *
+_isis_flex_algo_elected_supported(int algorithm, const struct isis_area *area,
+ struct isis_router_cap_fad **fad)
+{
+ struct isis_router_cap_fad *elected_fad;
+
+ elected_fad = _isis_flex_algo_elected(algorithm, area, fad);
+ if (!elected_fad)
+ return NULL;
+
+ if (isis_flex_algo_supported(&elected_fad->fad))
+ return elected_fad;
+
+ return NULL;
+}
+
+struct isis_router_cap_fad *
+isis_flex_algo_elected_supported(int algorithm, const struct isis_area *area)
+{
+ return _isis_flex_algo_elected_supported(algorithm, area, NULL);
+}
+
+struct isis_router_cap_fad *
+isis_flex_algo_elected_supported_local_fad(int algorithm,
+ const struct isis_area *area,
+ struct isis_router_cap_fad **fad)
+{
+ return _isis_flex_algo_elected_supported(algorithm, area, fad);
+}
+
+/**
+ * Check LSP is participating specified SR Algorithm
+ *
+ * @param lsp IS-IS lsp
+ * @param algorithm SR Algorithm
+ * @return Return true if sr-algorithm tlv includes specified
+ * algorithm in router capability tlv
+ */
+bool sr_algorithm_participated(const struct isis_lsp *lsp, uint8_t algorithm)
+{
+ if (!lsp || !lsp->tlvs || !lsp->tlvs->router_cap)
+ return false;
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
+ if (lsp->tlvs->router_cap->algo[i] == algorithm)
+ return true;
+ return false;
+}
+
+bool isis_flex_algo_constraint_drop(struct isis_spftree *spftree,
+ struct isis_lsp *lsp,
+ struct isis_extended_reach *reach)
+{
+ bool ret;
+ struct isis_ext_subtlvs *subtlvs = reach->subtlvs;
+ struct isis_router_cap_fad *fad;
+ struct isis_asla_subtlvs *asla;
+ struct listnode *node;
+ uint32_t *link_admin_group = NULL;
+ uint32_t link_ext_admin_group_bitmap0;
+ struct admin_group *link_ext_admin_group = NULL;
+
+ fad = isis_flex_algo_elected_supported(spftree->algorithm,
+ spftree->area);
+ if (!fad)
+ return true;
+
+ for (ALL_LIST_ELEMENTS_RO(subtlvs->aslas, node, asla)) {
+ if (!CHECK_FLAG(asla->standard_apps, ISIS_SABM_FLAG_X))
+ continue;
+ if (asla->legacy) {
+ if (IS_SUBTLV(subtlvs, EXT_ADM_GRP))
+ link_admin_group = &subtlvs->adm_group;
+
+ if (IS_SUBTLV(subtlvs, EXT_EXTEND_ADM_GRP) &&
+ admin_group_nb_words(&subtlvs->ext_admin_group) !=
+ 0)
+ link_ext_admin_group =
+ &subtlvs->ext_admin_group;
+ } else {
+ if (IS_SUBTLV(asla, EXT_ADM_GRP))
+ link_admin_group = &asla->admin_group;
+ if (IS_SUBTLV(asla, EXT_EXTEND_ADM_GRP) &&
+ admin_group_nb_words(&asla->ext_admin_group) != 0)
+ link_ext_admin_group = &asla->ext_admin_group;
+ }
+ break;
+ }
+
+ /* RFC7308 section 2.3.1
+ * A receiving node that notices that the AG differs from the first 32
+ * bits of the EAG SHOULD report this mismatch to the operator.
+ */
+ if (link_admin_group && link_ext_admin_group) {
+ link_ext_admin_group_bitmap0 =
+ admin_group_get_offset(link_ext_admin_group, 0);
+ if (*link_admin_group != link_ext_admin_group_bitmap0)
+ zlog_warn(
+ "ISIS-SPF: LSP from %pPN neighbor %pPN. Admin-group 0x%08x differs from ext admin-group 0x%08x.",
+ lsp->hdr.lsp_id, reach->id, *link_admin_group,
+ link_ext_admin_group_bitmap0);
+ }
+
+ /*
+ * Exclude Any
+ */
+ if (!admin_group_zero(&fad->fad.admin_group_exclude_any)) {
+ ret = admin_group_match_any(&fad->fad.admin_group_exclude_any,
+ link_admin_group,
+ link_ext_admin_group);
+ if (ret)
+ return true;
+ }
+
+ /*
+ * Include Any
+ */
+ if (!admin_group_zero(&fad->fad.admin_group_include_any)) {
+ ret = admin_group_match_any(&fad->fad.admin_group_include_any,
+ link_admin_group,
+ link_ext_admin_group);
+ if (!ret)
+ return true;
+ }
+
+ /*
+ * Include All
+ */
+ if (!admin_group_zero(&fad->fad.admin_group_include_all)) {
+ ret = admin_group_match_all(&fad->fad.admin_group_include_all,
+ link_admin_group,
+ link_ext_admin_group);
+ if (!ret)
+ return true;
+ }
+
+ return false;
+}
+
+#endif /* ifndef FABRICD */
diff --git a/isisd/isis_flex_algo.h b/isisd/isis_flex_algo.h
new file mode 100644
index 0000000..c475838
--- /dev/null
+++ b/isisd/isis_flex_algo.h
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*********************************************************************
+ * Copyright 2022 Hiroki Shirokura, LINE Corporation
+ * Copyright 2022 Masakazu Asama
+ * Copyright 2022 6WIND S.A.
+ *
+ * isis_flex_algo.h: IS-IS Flexible Algorithm
+ *
+ * Authors
+ * -------
+ * Hiroki Shirokura
+ * Masakazu Asama
+ * Louis Scalbert
+ */
+
+#ifndef ISIS_FLEX_ALGO_H
+#define ISIS_FLEX_ALGO_H
+
+#include "flex_algo.h"
+#include "isisd/isis_constants.h"
+
+#ifndef FABRICD
+
+struct isis_flex_algo_data {
+ struct isis_spftree *spftree[SPFTREE_COUNT][ISIS_LEVELS];
+ struct isis_area *area;
+};
+
+struct isis_flex_algo_alloc_arg {
+ uint8_t algorithm;
+ struct isis_area *area;
+};
+
+void *isis_flex_algo_data_alloc(void *arg);
+void isis_flex_algo_data_free(void *data);
+
+struct isis_router_cap_fad *
+isis_flex_algo_elected(int algorithm, const struct isis_area *area);
+bool isis_flex_algo_supported(struct flex_algo *fad);
+struct isis_router_cap_fad *
+isis_flex_algo_elected_supported(int algorithm, const struct isis_area *area);
+struct isis_router_cap_fad *
+isis_flex_algo_elected_supported_local_fad(int algorithm,
+ const struct isis_area *area,
+ struct isis_router_cap_fad **fad);
+struct isis_lsp;
+bool sr_algorithm_participated(const struct isis_lsp *lsp, uint8_t algorithm);
+
+bool isis_flex_algo_constraint_drop(struct isis_spftree *spftree,
+ struct isis_lsp *lsp,
+ struct isis_extended_reach *reach);
+
+#endif /* ifndef FABRICD */
+
+#endif /* ISIS_FLEX_ALGO_H */
diff --git a/isisd/isis_ldp_sync.c b/isisd/isis_ldp_sync.c
new file mode 100644
index 0000000..53676ff
--- /dev/null
+++ b/isisd/isis_ldp_sync.c
@@ -0,0 +1,655 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * isis_ldp_sync.c: ISIS LDP-IGP Sync handling routines
+ * Copyright (C) 2020 Volta Networks, Inc.
+ */
+
+#include <zebra.h>
+#include <string.h>
+
+#include "monotime.h"
+#include "memory.h"
+#include "frrevent.h"
+#include "prefix.h"
+#include "table.h"
+#include "vty.h"
+#include "command.h"
+#include "plist.h"
+#include "log.h"
+#include "zclient.h"
+#include <lib/json.h>
+#include "defaults.h"
+#include "ldp_sync.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_network.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_dr.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isis_events.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_errors.h"
+#include "isisd/isis_tx_queue.h"
+#include "isisd/isis_nb.h"
+#include "isisd/isis_ldp_sync.h"
+
+extern struct zclient *zclient;
+
+/*
+ * LDP-SYNC msg between IGP and LDP
+ */
+int isis_ldp_sync_state_update(struct ldp_igp_sync_if_state state)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit = NULL;
+ struct isis_area *area;
+
+ /* lookup circuit */
+ ifp = if_lookup_by_index(state.ifindex, VRF_DEFAULT);
+ if (ifp == NULL)
+ return 0;
+
+ circuit = ifp->info;
+ if (circuit == NULL)
+ return 0;
+
+ /* if isis is not enabled or LDP-SYNC is not configured ignore */
+ area = circuit->area;
+ if (area == NULL
+ || !CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE))
+ return 0;
+
+ /* received ldp-sync interface state from LDP */
+ ils_debug("%s: rcvd %s from LDP if %s", __func__,
+ state.sync_start ? "sync-start" : "sync-complete", ifp->name);
+ if (state.sync_start)
+ isis_ldp_sync_if_start(circuit, false);
+ else
+ isis_ldp_sync_if_complete(circuit);
+
+ return 0;
+}
+
+int isis_ldp_sync_announce_update(struct ldp_igp_sync_announce announce)
+{
+ struct isis_area *area;
+ struct listnode *anode, *cnode;
+ struct isis_circuit *circuit;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ /* if isis is not enabled ignore */
+ if (!isis)
+ return 0;
+
+ if (announce.proto != ZEBRA_ROUTE_LDP)
+ return 0;
+
+ ils_debug("%s: rcvd announce from LDP", __func__);
+
+ /* LDP just started up:
+ * set cost to LSInfinity
+ * send request to LDP for LDP-SYNC state for each interface
+ */
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) {
+ if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE))
+ continue;
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit))
+ isis_ldp_sync_if_start(circuit, true);
+ }
+
+ return 0;
+}
+
+void isis_ldp_sync_state_req_msg(struct isis_circuit *circuit)
+{
+ struct ldp_igp_sync_if_state_req request;
+ struct interface *ifp = circuit->interface;
+
+ ils_debug("%s: send state request to LDP for %s", __func__, ifp->name);
+
+ memset(&request, 0, sizeof(request));
+ strlcpy(request.name, ifp->name, sizeof(ifp->name));
+ request.proto = LDP_IGP_SYNC_IF_STATE_REQUEST;
+ request.ifindex = ifp->ifindex;
+
+ zclient_send_opaque(zclient, LDP_IGP_SYNC_IF_STATE_REQUEST,
+ (uint8_t *)&request, sizeof(request));
+}
+
+/*
+ * LDP-SYNC general interface routines
+ */
+void isis_ldp_sync_if_start(struct isis_circuit *circuit,
+ bool send_state_req)
+{
+ struct ldp_sync_info *ldp_sync_info;
+
+ ldp_sync_info = circuit->ldp_sync_info;
+
+ /* Start LDP-SYNC on this interface:
+ * set cost of interface to LSInfinity so traffic will use different
+ * interface until LDP has learned all labels from peer
+ * start holddown timer if configured
+ * send msg to LDP to get LDP-SYNC state
+ */
+ if (ldp_sync_info &&
+ ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED &&
+ ldp_sync_info->state != LDP_IGP_SYNC_STATE_NOT_REQUIRED) {
+ ils_debug("%s: start on if %s state: %s", __func__,
+ circuit->interface->name, "Holding down until Sync");
+ ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP;
+ isis_ldp_sync_set_if_metric(circuit, true);
+ isis_ldp_sync_holddown_timer_add(circuit);
+
+ if (send_state_req)
+ isis_ldp_sync_state_req_msg(circuit);
+ }
+}
+
+void isis_ldp_sync_if_complete(struct isis_circuit *circuit)
+{
+ struct ldp_sync_info *ldp_sync_info;
+
+ ldp_sync_info = circuit->ldp_sync_info;
+
+ /* received sync-complete from LDP:
+ * set state to up
+ * stop timer
+ * restore interface cost to original value
+ */
+ if (ldp_sync_info && ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED) {
+ if (ldp_sync_info->state == LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP)
+ ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_UP;
+
+ EVENT_OFF(ldp_sync_info->t_holddown);
+
+ isis_ldp_sync_set_if_metric(circuit, true);
+ }
+}
+
+void isis_ldp_sync_ldp_fail(struct isis_circuit *circuit)
+{
+ struct ldp_sync_info *ldp_sync_info;
+
+ ldp_sync_info = circuit->ldp_sync_info;
+
+ /* LDP client close detected:
+ * stop holddown timer
+ * set cost of interface to LSInfinity so traffic will use different
+ * interface until LDP restarts and has learned all labels from peer
+ */
+ if (ldp_sync_info &&
+ ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED &&
+ ldp_sync_info->state != LDP_IGP_SYNC_STATE_NOT_REQUIRED) {
+ EVENT_OFF(ldp_sync_info->t_holddown);
+ ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP;
+ isis_ldp_sync_set_if_metric(circuit, true);
+ }
+}
+
+static int isis_ldp_sync_adj_state_change(struct isis_adjacency *adj)
+{
+ struct isis_circuit *circuit = adj->circuit;
+ struct ldp_sync_info *ldp_sync_info = circuit->ldp_sync_info;
+ struct isis_area *area = circuit->area;
+
+ if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)
+ || circuit->interface->vrf->vrf_id != VRF_DEFAULT
+ || if_is_loopback(circuit->interface))
+ return 0;
+
+ if (ldp_sync_info->enabled != LDP_IGP_SYNC_ENABLED)
+ return 0;
+
+ if (adj->adj_state == ISIS_ADJ_UP) {
+ if (circuit->circ_type == CIRCUIT_T_P2P ||
+ if_is_pointopoint(circuit->interface)) {
+ /* If LDP-SYNC is configure on interface then start */
+ ldp_sync_info->state =
+ LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP;
+ isis_ldp_sync_if_start(circuit, true);
+ } else {
+ /* non ptop link so don't run ldp-sync */
+ ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED;
+ isis_ldp_sync_set_if_metric(circuit, true);
+ }
+ } else {
+ /* If LDP-SYNC is configure on this interface then stop it */
+ if (circuit->circ_type == CIRCUIT_T_P2P ||
+ if_is_pointopoint(circuit->interface))
+ ldp_sync_info->state =
+ LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP;
+ else
+ ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED;
+
+ ils_debug("%s: down on if %s", __func__,
+ circuit->interface->name);
+ ldp_sync_if_down(circuit->ldp_sync_info);
+ }
+
+ return 0;
+}
+
+bool isis_ldp_sync_if_metric_config(struct isis_circuit *circuit, int level,
+ int metric)
+{
+ struct ldp_sync_info *ldp_sync_info = circuit->ldp_sync_info;
+ struct isis_area *area = circuit->area;
+
+ /* configured interface metric has been changed:
+ * if LDP-IGP Sync is running and metric has been set to LSInfinity
+ * change saved value so when ldp-sync completes proper metric is
+ * restored
+ */
+ if (area && CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)
+ && ldp_sync_info != NULL) {
+
+ if (CHECK_FLAG(ldp_sync_info->flags,
+ LDP_SYNC_FLAG_SET_METRIC)) {
+ ldp_sync_info->metric[level-1] = metric;
+ ldp_sync_info->metric[level-1] = metric;
+ return false;
+ }
+ }
+ return true;
+}
+
+void isis_ldp_sync_set_if_metric(struct isis_circuit *circuit, bool run_regen)
+{
+ struct ldp_sync_info *ldp_sync_info;
+
+ /* set interface metric:
+ * if LDP-IGP Sync is starting set metric so interface
+ * is used only as last resort
+ * else restore metric to original value
+ */
+ if (circuit->ldp_sync_info == NULL || circuit->area == NULL)
+ return;
+
+ ldp_sync_info = circuit->ldp_sync_info;
+ if (ldp_sync_if_is_enabled(ldp_sync_info)) {
+ /* if metric already set to LSInfinity just return */
+ if (CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_SET_METRIC))
+ return;
+
+ SET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_SET_METRIC);
+ if (circuit->is_type & IS_LEVEL_1) {
+ if (circuit->area->newmetric) {
+ ldp_sync_info->metric[0] =
+ circuit->te_metric[0];
+ circuit->te_metric[0] =
+ ISIS_WIDE_METRIC_INFINITY;
+ } else {
+ ldp_sync_info->metric[0] = circuit->metric[0];
+ circuit->metric[0] =
+ ISIS_NARROW_METRIC_INFINITY;
+ }
+ }
+ if (circuit->is_type & IS_LEVEL_2) {
+ if (circuit->area->newmetric) {
+ ldp_sync_info->metric[1] =
+ circuit->te_metric[1];
+ circuit->te_metric[1] =
+ ISIS_WIDE_METRIC_INFINITY;
+ } else {
+ ldp_sync_info->metric[1] = circuit->metric[1];
+ circuit->metric[1] =
+ ISIS_NARROW_METRIC_INFINITY;
+ }
+ }
+ } else {
+ /* if metric already restored just return */
+ if (!CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_SET_METRIC))
+ return;
+
+ UNSET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_SET_METRIC);
+ if (circuit->is_type & IS_LEVEL_1) {
+ circuit->te_metric[0] = ldp_sync_info->metric[0];
+ circuit->metric[0] = ldp_sync_info->metric[0];
+ }
+ if (circuit->is_type & IS_LEVEL_2) {
+ circuit->te_metric[1] = ldp_sync_info->metric[1];
+ circuit->metric[1] = ldp_sync_info->metric[1];
+ }
+ }
+
+ if (run_regen)
+ lsp_regenerate_schedule(circuit->area, circuit->is_type, 0);
+}
+
+
+/*
+ * LDP-SYNC holddown timer routines
+ */
+static void isis_ldp_sync_holddown_timer(struct event *thread)
+{
+ struct isis_circuit *circuit;
+ struct ldp_sync_info *ldp_sync_info;
+
+ /* holddown timer expired:
+ * didn't receive msg from LDP indicating sync-complete
+ * restore interface cost to original value
+ */
+ circuit = EVENT_ARG(thread);
+ if (circuit->ldp_sync_info == NULL)
+ return;
+
+ ldp_sync_info = circuit->ldp_sync_info;
+
+ ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_UP;
+ ldp_sync_info->t_holddown = NULL;
+
+ ils_debug("%s: holddown timer expired for %s state:sync achieved",
+ __func__, circuit->interface->name);
+
+ isis_ldp_sync_set_if_metric(circuit, true);
+}
+
+void isis_ldp_sync_holddown_timer_add(struct isis_circuit *circuit)
+{
+ struct ldp_sync_info *ldp_sync_info;
+
+ ldp_sync_info = circuit->ldp_sync_info;
+
+ /* Start holddown timer:
+ * this timer is used to keep interface cost at LSInfinity
+ * once expires returns cost to original value
+ * if timer is already running or holddown time is off just return
+ */
+ if (ldp_sync_info->t_holddown ||
+ ldp_sync_info->holddown == LDP_IGP_SYNC_HOLDDOWN_DEFAULT)
+ return;
+
+ ils_debug("%s: start holddown timer for %s time %d", __func__,
+ circuit->interface->name, ldp_sync_info->holddown);
+
+ event_add_timer(master, isis_ldp_sync_holddown_timer, circuit,
+ ldp_sync_info->holddown, &ldp_sync_info->t_holddown);
+}
+
+/*
+ * LDP-SYNC handle client close routine
+ */
+void isis_ldp_sync_handle_client_close(struct zapi_client_close_info *info)
+{
+ struct isis_area *area;
+ struct listnode *anode, *cnode;
+ struct isis_circuit *circuit;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ /* if isis is not enabled ignore */
+ if (!isis)
+ return;
+
+ /* Check if the LDP main client session closed */
+ if (info->proto != ZEBRA_ROUTE_LDP || info->session_id == 0)
+ return;
+
+ /* Handle the zebra notification that the LDP client session closed.
+ * set cost to LSInfinity
+ * send request to LDP for LDP-SYNC state for each interface
+ */
+ zlog_err("%s: LDP down", __func__);
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) {
+ if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE))
+ continue;
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit))
+ isis_ldp_sync_ldp_fail(circuit);
+ }
+}
+
+/*
+ * LDP-SYNC routes used by set commands.
+ */
+
+void isis_area_ldp_sync_enable(struct isis_area *area)
+{
+ struct isis_circuit *circuit;
+ struct listnode *node;
+
+ if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) {
+ SET_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE);
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit))
+ isis_if_ldp_sync_enable(circuit);
+ }
+}
+
+void isis_area_ldp_sync_disable(struct isis_area *area)
+{
+ struct isis_circuit *circuit;
+ struct listnode *node;
+
+ if (CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) {
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit))
+ isis_if_ldp_sync_disable(circuit);
+
+ UNSET_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE);
+
+ UNSET_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN);
+ area->ldp_sync_cmd.holddown = LDP_IGP_SYNC_HOLDDOWN_DEFAULT;
+ }
+}
+
+void isis_area_ldp_sync_set_holddown(struct isis_area *area, uint16_t holddown)
+{
+ struct isis_circuit *circuit;
+ struct listnode *node;
+
+ if (holddown == LDP_IGP_SYNC_HOLDDOWN_DEFAULT)
+ UNSET_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN);
+ else
+ SET_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN);
+
+ area->ldp_sync_cmd.holddown = holddown;
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit))
+ isis_if_set_ldp_sync_holddown(circuit);
+}
+
+void isis_if_ldp_sync_enable(struct isis_circuit *circuit)
+{
+ struct ldp_sync_info *ldp_sync_info = circuit->ldp_sync_info;
+ struct isis_area *area = circuit->area;
+
+ /* called when setting LDP-SYNC at the global level:
+ * specified on interface overrides global config
+ * if ptop link send msg to LDP indicating ldp-sync enabled
+ */
+ if (if_is_loopback(circuit->interface))
+ return;
+
+ if (circuit->interface->vrf->vrf_id != VRF_DEFAULT)
+ return;
+
+ ils_debug("%s: enable if %s", __func__, circuit->interface->name);
+
+ if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE))
+ return;
+
+ /* config on interface, overrides global config. */
+ if (CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_IF_CONFIG))
+ if (ldp_sync_info->enabled != LDP_IGP_SYNC_ENABLED)
+ return;
+
+ if (!CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN))
+ ldp_sync_info->holddown = area->ldp_sync_cmd.holddown;
+
+ if (circuit->circ_type == CIRCUIT_T_P2P
+ || if_is_pointopoint(circuit->interface)) {
+ ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP;
+ isis_ldp_sync_state_req_msg(circuit);
+ } else {
+ ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED;
+ ils_debug("%s: Sync only runs on P2P links %s", __func__,
+ circuit->interface->name);
+ }
+}
+
+void isis_if_ldp_sync_disable(struct isis_circuit *circuit)
+{
+ struct ldp_sync_info *ldp_sync_info = circuit->ldp_sync_info;
+ struct isis_area *area = circuit->area;
+
+ /* Stop LDP-SYNC on this interface:
+ * if holddown timer is running stop it
+ * delete ldp instance on interface
+ * restore metric
+ */
+ if (if_is_loopback(circuit->interface))
+ return;
+
+ ils_debug("%s: remove if %s", __func__, circuit->interface->name);
+
+ if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE))
+ return;
+
+ EVENT_OFF(ldp_sync_info->t_holddown);
+ ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED;
+ isis_ldp_sync_set_if_metric(circuit, true);
+}
+
+void isis_if_set_ldp_sync_holddown(struct isis_circuit *circuit)
+{
+ struct ldp_sync_info *ldp_sync_info = circuit->ldp_sync_info;
+ struct isis_area *area = circuit->area;
+
+ /* called when setting LDP-SYNC at the global level:
+ * specified on interface overrides global config.
+ */
+ if (if_is_loopback(circuit->interface))
+ return;
+
+ /* config on interface, overrides global config. */
+ if (CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN))
+ return;
+ if (CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN))
+ ldp_sync_info->holddown = area->ldp_sync_cmd.holddown;
+ else
+ ldp_sync_info->holddown = LDP_IGP_SYNC_HOLDDOWN_DEFAULT;
+}
+
+/*
+ * LDP-SYNC routines used by show commands.
+ */
+
+static void isis_circuit_ldp_sync_print_vty(struct isis_circuit *circuit,
+ struct vty *vty)
+{
+ struct ldp_sync_info *ldp_sync_info;
+ const char *ldp_state;
+
+ if (circuit->ldp_sync_info == NULL ||
+ if_is_loopback(circuit->interface))
+ return;
+
+ ldp_sync_info = circuit->ldp_sync_info;
+ vty_out(vty, "%-16s\n", circuit->interface->name);
+ if (circuit->state == C_STATE_CONF) {
+ vty_out(vty, " Interface down\n");
+ return;
+ }
+
+ vty_out(vty, " LDP-IGP Synchronization enabled: %s\n",
+ ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED
+ ? "yes"
+ : "no");
+ vty_out(vty, " holddown timer in seconds: %u\n",
+ ldp_sync_info->holddown);
+
+ switch (ldp_sync_info->state) {
+ case LDP_IGP_SYNC_STATE_REQUIRED_UP:
+ vty_out(vty, " State: Sync achieved\n");
+ break;
+ case LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP:
+ if (ldp_sync_info->t_holddown != NULL) {
+ struct timeval remain =
+ event_timer_remain(ldp_sync_info->t_holddown);
+ vty_out(vty,
+ " Holddown timer is running %lld.%03lld remaining\n",
+ (long long)remain.tv_sec,
+ (long long)remain.tv_usec/1000);
+
+ vty_out(vty, " State: Holding down until Sync\n");
+ } else
+ vty_out(vty, " State: Sync not achieved\n");
+ break;
+ case LDP_IGP_SYNC_STATE_NOT_REQUIRED:
+ default:
+ if ((circuit->circ_type != CIRCUIT_T_P2P &&
+ !if_is_pointopoint(circuit->interface)) &&
+ circuit->circ_type != CIRCUIT_T_UNKNOWN)
+ ldp_state = "Sync not required: non-p2p link";
+ else
+ ldp_state = "Sync not required";
+ vty_out(vty, " State: %s\n", ldp_state);
+ break;
+ }
+}
+
+DEFUN (show_isis_mpls_ldp_interface,
+ show_isis_mpls_ldp_interface_cmd,
+ "show " PROTO_NAME " mpls ldp-sync [interface <INTERFACE|all>]",
+ SHOW_STR
+ PROTO_HELP
+ MPLS_STR
+ "LDP-IGP Sync information\n"
+ "Interface information\n"
+ "Interface name\n"
+ "All interfaces\n")
+{
+ char *ifname = NULL;
+ int idx_intf = 0;
+ struct listnode *anode, *cnode;
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+ bool found = false;
+
+ if (!isis) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+
+ if (argv_find(argv, argc, "INTERFACE", &idx_intf))
+ ifname = argv[idx_intf]->arg;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) {
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit))
+ if (!ifname)
+ isis_circuit_ldp_sync_print_vty(circuit, vty);
+ else if (strcmp(circuit->interface->name, ifname)
+ == 0) {
+ isis_circuit_ldp_sync_print_vty(circuit, vty);
+ found = true;
+ }
+ }
+
+ if (found == false && ifname)
+ vty_out(vty, "%-16s\n ISIS not enabled\n", ifname);
+
+ return CMD_SUCCESS;
+}
+
+void isis_ldp_sync_init(void)
+{
+
+ /* "show ip isis mpls ldp interface" commands. */
+ install_element(VIEW_NODE, &show_isis_mpls_ldp_interface_cmd);
+
+ /* register for adjacency state changes */
+ hook_register(isis_adj_state_change_hook,
+ isis_ldp_sync_adj_state_change);
+}
diff --git a/isisd/isis_ldp_sync.h b/isisd/isis_ldp_sync.h
new file mode 100644
index 0000000..d593ed3
--- /dev/null
+++ b/isisd/isis_ldp_sync.h
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * isis_ldp_sync.h: ISIS LDP-IGP Sync handling routines
+ * Copyright (C) 2020 Volta Networks, Inc.
+ */
+
+#ifndef _ZEBRA_ISIS_LDP_SYNC_H
+#define _ZEBRA_ISIS_LDP_SYNC_H
+
+#include "zclient.h"
+
+/* Macro to log debug message */
+#define ils_debug(...) \
+ do { \
+ if (IS_DEBUG_LDP_SYNC) \
+ zlog_debug(__VA_ARGS__); \
+ } while (0)
+
+extern void isis_area_ldp_sync_enable(struct isis_area *area);
+extern void isis_area_ldp_sync_disable(struct isis_area *area);
+extern void isis_area_ldp_sync_set_holddown(struct isis_area *area,
+ uint16_t holddown);
+extern void isis_if_ldp_sync_enable(struct isis_circuit *circuit);
+extern void isis_if_ldp_sync_disable(struct isis_circuit *circuit);
+extern void isis_if_set_ldp_sync_holddown(struct isis_circuit *circuit);
+extern void isis_ldp_sync_if_start(struct isis_circuit *circuit,
+ bool send_state_req);
+extern void isis_ldp_sync_if_complete(struct isis_circuit *circuit);
+extern void isis_ldp_sync_holddown_timer_add(struct isis_circuit *circuit);
+extern void
+isis_ldp_sync_handle_client_close(struct zapi_client_close_info *info);
+extern void isis_ldp_sync_ldp_fail(struct isis_circuit *circuit);
+extern int isis_ldp_sync_state_update(struct ldp_igp_sync_if_state state);
+extern int isis_ldp_sync_announce_update(struct ldp_igp_sync_announce announce);
+extern void isis_ldp_sync_state_req_msg(struct isis_circuit *circuit);
+extern void isis_ldp_sync_set_if_metric(struct isis_circuit *circuit,
+ bool run_regen);
+extern bool isis_ldp_sync_if_metric_config(struct isis_circuit *circuit,
+ int level, int metric);
+extern void isis_ldp_sync_init(void);
+#endif /* _ZEBRA_ISIS_LDP_SYNC_H */
diff --git a/isisd/isis_lfa.c b/isisd/isis_lfa.c
new file mode 100644
index 0000000..6f21f4c
--- /dev/null
+++ b/isisd/isis_lfa.c
@@ -0,0 +1,2366 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 NetDEF, Inc.
+ * Renato Westphal
+ */
+
+#include <zebra.h>
+
+#include "linklist.h"
+#include "log.h"
+#include "memory.h"
+#include "vrf.h"
+#include "table.h"
+#include "srcdest_table.h"
+#include "plist.h"
+#include "zclient.h"
+
+#include "isis_common.h"
+#include "isisd.h"
+#include "isis_misc.h"
+#include "isis_adjacency.h"
+#include "isis_circuit.h"
+#include "isis_lsp.h"
+#include "isis_spf.h"
+#include "isis_route.h"
+#include "isis_mt.h"
+#include "isis_tlvs.h"
+#include "isis_spf_private.h"
+#include "isis_zebra.h"
+#include "isis_errors.h"
+
+DEFINE_MTYPE_STATIC(ISISD, ISIS_SPF_NODE, "ISIS SPF Node");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_LFA_TIEBREAKER, "ISIS LFA Tiebreaker");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_LFA_EXCL_IFACE, "ISIS LFA Excluded Interface");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_RLFA, "ISIS Remote LFA");
+DEFINE_MTYPE(ISISD, ISIS_NEXTHOP_LABELS, "ISIS nexthop MPLS labels");
+
+static inline int isis_spf_node_compare(const struct isis_spf_node *a,
+ const struct isis_spf_node *b)
+{
+ return memcmp(a->sysid, b->sysid, sizeof(a->sysid));
+}
+RB_GENERATE(isis_spf_nodes, isis_spf_node, entry, isis_spf_node_compare)
+
+/**
+ * Initialize list of SPF nodes.
+ *
+ * @param nodes List of SPF nodes
+ */
+void isis_spf_node_list_init(struct isis_spf_nodes *nodes)
+{
+ RB_INIT(isis_spf_nodes, nodes);
+}
+
+/**
+ * Clear list of SPF nodes, releasing all allocated memory.
+ *
+ * @param nodes List of SPF nodes
+ */
+void isis_spf_node_list_clear(struct isis_spf_nodes *nodes)
+{
+ while (!RB_EMPTY(isis_spf_nodes, nodes)) {
+ struct isis_spf_node *node = RB_ROOT(isis_spf_nodes, nodes);
+
+ if (node->adjacencies)
+ list_delete(&node->adjacencies);
+ if (node->lfa.spftree)
+ isis_spftree_del(node->lfa.spftree);
+ if (node->lfa.spftree_reverse)
+ isis_spftree_del(node->lfa.spftree_reverse);
+ isis_spf_node_list_clear(&node->lfa.p_space);
+ RB_REMOVE(isis_spf_nodes, nodes, node);
+ XFREE(MTYPE_ISIS_SPF_NODE, node);
+ }
+}
+
+/**
+ * Add new node to list of SPF nodes.
+ *
+ * @param nodes List of SPF nodes
+ * @param sysid Node System ID
+ *
+ * @return Pointer to new IS-IS SPF node structure.
+ */
+struct isis_spf_node *isis_spf_node_new(struct isis_spf_nodes *nodes,
+ const uint8_t *sysid)
+{
+ struct isis_spf_node *node;
+
+ node = XCALLOC(MTYPE_ISIS_SPF_NODE, sizeof(*node));
+ memcpy(node->sysid, sysid, sizeof(node->sysid));
+ node->adjacencies = list_new();
+ isis_spf_node_list_init(&node->lfa.p_space);
+ RB_INSERT(isis_spf_nodes, nodes, node);
+
+ return node;
+}
+
+/**
+ * Lookup SPF node by its System ID on the given list.
+ *
+ * @param nodes List of SPF nodes
+ * @param sysid Node System ID
+ *
+ * @return Pointer to SPF node if found, NULL otherwise
+ */
+struct isis_spf_node *isis_spf_node_find(const struct isis_spf_nodes *nodes,
+ const uint8_t *sysid)
+{
+ struct isis_spf_node node = {};
+
+ memcpy(node.sysid, sysid, sizeof(node.sysid));
+ return RB_FIND(isis_spf_nodes, nodes, &node);
+}
+
+/**
+ * LFA tiebreaker RB-tree comparison function.
+ *
+ * @param a First LFA tiebreaker
+ * @param b Second LFA tiebreaker
+ *
+ * @return -1 (a < b), 0 (a == b) or +1 (a > b)
+ */
+int lfa_tiebreaker_cmp(const struct lfa_tiebreaker *a,
+ const struct lfa_tiebreaker *b)
+{
+ if (a->index < b->index)
+ return -1;
+ if (a->index > b->index)
+ return 1;
+
+ return a->type - b->type;
+}
+
+/**
+ * Initialize list of LFA tie-breakers.
+ *
+ * @param area IS-IS area
+ * @param level IS-IS level
+ */
+void isis_lfa_tiebreakers_init(struct isis_area *area, int level)
+{
+ lfa_tiebreaker_tree_init(&area->lfa_tiebreakers[level - 1]);
+}
+
+/**
+ * Clear list of LFA tie-breakers, releasing all allocated memory.
+ *
+ * @param area IS-IS area
+ * @param level IS-IS level
+ */
+void isis_lfa_tiebreakers_clear(struct isis_area *area, int level)
+{
+ while (lfa_tiebreaker_tree_count(&area->lfa_tiebreakers[level - 1])
+ > 0) {
+ struct lfa_tiebreaker *tie_b;
+
+ tie_b = lfa_tiebreaker_tree_first(
+ &area->lfa_tiebreakers[level - 1]);
+ isis_lfa_tiebreaker_delete(area, level, tie_b);
+ }
+}
+
+/**
+ * Add new LFA tie-breaker to list of LFA tie-breakers.
+ *
+ * @param area IS-IS area
+ * @param level IS-IS level
+ * @param index LFA tie-breaker index
+ * @param type LFA tie-breaker type
+ *
+ * @return Pointer to new LFA tie-breaker structure.
+ */
+struct lfa_tiebreaker *isis_lfa_tiebreaker_add(struct isis_area *area,
+ int level, uint8_t index,
+ enum lfa_tiebreaker_type type)
+{
+ struct lfa_tiebreaker *tie_b;
+
+ tie_b = XCALLOC(MTYPE_ISIS_LFA_TIEBREAKER, sizeof(*tie_b));
+ tie_b->index = index;
+ tie_b->type = type;
+ tie_b->area = area;
+ lfa_tiebreaker_tree_add(&area->lfa_tiebreakers[level - 1], tie_b);
+
+ return tie_b;
+}
+
+/**
+ * Remove LFA tie-breaker from list of LFA tie-breakers.
+ *
+ * @param area IS-IS area
+ * @param level IS-IS level
+ * @param tie_b Pointer to LFA tie-breaker structure
+ */
+void isis_lfa_tiebreaker_delete(struct isis_area *area, int level,
+ struct lfa_tiebreaker *tie_b)
+{
+ lfa_tiebreaker_tree_del(&area->lfa_tiebreakers[level - 1], tie_b);
+ XFREE(MTYPE_ISIS_LFA_TIEBREAKER, tie_b);
+}
+
+static bool lfa_excl_interface_hash_cmp(const void *value1, const void *value2)
+{
+ return strmatch(value1, value2);
+}
+
+static unsigned int lfa_excl_interface_hash_make(const void *value)
+{
+ return string_hash_make(value);
+}
+
+static void *lfa_excl_interface_hash_alloc(void *p)
+{
+ return XSTRDUP(MTYPE_ISIS_LFA_EXCL_IFACE, p);
+}
+
+static void lfa_excl_interface_hash_free(void *arg)
+{
+ XFREE(MTYPE_ISIS_LFA_EXCL_IFACE, arg);
+}
+
+/**
+ * Initialize hash table of LFA excluded interfaces.
+ *
+ * @param circuit IS-IS interface
+ * @param level IS-IS level
+ */
+void isis_lfa_excluded_ifaces_init(struct isis_circuit *circuit, int level)
+{
+ circuit->lfa_excluded_ifaces[level - 1] = hash_create(
+ lfa_excl_interface_hash_make, lfa_excl_interface_hash_cmp,
+ "LFA Excluded Interfaces");
+}
+
+/**
+ * Clear hash table of LFA excluded interfaces, releasing all allocated memory.
+ *
+ * @param nodes List of SPF nodes
+ */
+void isis_lfa_excluded_ifaces_clear(struct isis_circuit *circuit, int level)
+{
+ hash_clean(circuit->lfa_excluded_ifaces[level - 1],
+ lfa_excl_interface_hash_free);
+}
+
+/**
+ * Add new interface to hash table of excluded interfaces.
+ *
+ * @param circuit IS-IS interface
+ * @param level IS-IS level
+ * @param ifname Excluded interface name
+ */
+void isis_lfa_excluded_iface_add(struct isis_circuit *circuit, int level,
+ const char *ifname)
+{
+ (void)hash_get(circuit->lfa_excluded_ifaces[level - 1], (char *)ifname,
+ lfa_excl_interface_hash_alloc);
+}
+
+/**
+ * Remove interface from hash table of excluded interfaces.
+ *
+ * @param circuit IS-IS interface
+ * @param level IS-IS level
+ * @param ifname Excluded interface name
+ */
+void isis_lfa_excluded_iface_delete(struct isis_circuit *circuit, int level,
+ const char *ifname)
+{
+ char *found;
+
+ found = hash_lookup(circuit->lfa_excluded_ifaces[level - 1],
+ (char *)ifname);
+ if (found) {
+ hash_release(circuit->lfa_excluded_ifaces[level - 1], found);
+ lfa_excl_interface_hash_free(found);
+ }
+}
+
+/**
+ * Lookup excluded interface.
+ *
+ * @param circuit IS-IS interface
+ * @param level IS-IS level
+ * @param ifname Excluded interface name
+ */
+bool isis_lfa_excluded_iface_check(struct isis_circuit *circuit, int level,
+ const char *ifname)
+{
+ return hash_lookup(circuit->lfa_excluded_ifaces[level - 1],
+ (char *)ifname);
+}
+
+/**
+ * Check if a given IS-IS adjacency needs to be excised when computing the SPF
+ * post-convergence tree.
+ *
+ * @param spftree IS-IS SPF tree
+ * @param id Adjacency System ID (or LAN ID of the designated router
+ * for broadcast interfaces)
+ *
+ * @return true if the adjacency needs to be excised, false
+ * otherwise
+ */
+bool isis_lfa_excise_adj_check(const struct isis_spftree *spftree,
+ const uint8_t *id)
+{
+ const struct lfa_protected_resource *resource;
+
+ if (spftree->type != SPF_TYPE_RLFA && spftree->type != SPF_TYPE_TI_LFA)
+ return false;
+
+ /*
+ * Adjacencies formed over the failed interface should be excised both
+ * when using link and node protection.
+ */
+ resource = &spftree->lfa.protected_resource;
+ if (!memcmp(resource->adjacency, id, ISIS_SYS_ID_LEN + 1))
+ return true;
+
+ return false;
+}
+
+/**
+ * Check if a given IS-IS node needs to be excised when computing the SPF
+ * post-convergence tree.
+ *
+ * @param spftree IS-IS SPF tree
+ * @param id Node System ID
+ *
+ * @return true if the node needs to be excised, false otherwise
+ */
+bool isis_lfa_excise_node_check(const struct isis_spftree *spftree,
+ const uint8_t *id)
+{
+ const struct lfa_protected_resource *resource;
+
+ if (spftree->type != SPF_TYPE_TI_LFA)
+ return false;
+
+ /*
+ * When using node protection, nodes reachable over the failed interface
+ * must be excised.
+ */
+ resource = &spftree->lfa.protected_resource;
+ if (resource->type == LFA_LINK_PROTECTION)
+ return false;
+
+ if (isis_spf_node_find(&resource->nodes, id))
+ return true;
+
+ return false;
+}
+
+struct tilfa_find_pnode_prefix_sid_args {
+ uint32_t sid_index;
+ int algorithm;
+};
+
+static int tilfa_find_pnode_prefix_sid_cb(const struct prefix *prefix,
+ uint32_t metric, bool external,
+ struct isis_subtlvs *subtlvs,
+ void *arg)
+{
+ struct tilfa_find_pnode_prefix_sid_args *args = arg;
+ struct isis_prefix_sid *psid;
+
+ if (!subtlvs || subtlvs->prefix_sids.count == 0)
+ return LSP_ITER_CONTINUE;
+
+ for (psid = (struct isis_prefix_sid *)subtlvs->prefix_sids.head; psid;
+ psid = psid->next) {
+ /* Require the node flag to be set. */
+ if (!CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_NODE))
+ continue;
+ if (psid->algorithm != args->algorithm)
+ continue;
+ args->sid_index = psid->value;
+ return LSP_ITER_STOP;
+ }
+ return LSP_ITER_CONTINUE;
+}
+
+/* Find Prefix-SID associated to a System ID. */
+static uint32_t tilfa_find_pnode_prefix_sid(struct isis_spftree *spftree,
+ const uint8_t *sysid)
+{
+ struct isis_lsp *lsp;
+ struct tilfa_find_pnode_prefix_sid_args args;
+
+ lsp = isis_root_system_lsp(spftree->lspdb, sysid);
+ if (!lsp)
+ return UINT32_MAX;
+
+ args.algorithm = spftree->algorithm;
+
+ args.sid_index = UINT32_MAX;
+ isis_lsp_iterate_ip_reach(lsp, spftree->family, spftree->mtid,
+ tilfa_find_pnode_prefix_sid_cb, &args);
+
+ return args.sid_index;
+}
+
+struct tilfa_find_qnode_adj_sid_args {
+ const uint8_t *qnode_sysid;
+ mpls_label_t label;
+};
+
+static int tilfa_find_qnode_adj_sid_cb(const uint8_t *id, uint32_t metric,
+ bool oldmetric,
+ struct isis_ext_subtlvs *subtlvs,
+ void *arg)
+{
+ struct tilfa_find_qnode_adj_sid_args *args = arg;
+ struct isis_adj_sid *adj_sid;
+
+ if (memcmp(id, args->qnode_sysid, ISIS_SYS_ID_LEN))
+ return LSP_ITER_CONTINUE;
+ if (!subtlvs || subtlvs->adj_sid.count == 0)
+ return LSP_ITER_CONTINUE;
+
+ adj_sid = (struct isis_adj_sid *)subtlvs->adj_sid.head;
+ args->label = adj_sid->sid;
+
+ return LSP_ITER_STOP;
+}
+
+/* Find Adj-SID associated to a pair of System IDs. */
+static mpls_label_t tilfa_find_qnode_adj_sid(struct isis_spftree *spftree,
+ const uint8_t *source_sysid,
+ const uint8_t *qnode_sysid)
+{
+ struct isis_lsp *lsp;
+ struct tilfa_find_qnode_adj_sid_args args;
+
+ lsp = isis_root_system_lsp(spftree->lspdb, source_sysid);
+ if (!lsp)
+ return MPLS_INVALID_LABEL;
+
+ args.qnode_sysid = qnode_sysid;
+ args.label = MPLS_INVALID_LABEL;
+ isis_lsp_iterate_is_reach(lsp, spftree->mtid,
+ tilfa_find_qnode_adj_sid_cb, &args);
+
+ return args.label;
+}
+
+/*
+ * Compute the MPLS label stack associated to a TI-LFA repair list. This
+ * needs to be computed separately for each adjacency since different
+ * neighbors can have different SRGBs.
+ */
+static struct mpls_label_stack *
+tilfa_compute_label_stack(struct lspdb_head *lspdb,
+ const struct isis_spf_adj *sadj,
+ const struct list *repair_list)
+{
+ struct mpls_label_stack *label_stack;
+ struct isis_tilfa_sid *sid;
+ struct listnode *node;
+ size_t i = 0;
+
+ /* Allocate label stack. */
+ label_stack = XCALLOC(MTYPE_ISIS_NEXTHOP_LABELS,
+ sizeof(struct mpls_label_stack)
+ + listcount(repair_list)
+ * sizeof(mpls_label_t));
+ label_stack->num_labels = listcount(repair_list);
+
+ for (ALL_LIST_ELEMENTS_RO(repair_list, node, sid)) {
+ const uint8_t *target_node;
+ struct isis_sr_block *srgb;
+ mpls_label_t label;
+
+ switch (sid->type) {
+ case TILFA_SID_PREFIX:
+ if (sid->value.index.remote)
+ target_node = sid->value.index.remote_sysid;
+ else
+ target_node = sadj->id;
+ srgb = isis_sr_find_srgb(lspdb, target_node);
+ if (!srgb) {
+ zlog_warn("%s: SRGB not found for node %s",
+ __func__,
+ print_sys_hostname(target_node));
+ goto error;
+ }
+
+ /* Check if the SID index falls inside the SRGB. */
+ if (sid->value.index.value >= srgb->range_size) {
+ flog_warn(
+ EC_ISIS_SID_OVERFLOW,
+ "%s: SID index %u falls outside remote SRGB range",
+ __func__, sid->value.index.value);
+ goto error;
+ }
+
+ /*
+ * Prefix-SID: map SID index to label value within the
+ * SRGB.
+ */
+ label = srgb->lower_bound + sid->value.index.value;
+ break;
+ case TILFA_SID_ADJ:
+ /* Adj-SID: absolute label value can be used directly */
+ label = sid->value.label;
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unknown TI-LFA SID type [%u]", __func__,
+ sid->type);
+ exit(1);
+ }
+ label_stack->label[i++] = label;
+ }
+
+ return label_stack;
+
+error:
+ XFREE(MTYPE_ISIS_NEXTHOP_LABELS, label_stack);
+ return NULL;
+}
+
+static int tilfa_repair_list_apply(struct isis_spftree *spftree,
+ struct isis_vertex *vertex_dest,
+ const struct isis_vertex *vertex_pnode,
+ const struct list *repair_list)
+{
+ struct isis_vertex_adj *vadj;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(vertex_dest->Adj_N, node, vadj)) {
+ struct isis_spf_adj *sadj = vadj->sadj;
+ struct mpls_label_stack *label_stack;
+
+ /*
+ * Don't try to apply the repair list if one was already applied
+ * before (can't have ECMP past the P-node).
+ */
+ if (vadj->label_stack)
+ continue;
+
+ if (!isis_vertex_adj_exists(spftree, vertex_pnode, sadj))
+ continue;
+
+ label_stack = tilfa_compute_label_stack(spftree->lspdb, sadj,
+ repair_list);
+ if (!label_stack) {
+ char buf[VID2STR_BUFFER];
+
+ vid2string(vertex_dest, buf, sizeof(buf));
+ zlog_warn(
+ "%s: %s %s adjacency %s: failed to compute label stack",
+ __func__, vtype2string(vertex_dest->type), buf,
+ print_sys_hostname(sadj->id));
+ return -1;
+ }
+
+ vadj->label_stack = label_stack;
+ }
+
+ return 0;
+}
+
+/*
+ * Check if a node belongs to the extended P-space corresponding to a given
+ * destination.
+ */
+static bool lfa_ext_p_space_check(const struct isis_spftree *spftree_pc,
+ const struct isis_vertex *vertex_dest,
+ const struct isis_vertex *vertex)
+{
+ struct isis_spftree *spftree_old = spftree_pc->lfa.old.spftree;
+ struct isis_vertex_adj *vadj;
+ struct listnode *node;
+
+ /* Check the local P-space first. */
+ if (isis_spf_node_find(&spftree_pc->lfa.p_space, vertex->N.id))
+ return true;
+
+ /*
+ * Check the P-space of the adjacent routers used to reach the
+ * destination.
+ */
+ for (ALL_LIST_ELEMENTS_RO(vertex_dest->Adj_N, node, vadj)) {
+ struct isis_spf_adj *sadj = vadj->sadj;
+ struct isis_spf_node *adj_node;
+
+ adj_node =
+ isis_spf_node_find(&spftree_old->adj_nodes, sadj->id);
+ if (!adj_node)
+ continue;
+
+ if (isis_spf_node_find(&adj_node->lfa.p_space, vertex->N.id))
+ return true;
+ }
+
+ return false;
+}
+
+/* Check if a node belongs to the Q-space. */
+static bool lfa_q_space_check(const struct isis_spftree *spftree_pc,
+ const struct isis_vertex *vertex)
+{
+ return isis_spf_node_find(&spftree_pc->lfa.q_space, vertex->N.id);
+}
+
+/* This is a recursive function. */
+static int tilfa_build_repair_list(struct isis_spftree *spftree_pc,
+ struct isis_vertex *vertex_dest,
+ const struct isis_vertex *vertex,
+ const struct isis_vertex *vertex_child,
+ struct isis_spf_nodes *used_pnodes,
+ struct list *repair_list)
+{
+ struct isis_vertex *pvertex;
+ struct listnode *node;
+ bool is_pnode, is_qnode;
+ char buf[VID2STR_BUFFER];
+ struct isis_tilfa_sid sid_dest = {}, sid_qnode = {}, sid_pnode = {};
+ uint32_t sid_index;
+ mpls_label_t label_qnode;
+
+ if (IS_DEBUG_LFA) {
+ vid2string(vertex, buf, sizeof(buf));
+ zlog_debug("ISIS-LFA: vertex %s %s", vtype2string(vertex->type),
+ buf);
+ }
+
+ /* Push original Prefix-SID label when necessary. */
+ if (VTYPE_IP(vertex->type) && vertex->N.ip.sr.present) {
+ pvertex = listnode_head(vertex->parents);
+ assert(pvertex);
+
+ sid_index = vertex->N.ip.sr.sid.value;
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: pushing Prefix-SID to %pFX (index %u)",
+ &vertex->N.ip.p.dest, sid_index);
+ sid_dest.type = TILFA_SID_PREFIX;
+ sid_dest.value.index.value = sid_index;
+ sid_dest.value.index.remote = true;
+ memcpy(sid_dest.value.index.remote_sysid, pvertex->N.id,
+ sizeof(sid_dest.value.index.remote_sysid));
+ listnode_add_head(repair_list, &sid_dest);
+ }
+
+ if (!vertex_child)
+ goto parents;
+ if (vertex->type != VTYPE_NONPSEUDO_IS
+ && vertex->type != VTYPE_NONPSEUDO_TE_IS)
+ goto parents;
+ if (!VTYPE_IS(vertex_child->type))
+ vertex_child = NULL;
+
+ /* Check if node is part of the extended P-space and/or Q-space. */
+ is_pnode = lfa_ext_p_space_check(spftree_pc, vertex_dest, vertex);
+ is_qnode = lfa_q_space_check(spftree_pc, vertex);
+
+ /* Push Adj-SID label when necessary. */
+ if ((!is_qnode
+ || spftree_pc->lfa.protected_resource.type == LFA_NODE_PROTECTION)
+ && vertex_child) {
+ /*
+ * If vertex is the penultimate hop router, then pushing an
+ * Adj-SID towards the final hop means that the No-PHP flag of
+ * the original Prefix-SID must be honored. We do that by
+ * removing the previously added Prefix-SID from the repair list
+ * when those conditions are met.
+ */
+ if (vertex->depth == (vertex_dest->depth - 2)
+ && VTYPE_IP(vertex_dest->type)
+ && vertex_dest->N.ip.sr.present
+ && !CHECK_FLAG(vertex_dest->N.ip.sr.sid.flags,
+ ISIS_PREFIX_SID_NO_PHP)) {
+ list_delete_all_node(repair_list);
+ }
+
+ label_qnode = tilfa_find_qnode_adj_sid(spftree_pc, vertex->N.id,
+ vertex_child->N.id);
+ if (label_qnode == MPLS_INVALID_LABEL) {
+ zlog_warn("ISIS-LFA: failed to find %s->%s Adj-SID",
+ print_sys_hostname(vertex->N.id),
+ print_sys_hostname(vertex_child->N.id));
+ return -1;
+ }
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: pushing %s->%s Adj-SID (label %u)",
+ print_sys_hostname(vertex->N.id),
+ print_sys_hostname(vertex_child->N.id),
+ label_qnode);
+ sid_qnode.type = TILFA_SID_ADJ;
+ sid_qnode.value.label = label_qnode;
+ listnode_add_head(repair_list, &sid_qnode);
+ }
+
+ /* Push Prefix-SID label when necessary. */
+ if (is_pnode) {
+ /* The same P-node can't be used more than once. */
+ if (isis_spf_node_find(used_pnodes, vertex->N.id)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: skipping already used P-node");
+ return 0;
+ }
+ isis_spf_node_new(used_pnodes, vertex->N.id);
+
+ if (!vertex_child) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: destination is within Ext-P-Space");
+ return 0;
+ }
+
+ sid_index =
+ tilfa_find_pnode_prefix_sid(spftree_pc, vertex->N.id);
+ if (sid_index == UINT32_MAX) {
+ zlog_warn(
+ "ISIS-LFA: failed to find Prefix-SID corresponding to PQ-node %s",
+ print_sys_hostname(vertex->N.id));
+ return -1;
+ }
+
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: pushing Node-SID to %s (index %u)",
+ print_sys_hostname(vertex->N.id), sid_index);
+ sid_pnode.type = TILFA_SID_PREFIX;
+ sid_pnode.value.index.value = sid_index;
+ listnode_add_head(repair_list, &sid_pnode);
+
+ /* Apply repair list. */
+ if (spftree_pc->area->srdb.config.msd
+ && listcount(repair_list)
+ > spftree_pc->area->srdb.config.msd) {
+ zlog_warn(
+ "ISIS-LFA: list of repair segments exceeds locally configured MSD (%u > %u)",
+ listcount(repair_list),
+ spftree_pc->area->srdb.config.msd);
+ return -1;
+ }
+ if (tilfa_repair_list_apply(spftree_pc, vertex_dest, vertex,
+ repair_list)
+ != 0)
+ return -1;
+ return 0;
+ }
+
+parents:
+ for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, pvertex)) {
+ struct list *repair_list_parent;
+ bool ecmp;
+ int ret;
+
+ ecmp = (listcount(vertex->parents) > 1) ? true : false;
+ repair_list_parent = ecmp ? list_dup(repair_list) : repair_list;
+ ret = tilfa_build_repair_list(spftree_pc, vertex_dest, pvertex,
+ vertex, used_pnodes,
+ repair_list_parent);
+ if (ecmp)
+ list_delete(&repair_list_parent);
+ if (ret != 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const char *lfa_protection_type2str(enum lfa_protection_type type)
+{
+ switch (type) {
+ case LFA_LINK_PROTECTION:
+ return "link protection";
+ case LFA_NODE_PROTECTION:
+ return "node protection";
+ default:
+ return "unknown protection type";
+ }
+}
+
+static const char *
+lfa_protected_resource2str(const struct lfa_protected_resource *resource)
+{
+ const uint8_t *fail_id;
+ static char buffer[128];
+
+ fail_id = resource->adjacency;
+ snprintf(buffer, sizeof(buffer), "%s.%u's failure (%s)",
+ print_sys_hostname(fail_id), LSP_PSEUDO_ID(fail_id),
+ lfa_protection_type2str(resource->type));
+
+ return buffer;
+}
+
+static bool
+spf_adj_check_is_affected(const struct isis_spf_adj *sadj,
+ const struct lfa_protected_resource *resource,
+ const uint8_t *root_sysid, bool reverse)
+{
+ if (!!CHECK_FLAG(sadj->flags, F_ISIS_SPF_ADJ_BROADCAST)
+ != !!LSP_PSEUDO_ID(resource->adjacency))
+ return false;
+
+ if (CHECK_FLAG(sadj->flags, F_ISIS_SPF_ADJ_BROADCAST)) {
+ if (!memcmp(sadj->lan.desig_is_id, resource->adjacency,
+ ISIS_SYS_ID_LEN + 1))
+ return true;
+ } else {
+ if (!reverse
+ && !memcmp(sadj->id, resource->adjacency, ISIS_SYS_ID_LEN))
+ return true;
+ if (reverse && !memcmp(sadj->id, root_sysid, ISIS_SYS_ID_LEN))
+ return true;
+ }
+
+ return false;
+}
+
+/* Check if the given vertex is affected by a given local failure. */
+static bool
+spf_vertex_check_is_affected(const struct isis_vertex *vertex,
+ const uint8_t *root_sysid,
+ const struct lfa_protected_resource *resource)
+{
+ struct isis_vertex_adj *vadj;
+ struct listnode *node;
+ size_t affected_nhs = 0;
+
+ /* Local routes don't need protection. */
+ if (VTYPE_IP(vertex->type) && vertex->depth == 1)
+ return false;
+
+ for (ALL_LIST_ELEMENTS_RO(vertex->Adj_N, node, vadj)) {
+ struct isis_spf_adj *sadj = vadj->sadj;
+
+ if (spf_adj_check_is_affected(sadj, resource, root_sysid,
+ false))
+ affected_nhs++;
+ }
+
+ /*
+ * No need to compute backup paths for ECMP routes, except if all
+ * primary nexthops share the same broadcast interface.
+ */
+ if (listcount(vertex->Adj_N) == affected_nhs)
+ return true;
+
+ return false;
+}
+
+/* Check if a given RLFA/TI-LFA post-convergence SPF vertex needs protection. */
+static bool lfa_check_needs_protection(const struct isis_spftree *spftree_pc,
+ const struct isis_vertex *vertex)
+{
+ struct isis_vertex *vertex_old;
+
+ /* Only local adjacencies need TI-LFA Adj-SID protection. */
+ if (spftree_pc->type == SPF_TYPE_TI_LFA && VTYPE_IS(vertex->type)
+ && !isis_adj_find(spftree_pc->area, spftree_pc->level,
+ vertex->N.id))
+ return false;
+
+ vertex_old = isis_find_vertex(&spftree_pc->lfa.old.spftree->paths,
+ &vertex->N, vertex->type);
+ if (!vertex_old)
+ return false;
+
+ /* Skip vertex if it's already protected by local LFA. */
+ if (CHECK_FLAG(vertex_old->flags, F_ISIS_VERTEX_LFA_PROTECTED))
+ return false;
+
+ return spf_vertex_check_is_affected(
+ vertex_old, spftree_pc->sysid,
+ &spftree_pc->lfa.protected_resource);
+}
+
+/**
+ * Check if the given SPF vertex needs protection and, if so, compute and
+ * install the corresponding repair paths.
+ *
+ * @param spftree_pc The post-convergence SPF tree
+ * @param vertex IS-IS SPF vertex to check
+ *
+ * @return 0 if the vertex needs to be protected, -1 otherwise
+ */
+int isis_tilfa_check(struct isis_spftree *spftree_pc,
+ struct isis_vertex *vertex)
+{
+ struct isis_spf_nodes used_pnodes;
+ char buf[VID2STR_BUFFER];
+ struct list *repair_list;
+ int ret;
+
+ if (!spftree_pc->area->srdb.enabled)
+ return -1;
+
+ if (!lfa_check_needs_protection(spftree_pc, vertex)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: %s %s unaffected by %s",
+ vtype2string(vertex->type),
+ vid2string(vertex, buf, sizeof(buf)),
+ lfa_protected_resource2str(
+ &spftree_pc->lfa.protected_resource));
+
+ return -1;
+ }
+
+ /*
+ * Check if the route/adjacency was already covered by node protection.
+ */
+ if (VTYPE_IS(vertex->type)) {
+ struct isis_adjacency *adj;
+
+ adj = isis_adj_find(spftree_pc->area, spftree_pc->level,
+ vertex->N.id);
+ if (adj
+ && isis_sr_adj_sid_find(adj, spftree_pc->family,
+ ISIS_SR_LAN_BACKUP)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: %s %s already covered by node protection",
+ vtype2string(vertex->type),
+ vid2string(vertex, buf, sizeof(buf)));
+
+ return -1;
+ }
+ }
+ if (VTYPE_IP(vertex->type)) {
+ struct route_table *route_table;
+
+ route_table = spftree_pc->lfa.old.spftree->route_table_backup;
+ if (route_node_lookup(route_table, &vertex->N.ip.p.dest)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: %s %s already covered by node protection",
+ vtype2string(vertex->type),
+ vid2string(vertex, buf, sizeof(buf)));
+
+ return -1;
+ }
+ }
+
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: computing repair path(s) of %s %s w.r.t %s",
+ vtype2string(vertex->type),
+ vid2string(vertex, buf, sizeof(buf)),
+ lfa_protected_resource2str(
+ &spftree_pc->lfa.protected_resource));
+
+ /* Create base repair list. */
+ repair_list = list_new();
+
+ isis_spf_node_list_init(&used_pnodes);
+ ret = tilfa_build_repair_list(spftree_pc, vertex, vertex, NULL,
+ &used_pnodes, repair_list);
+ isis_spf_node_list_clear(&used_pnodes);
+ list_delete(&repair_list);
+ if (ret != 0)
+ zlog_warn(
+ "ISIS-LFA: failed to compute repair path(s) of %s %s w.r.t %s",
+ vtype2string(vertex->type),
+ vid2string(vertex, buf, sizeof(buf)),
+ lfa_protected_resource2str(
+ &spftree_pc->lfa.protected_resource));
+
+ return ret;
+}
+
+static bool
+spf_adj_node_is_affected(struct isis_spf_node *adj_node,
+ const struct lfa_protected_resource *resource,
+ const uint8_t *root_sysid)
+{
+ struct isis_spf_adj *sadj;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(adj_node->adjacencies, node, sadj)) {
+ if (sadj->metric != adj_node->best_metric)
+ continue;
+ if (spf_adj_check_is_affected(sadj, resource, root_sysid,
+ false))
+ return true;
+ }
+
+ return false;
+}
+
+static bool vertex_is_affected(struct isis_spftree *spftree_root,
+ const struct isis_spf_nodes *adj_nodes,
+ bool p_space, const struct isis_vertex *vertex,
+ const struct lfa_protected_resource *resource)
+{
+ struct isis_vertex *pvertex;
+ struct listnode *node, *vnode;
+
+ for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, pvertex)) {
+ struct isis_spftree *spftree_parent;
+ struct isis_vertex *vertex_child;
+ struct isis_vertex_adj *vadj;
+ bool reverse = false;
+
+ if (p_space && resource->type == LFA_NODE_PROTECTION) {
+ if (isis_spf_node_find(&resource->nodes, vertex->N.id))
+ return true;
+ goto parents;
+ }
+
+ /* Check if either the vertex or its parent is the root node. */
+ if (memcmp(vertex->N.id, spftree_root->sysid, ISIS_SYS_ID_LEN)
+ && memcmp(pvertex->N.id, spftree_root->sysid,
+ ISIS_SYS_ID_LEN))
+ goto parents;
+
+ /* Get SPT of the parent vertex. */
+ if (!memcmp(pvertex->N.id, spftree_root->sysid,
+ ISIS_SYS_ID_LEN))
+ spftree_parent = spftree_root;
+ else {
+ struct isis_spf_node *adj_node;
+
+ adj_node = isis_spf_node_find(adj_nodes, pvertex->N.id);
+ assert(adj_node);
+ spftree_parent = adj_node->lfa.spftree;
+ assert(spftree_parent);
+ reverse = true;
+ }
+
+ /* Get paths pvertex uses to reach vertex. */
+ vertex_child = isis_find_vertex(&spftree_parent->paths,
+ &vertex->N, vertex->type);
+ if (!vertex_child)
+ goto parents;
+
+ /* Check if any of these paths use the protected resource. */
+ for (ALL_LIST_ELEMENTS_RO(vertex_child->Adj_N, vnode, vadj))
+ if (spf_adj_check_is_affected(vadj->sadj, resource,
+ spftree_root->sysid,
+ reverse))
+ return true;
+
+ parents:
+ if (vertex_is_affected(spftree_root, adj_nodes, p_space,
+ pvertex, resource))
+ return true;
+ }
+
+ return false;
+}
+
+/* Calculate set of nodes reachable without using the protected interface. */
+static void lfa_calc_reach_nodes(struct isis_spftree *spftree,
+ struct isis_spftree *spftree_root,
+ const struct isis_spf_nodes *adj_nodes,
+ bool p_space,
+ const struct lfa_protected_resource *resource,
+ struct isis_spf_nodes *nodes)
+{
+ struct isis_vertex *vertex;
+ struct listnode *node;
+
+ for (ALL_QUEUE_ELEMENTS_RO(&spftree->paths, node, vertex)) {
+ char buf[VID2STR_BUFFER];
+
+ if (!VTYPE_IS(vertex->type))
+ continue;
+
+ /* Skip root node. */
+ if (!memcmp(vertex->N.id, spftree_root->sysid, ISIS_SYS_ID_LEN))
+ continue;
+
+ /* Don't add the same node twice. */
+ if (isis_spf_node_find(nodes, vertex->N.id))
+ continue;
+
+ if (!vertex_is_affected(spftree_root, adj_nodes, p_space,
+ vertex, resource)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: adding %s",
+ vid2string(vertex, buf, sizeof(buf)));
+
+ isis_spf_node_new(nodes, vertex->N.id);
+ }
+ }
+}
+
+/**
+ * Helper function used to create an SPF tree structure and run reverse SPF on
+ * it.
+ *
+ * @param spftree IS-IS SPF tree
+ *
+ * @return Pointer to new SPF tree structure.
+ */
+struct isis_spftree *isis_spf_reverse_run(const struct isis_spftree *spftree)
+{
+ struct isis_spftree *spftree_reverse;
+
+ spftree_reverse = isis_spftree_new(
+ spftree->area, spftree->lspdb, spftree->sysid, spftree->level,
+ spftree->tree_id, SPF_TYPE_REVERSE,
+ F_SPFTREE_NO_ADJACENCIES | F_SPFTREE_NO_ROUTES,
+ spftree->algorithm);
+ isis_run_spf(spftree_reverse);
+
+ return spftree_reverse;
+}
+
+/*
+ * Calculate the Extended P-space and Q-space associated to a given link
+ * failure.
+ */
+static void lfa_calc_pq_spaces(struct isis_spftree *spftree_pc,
+ const struct lfa_protected_resource *resource)
+{
+ struct isis_spftree *spftree;
+ struct isis_spftree *spftree_reverse;
+ struct isis_spf_nodes *adj_nodes;
+ struct isis_spf_node *adj_node;
+
+ /* Obtain pre-failure SPTs and list of adjacent nodes. */
+ spftree = spftree_pc->lfa.old.spftree;
+ spftree_reverse = spftree_pc->lfa.old.spftree_reverse;
+ adj_nodes = &spftree->adj_nodes;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: computing P-space (self)");
+ lfa_calc_reach_nodes(spftree, spftree, adj_nodes, true, resource,
+ &spftree_pc->lfa.p_space);
+
+ RB_FOREACH (adj_node, isis_spf_nodes, adj_nodes) {
+ if (spf_adj_node_is_affected(adj_node, resource,
+ spftree->sysid)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: computing Q-space (%s)",
+ print_sys_hostname(adj_node->sysid));
+
+ /*
+ * Compute the reverse SPF in the behalf of the node
+ * adjacent to the failure, if we haven't done that
+ * before
+ */
+ if (!adj_node->lfa.spftree_reverse)
+ adj_node->lfa.spftree_reverse =
+ isis_spf_reverse_run(
+ adj_node->lfa.spftree);
+
+ lfa_calc_reach_nodes(adj_node->lfa.spftree_reverse,
+ spftree_reverse, adj_nodes, false,
+ resource,
+ &spftree_pc->lfa.q_space);
+ } else {
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: computing P-space (%s)",
+ print_sys_hostname(adj_node->sysid));
+ lfa_calc_reach_nodes(adj_node->lfa.spftree, spftree,
+ adj_nodes, true, resource,
+ &adj_node->lfa.p_space);
+ }
+ }
+}
+
+/**
+ * Compute the TI-LFA backup paths for a given protected interface.
+ *
+ * @param area IS-IS area
+ * @param spftree IS-IS SPF tree
+ * @param spftree_reverse IS-IS Reverse SPF tree
+ * @param resource Protected resource
+ *
+ * @return Pointer to the post-convergence SPF tree
+ */
+struct isis_spftree *isis_tilfa_compute(struct isis_area *area,
+ struct isis_spftree *spftree,
+ struct isis_spftree *spftree_reverse,
+ struct lfa_protected_resource *resource)
+{
+ struct isis_spftree *spftree_pc;
+ struct isis_spf_node *adj_node;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: computing TI-LFAs for %s",
+ lfa_protected_resource2str(resource));
+
+ /* Populate list of nodes affected by link failure. */
+ if (resource->type == LFA_NODE_PROTECTION) {
+ isis_spf_node_list_init(&resource->nodes);
+ RB_FOREACH (adj_node, isis_spf_nodes, &spftree->adj_nodes) {
+ if (spf_adj_node_is_affected(adj_node, resource,
+ spftree->sysid))
+ isis_spf_node_new(&resource->nodes,
+ adj_node->sysid);
+ }
+ }
+
+ /* Create post-convergence SPF tree. */
+ spftree_pc = isis_spftree_new(area, spftree->lspdb, spftree->sysid,
+ spftree->level, spftree->tree_id,
+ SPF_TYPE_TI_LFA, spftree->flags,
+ spftree->algorithm);
+ spftree_pc->lfa.old.spftree = spftree;
+ spftree_pc->lfa.old.spftree_reverse = spftree_reverse;
+ spftree_pc->lfa.protected_resource = *resource;
+
+ /* Compute the extended P-space and Q-space. */
+ lfa_calc_pq_spaces(spftree_pc, resource);
+
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: computing the post convergence SPT w.r.t. %s",
+ lfa_protected_resource2str(resource));
+
+ /* Re-run SPF in the local node to find the post-convergence paths. */
+ isis_run_spf(spftree_pc);
+
+ /* Clear list of nodes affeted by link failure. */
+ if (resource->type == LFA_NODE_PROTECTION)
+ isis_spf_node_list_clear(&resource->nodes);
+
+ return spftree_pc;
+}
+
+/**
+ * Run forward SPF on all adjacent routers.
+ *
+ * @param spftree IS-IS SPF tree
+ *
+ * @return 0 on success, -1 otherwise
+ */
+int isis_spf_run_neighbors(struct isis_spftree *spftree)
+{
+ struct isis_lsp *lsp;
+ struct isis_spf_node *adj_node;
+
+ lsp = isis_root_system_lsp(spftree->lspdb, spftree->sysid);
+ if (!lsp)
+ return -1;
+
+ RB_FOREACH (adj_node, isis_spf_nodes, &spftree->adj_nodes) {
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: running SPF on neighbor %s",
+ print_sys_hostname(adj_node->sysid));
+
+ /* Compute the SPT on behalf of the neighbor. */
+ adj_node->lfa.spftree = isis_spftree_new(
+ spftree->area, spftree->lspdb, adj_node->sysid,
+ spftree->level, spftree->tree_id, SPF_TYPE_FORWARD,
+ F_SPFTREE_NO_ADJACENCIES | F_SPFTREE_NO_ROUTES,
+ spftree->algorithm);
+ isis_run_spf(adj_node->lfa.spftree);
+ }
+
+ return 0;
+}
+
+/* Find Router ID of PQ node. */
+static struct in_addr *rlfa_pq_node_rtr_id(struct isis_spftree *spftree,
+ const struct isis_vertex *vertex_pq)
+{
+ struct isis_lsp *lsp;
+
+ lsp = isis_root_system_lsp(spftree->lspdb, vertex_pq->N.id);
+ if (!lsp)
+ return NULL;
+
+ if (lsp->tlvs->router_cap->router_id.s_addr == INADDR_ANY)
+ return NULL;
+
+ return &lsp->tlvs->router_cap->router_id;
+}
+
+/* Find PQ node by intersecting the P/Q spaces. This is a recursive function. */
+static const struct in_addr *
+rlfa_find_pq_node(struct isis_spftree *spftree_pc,
+ struct isis_vertex *vertex_dest,
+ const struct isis_vertex *vertex,
+ const struct isis_vertex *vertex_child)
+{
+ struct isis_area *area = spftree_pc->area;
+ int level = spftree_pc->level;
+ struct isis_vertex *pvertex;
+ struct listnode *node;
+ bool is_pnode, is_qnode;
+
+ if (!vertex_child)
+ goto parents;
+ if (vertex->type != VTYPE_NONPSEUDO_IS
+ && vertex->type != VTYPE_NONPSEUDO_TE_IS)
+ goto parents;
+ if (!VTYPE_IS(vertex_child->type))
+ vertex_child = NULL;
+
+ /* Check if node is part of the extended P-space and/or Q-space. */
+ is_pnode = lfa_ext_p_space_check(spftree_pc, vertex_dest, vertex);
+ is_qnode = lfa_q_space_check(spftree_pc, vertex);
+
+ if (is_pnode && is_qnode) {
+ const struct in_addr *rtr_id_pq;
+ uint32_t max_metric;
+ struct prefix_list *plist = NULL;
+
+ rtr_id_pq = rlfa_pq_node_rtr_id(spftree_pc, vertex);
+ if (!rtr_id_pq) {
+ if (IS_DEBUG_LFA) {
+ char buf[VID2STR_BUFFER];
+
+ vid2string(vertex, buf, sizeof(buf));
+ zlog_debug(
+ "ISIS-LFA: tentative PQ node (%s %s) doesn't have a router-ID",
+ vtype2string(vertex->type), buf);
+ }
+ goto parents;
+ }
+
+ max_metric = spftree_pc->lfa.remote.max_metric;
+ if (max_metric && vertex->d_N > max_metric) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: skipping PQ node %pI4 (maximum metric)",
+ rtr_id_pq);
+ goto parents;
+ }
+
+ plist = area->rlfa_plist[level - 1];
+ if (plist) {
+ struct prefix p;
+
+ p.family = AF_INET;
+ p.prefixlen = IPV4_MAX_BITLEN;
+ p.u.prefix4 = *rtr_id_pq;
+ if (prefix_list_apply(plist, &p) == PREFIX_DENY) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: PQ node %pI4 filtered by prefix-list",
+ rtr_id_pq);
+ goto parents;
+ }
+ }
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: found PQ node: %pI4", rtr_id_pq);
+
+ return rtr_id_pq;
+ }
+
+parents:
+ for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, pvertex)) {
+ const struct in_addr *rtr_id_pq;
+
+ rtr_id_pq = rlfa_find_pq_node(spftree_pc, vertex_dest, pvertex,
+ vertex);
+ if (rtr_id_pq)
+ return rtr_id_pq;
+ }
+
+ return NULL;
+}
+
+int rlfa_cmp(const struct rlfa *a, const struct rlfa *b)
+{
+ return prefix_cmp(&a->prefix, &b->prefix);
+}
+
+static struct rlfa *rlfa_add(struct isis_spftree *spftree,
+ struct isis_vertex *vertex,
+ struct in_addr pq_address)
+{
+ struct rlfa *rlfa;
+
+ assert(VTYPE_IP(vertex->type));
+ rlfa = XCALLOC(MTYPE_ISIS_RLFA, sizeof(*rlfa));
+ rlfa->prefix = vertex->N.ip.p.dest;
+ rlfa->vertex = vertex;
+ rlfa->pq_address = pq_address;
+ rlfa_tree_add(&spftree->lfa.remote.rlfas, rlfa);
+
+ return rlfa;
+}
+
+static void rlfa_delete(struct isis_spftree *spftree, struct rlfa *rlfa)
+{
+ rlfa_tree_del(&spftree->lfa.remote.rlfas, rlfa);
+ XFREE(MTYPE_ISIS_RLFA, rlfa);
+}
+
+static struct rlfa *rlfa_lookup(struct isis_spftree *spftree,
+ union prefixconstptr pu)
+{
+ struct rlfa s = {};
+
+ s.prefix = *pu.p;
+ return rlfa_tree_find(&spftree->lfa.remote.rlfas, &s);
+}
+
+static void isis_area_verify_routes_cb(struct event *thread)
+{
+ struct isis_area *area = EVENT_ARG(thread);
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: updating RLFAs in the RIB");
+
+ isis_area_verify_routes(area);
+}
+
+static mpls_label_t rlfa_nexthop_label(struct isis_spftree *spftree,
+ struct isis_vertex_adj *vadj,
+ struct zapi_rlfa_response *response)
+{
+ struct isis_spf_adj *sadj = vadj->sadj;
+ struct isis_adjacency *adj = sadj->adj;
+
+ /*
+ * Special case to make unit tests work (use implicit-null labels
+ * instead of artifical ones).
+ */
+ if (CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES))
+ return MPLS_LABEL_IMPLICIT_NULL;
+
+ for (unsigned int i = 0; i < response->nexthop_num; i++) {
+ switch (response->nexthops[i].family) {
+ case AF_INET:
+ for (unsigned int j = 0; j < adj->ipv4_address_count;
+ j++) {
+ struct in_addr addr = adj->ipv4_addresses[j];
+
+ if (!IPV4_ADDR_SAME(
+ &addr,
+ &response->nexthops[i].gate.ipv4))
+ continue;
+
+ return response->nexthops[i].label;
+ }
+ break;
+ case AF_INET6:
+ for (unsigned int j = 0; j < adj->ll_ipv6_count; j++) {
+ struct in6_addr addr = adj->ll_ipv6_addrs[j];
+
+ if (!IPV6_ADDR_SAME(
+ &addr,
+ &response->nexthops[i].gate.ipv6))
+ continue;
+
+ return response->nexthops[i].label;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return MPLS_INVALID_LABEL;
+}
+
+int isis_rlfa_activate(struct isis_spftree *spftree, struct rlfa *rlfa,
+ struct zapi_rlfa_response *response)
+{
+ struct isis_area *area = spftree->area;
+ struct isis_vertex *vertex = rlfa->vertex;
+ struct isis_vertex_adj *vadj;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(vertex->Adj_N, node, vadj)) {
+ mpls_label_t ldp_label;
+ struct mpls_label_stack *label_stack;
+ size_t num_labels = 0;
+ size_t i = 0;
+
+ ldp_label = rlfa_nexthop_label(spftree, vadj, response);
+ if (ldp_label == MPLS_INVALID_LABEL) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: failed to activate RLFA: missing LDP label to reach PQ node through %pSY",
+ vadj->sadj->id);
+ return -1;
+ }
+
+ if (ldp_label != MPLS_LABEL_IMPLICIT_NULL)
+ num_labels++;
+ if (response->pq_label != MPLS_LABEL_IMPLICIT_NULL)
+ num_labels++;
+ if (vadj->sr.present
+ && vadj->sr.label != MPLS_LABEL_IMPLICIT_NULL)
+ num_labels++;
+
+ /* Allocate label stack. */
+ label_stack =
+ XCALLOC(MTYPE_ISIS_NEXTHOP_LABELS,
+ sizeof(struct mpls_label_stack)
+ + num_labels * sizeof(mpls_label_t));
+ label_stack->num_labels = num_labels;
+
+ /* Push label allocated by the nexthop (outer label). */
+ if (ldp_label != MPLS_LABEL_IMPLICIT_NULL)
+ label_stack->label[i++] = ldp_label;
+ /* Push label allocated by the PQ node (inner label). */
+ if (response->pq_label != MPLS_LABEL_IMPLICIT_NULL)
+ label_stack->label[i++] = response->pq_label;
+ /* Preserve the original Prefix-SID label when it's present. */
+ if (vadj->sr.present
+ && vadj->sr.label != MPLS_LABEL_IMPLICIT_NULL)
+ label_stack->label[i++] = vadj->sr.label;
+
+ vadj->label_stack = label_stack;
+ }
+
+ isis_route_create(&vertex->N.ip.p.dest, &vertex->N.ip.p.src,
+ vertex->d_N, vertex->depth, &vertex->N.ip.sr,
+ vertex->Adj_N, true, area,
+ spftree->route_table_backup);
+ spftree->lfa.protection_counters.rlfa[vertex->N.ip.priority] += 1;
+
+ EVENT_OFF(area->t_rlfa_rib_update);
+ event_add_timer(master, isis_area_verify_routes_cb, area, 2,
+ &area->t_rlfa_rib_update);
+
+ return 0;
+}
+
+void isis_rlfa_deactivate(struct isis_spftree *spftree, struct rlfa *rlfa)
+{
+ struct isis_area *area = spftree->area;
+ struct isis_vertex *vertex = rlfa->vertex;
+ struct route_node *rn;
+
+ rn = route_node_lookup(spftree->route_table_backup, &rlfa->prefix);
+ if (!rn)
+ return;
+ isis_route_delete(area, rn, spftree->route_table_backup);
+ spftree->lfa.protection_counters.rlfa[vertex->N.ip.priority] -= 1;
+
+ EVENT_OFF(area->t_rlfa_rib_update);
+ event_add_timer(master, isis_area_verify_routes_cb, area, 2,
+ &area->t_rlfa_rib_update);
+}
+
+void isis_rlfa_list_init(struct isis_spftree *spftree)
+{
+ rlfa_tree_init(&spftree->lfa.remote.rlfas);
+}
+
+void isis_rlfa_list_clear(struct isis_spftree *spftree)
+{
+ while (rlfa_tree_count(&spftree->lfa.remote.rlfas) > 0) {
+ struct rlfa *rlfa;
+
+ rlfa = rlfa_tree_first(&spftree->lfa.remote.rlfas);
+ isis_rlfa_deactivate(spftree, rlfa);
+ rlfa_delete(spftree, rlfa);
+ }
+}
+
+void isis_rlfa_process_ldp_response(struct zapi_rlfa_response *response)
+{
+ struct isis *isis;
+ struct isis_area *area;
+ struct isis_spftree *spftree;
+ struct rlfa *rlfa;
+ enum spf_tree_id tree_id;
+ uint32_t spf_run_id;
+ int level;
+
+ if (response->igp.protocol != ZEBRA_ROUTE_ISIS)
+ return;
+
+ isis = isis_lookup_by_vrfid(response->igp.vrf_id);
+ if (!isis)
+ return;
+
+ area = isis_area_lookup(response->igp.isis.area_tag,
+ response->igp.vrf_id);
+ if (!area)
+ return;
+
+ tree_id = response->igp.isis.spf.tree_id;
+ if (tree_id != SPFTREE_IPV4 && tree_id != SPFTREE_IPV6) {
+ zlog_warn("ISIS-LFA: invalid SPF tree ID received from LDP");
+ return;
+ }
+
+ level = response->igp.isis.spf.level;
+ if (level != ISIS_LEVEL1 && level != ISIS_LEVEL2) {
+ zlog_warn("ISIS-LFA: invalid IS-IS level received from LDP");
+ return;
+ }
+
+ spf_run_id = response->igp.isis.spf.run_id;
+ spftree = area->spftree[tree_id][level - 1];
+ if (spftree->runcount != spf_run_id)
+ /* Outdated RLFA, ignore... */
+ return;
+
+ rlfa = rlfa_lookup(spftree, &response->destination);
+ if (!rlfa) {
+ zlog_warn(
+ "ISIS-LFA: couldn't find Remote-LFA %pFX received from LDP",
+ &response->destination);
+ return;
+ }
+
+ if (response->pq_label != MPLS_INVALID_LABEL) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: activating/updating RLFA for %pFX",
+ &rlfa->prefix);
+
+ if (isis_rlfa_activate(spftree, rlfa, response) != 0)
+ isis_rlfa_deactivate(spftree, rlfa);
+ } else {
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: deactivating RLFA for %pFX",
+ &rlfa->prefix);
+
+ isis_rlfa_deactivate(spftree, rlfa);
+ }
+}
+
+void isis_ldp_rlfa_handle_client_close(struct zapi_client_close_info *info)
+{
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+ struct isis_area *area;
+ struct listnode *node;
+
+ if (!isis)
+ return;
+
+ /* Check if the LDP main client session closed */
+ if (info->proto != ZEBRA_ROUTE_LDP || info->session_id == 0)
+ return;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: LDP is down, deactivating all RLFAs");
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS;
+ level++) {
+ struct isis_spftree *spftree;
+
+ if (!(area->is_type & level))
+ continue;
+ if (!area->spftree[tree][level - 1])
+ continue;
+
+ spftree = area->spftree[tree][level - 1];
+ isis_rlfa_list_clear(spftree);
+ }
+ }
+ }
+}
+
+/**
+ * Check if the given SPF vertex needs protection and, if so, attempt to
+ * compute a Remote LFA for it.
+ *
+ * @param spftree_pc The post-convergence SPF tree
+ * @param vertex IS-IS SPF vertex to check
+ */
+void isis_rlfa_check(struct isis_spftree *spftree_pc,
+ struct isis_vertex *vertex)
+{
+ struct isis_spftree *spftree_old = spftree_pc->lfa.old.spftree;
+ struct rlfa *rlfa;
+ const struct in_addr *rtr_id_pq;
+ char buf[VID2STR_BUFFER];
+
+ if (!lfa_check_needs_protection(spftree_pc, vertex)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: %s %s unaffected by %s",
+ vtype2string(vertex->type),
+ vid2string(vertex, buf, sizeof(buf)),
+ lfa_protected_resource2str(
+ &spftree_pc->lfa.protected_resource));
+
+ return;
+ }
+
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: computing repair path(s) of %s %s w.r.t %s",
+ vtype2string(vertex->type),
+ vid2string(vertex, buf, sizeof(buf)),
+ lfa_protected_resource2str(
+ &spftree_pc->lfa.protected_resource));
+
+ /* Find PQ node. */
+ rtr_id_pq = rlfa_find_pq_node(spftree_pc, vertex, vertex, NULL);
+ if (!rtr_id_pq) {
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: no acceptable PQ node found");
+ return;
+ }
+
+ /* Store valid RLFA and store LDP label for the PQ node. */
+ rlfa = rlfa_add(spftree_old, vertex, *rtr_id_pq);
+
+ /* Register RLFA with LDP. */
+ if (isis_zebra_rlfa_register(spftree_old, rlfa) != 0)
+ rlfa_delete(spftree_old, rlfa);
+}
+
+/**
+ * Compute the Remote LFA backup paths for a given protected interface.
+ *
+ * @param area IS-IS area
+ * @param spftree IS-IS SPF tree
+ * @param spftree_reverse IS-IS Reverse SPF tree
+ * @param max_metric Remote LFA maximum metric
+ * @param resource Protected resource
+ *
+ * @return Pointer to the post-convergence SPF tree
+ */
+struct isis_spftree *isis_rlfa_compute(struct isis_area *area,
+ struct isis_spftree *spftree,
+ struct isis_spftree *spftree_reverse,
+ uint32_t max_metric,
+ struct lfa_protected_resource *resource)
+{
+ struct isis_spftree *spftree_pc;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: computing remote LFAs for %s",
+ lfa_protected_resource2str(resource));
+
+ /* Create post-convergence SPF tree. */
+ spftree_pc = isis_spftree_new(area, spftree->lspdb, spftree->sysid,
+ spftree->level, spftree->tree_id,
+ SPF_TYPE_RLFA, spftree->flags,
+ spftree->algorithm);
+ spftree_pc->lfa.old.spftree = spftree;
+ spftree_pc->lfa.old.spftree_reverse = spftree_reverse;
+ spftree_pc->lfa.remote.max_metric = max_metric;
+ spftree_pc->lfa.protected_resource = *resource;
+
+ /* Compute the extended P-space and Q-space. */
+ lfa_calc_pq_spaces(spftree_pc, resource);
+
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: computing the post convergence SPT w.r.t. %s",
+ lfa_protected_resource2str(resource));
+
+ /* Re-run SPF in the local node to find the post-convergence paths. */
+ isis_run_spf(spftree_pc);
+
+ return spftree_pc;
+}
+
+/* Calculate the distance from the root node to the given IP destination. */
+static int lfa_calc_dist_destination(struct isis_spftree *spftree,
+ const struct isis_vertex *vertex_N,
+ uint32_t *distance)
+{
+ struct isis_vertex *vertex, *vertex_best = NULL;
+
+ switch (spftree->family) {
+ case AF_INET:
+ for (int vtype = VTYPE_IPREACH_INTERNAL;
+ vtype <= VTYPE_IPREACH_TE; vtype++) {
+ vertex = isis_find_vertex(
+ &spftree->paths, &vertex_N->N.ip.p.dest, vtype);
+ if (!vertex)
+ continue;
+
+ /* Pick vertex with the best metric. */
+ if (!vertex_best || vertex_best->d_N > vertex->d_N)
+ vertex_best = vertex;
+ }
+ break;
+ case AF_INET6:
+ for (int vtype = VTYPE_IP6REACH_INTERNAL;
+ vtype <= VTYPE_IP6REACH_EXTERNAL; vtype++) {
+ vertex = isis_find_vertex(
+ &spftree->paths, &vertex_N->N.ip.p.dest, vtype);
+ if (!vertex)
+ continue;
+
+ /* Pick vertex with the best metric. */
+ if (!vertex_best || vertex_best->d_N > vertex->d_N)
+ vertex_best = vertex;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!vertex_best)
+ return -1;
+
+ assert(VTYPE_IP(vertex_best->type));
+ vertex_best = listnode_head(vertex_best->parents);
+ *distance = vertex_best->d_N;
+
+ return 0;
+}
+
+/* Calculate the distance from the root node to the given node. */
+static int lfa_calc_dist_node(struct isis_spftree *spftree,
+ const uint8_t *sysid, uint32_t *distance)
+{
+ struct isis_vertex *vertex, *vertex_best = NULL;
+
+ for (int vtype = VTYPE_PSEUDO_IS; vtype <= VTYPE_NONPSEUDO_TE_IS;
+ vtype++) {
+ vertex = isis_find_vertex(&spftree->paths, sysid, vtype);
+ if (!vertex)
+ continue;
+
+ /* Pick vertex with the best metric. */
+ if (!vertex_best || vertex_best->d_N > vertex->d_N)
+ vertex_best = vertex;
+ }
+
+ if (!vertex_best)
+ return -1;
+
+ *distance = vertex_best->d_N;
+
+ return 0;
+}
+
+/*
+ * Check loop-free criterion (RFC 5286's inequality 1):
+ * - Dist_opt(N, D) < Dist_opt(N, S) + Dist_opt(S, D)
+ */
+static bool clfa_loop_free_check(struct isis_spftree *spftree,
+ struct isis_vertex *vertex_S_D,
+ struct isis_spf_adj *sadj_primary,
+ struct isis_spf_adj *sadj_N,
+ uint32_t *path_metric)
+{
+ struct isis_spf_node *node_N;
+ uint32_t dist_N_D;
+ uint32_t dist_N_S;
+ uint32_t dist_S_D;
+
+ node_N = isis_spf_node_find(&spftree->adj_nodes, sadj_N->id);
+ assert(node_N);
+
+ /* Distance from N to D. */
+ if (lfa_calc_dist_destination(node_N->lfa.spftree, vertex_S_D,
+ &dist_N_D)
+ != 0)
+ return false;
+
+ /* Distance from N to S (or PN). */
+ if (CHECK_FLAG(sadj_primary->flags, F_ISIS_SPF_ADJ_BROADCAST)) {
+ static uint8_t pn_sysid[ISIS_SYS_ID_LEN + 1];
+
+ memcpy(pn_sysid, sadj_primary->id, ISIS_SYS_ID_LEN + 1);
+ if (lfa_calc_dist_node(node_N->lfa.spftree, pn_sysid, &dist_N_S)
+ != 0)
+ return false;
+ } else {
+ static uint8_t root_sysid[ISIS_SYS_ID_LEN + 1];
+
+ memcpy(root_sysid, spftree->sysid, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(root_sysid) = 0;
+ if (lfa_calc_dist_node(node_N->lfa.spftree, root_sysid,
+ &dist_N_S)
+ != 0)
+ return false;
+ }
+
+ /* Distance from S (or PN) to D. */
+ vertex_S_D = listnode_head(vertex_S_D->parents);
+ dist_S_D = vertex_S_D->d_N;
+ if (CHECK_FLAG(sadj_primary->flags, F_ISIS_SPF_ADJ_BROADCAST))
+ dist_S_D -= sadj_primary->metric;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: loop-free check: %u < %u + %u", dist_N_D,
+ dist_N_S, dist_S_D);
+
+ if (dist_N_D < (dist_N_S + dist_S_D)) {
+ *path_metric = sadj_N->metric + dist_N_D;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Check loop-free criterion (RFC 5286's inequality 2):
+ * - Distance_opt(N, D) < Distance_opt(S, D)
+ */
+static bool clfa_downstream_check(struct isis_spftree *spftree,
+ struct isis_vertex *vertex_S_D,
+ struct isis_spf_adj *sadj_N)
+{
+ struct isis_spf_node *node_N;
+ uint32_t dist_N_D;
+ uint32_t dist_S_D;
+
+ node_N = isis_spf_node_find(&spftree->adj_nodes, sadj_N->id);
+ assert(node_N);
+
+ /* Distance from N to D. */
+ if (lfa_calc_dist_destination(node_N->lfa.spftree, vertex_S_D,
+ &dist_N_D)
+ != 0)
+ return false;
+
+ /* Distance from S (or PN) to D. */
+ vertex_S_D = listnode_head(vertex_S_D->parents);
+ dist_S_D = vertex_S_D->d_N;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: downstream check: %u < %u", dist_N_D,
+ dist_S_D);
+
+ if (dist_N_D < dist_S_D)
+ return true;
+
+ return false;
+}
+
+/*
+ * Check loop-free criterion (RFC 5286's inequality 3):
+ * - Dist_opt(N, D) < Dist_opt(N, E) + Dist_opt(E, D)
+ */
+static bool clfa_node_protecting_check(struct isis_spftree *spftree,
+ struct isis_vertex *vertex_S_D,
+ struct isis_spf_adj *sadj_N,
+ struct isis_spf_adj *sadj_E)
+{
+ struct isis_spf_node *node_N, *node_E;
+ uint32_t dist_N_D;
+ uint32_t dist_N_E;
+ uint32_t dist_E_D;
+
+ node_N = isis_spf_node_find(&spftree->adj_nodes, sadj_N->id);
+ assert(node_N);
+ node_E = isis_spf_node_find(&spftree->adj_nodes, sadj_E->id);
+ assert(node_E);
+
+ /* Distance from N to D. */
+ if (lfa_calc_dist_destination(node_N->lfa.spftree, vertex_S_D,
+ &dist_N_D)
+ != 0)
+ return false;
+
+ /* Distance from N to E. */
+ if (lfa_calc_dist_node(node_N->lfa.spftree, node_E->sysid, &dist_N_E)
+ != 0)
+ return false;
+
+ /* Distance from E to D. */
+ if (lfa_calc_dist_destination(node_E->lfa.spftree, vertex_S_D,
+ &dist_E_D)
+ != 0)
+ return false;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: node protecting check: %u < %u + %u",
+ dist_N_D, dist_N_E, dist_E_D);
+
+ return (dist_N_D < (dist_N_E + dist_E_D));
+}
+
+static struct list *
+isis_lfa_tiebreakers(struct isis_area *area, struct isis_spftree *spftree,
+ struct lfa_protected_resource *resource,
+ struct isis_vertex *vertex,
+ struct isis_spf_adj *sadj_primary, struct list *lfa_list)
+{
+ struct lfa_tiebreaker *tie_b;
+ int level = spftree->level;
+ struct list *filtered_lfa_list;
+ struct list *tent_lfa_list;
+
+ filtered_lfa_list = list_dup(lfa_list);
+ filtered_lfa_list->del = NULL;
+
+ if (listcount(filtered_lfa_list) == 1)
+ return filtered_lfa_list;
+
+ /* Check tiebreakers in ascending order by index. */
+ frr_each (lfa_tiebreaker_tree, &area->lfa_tiebreakers[level - 1],
+ tie_b) {
+ struct isis_vertex_adj *lfa;
+ struct listnode *node, *nnode;
+ uint32_t best_metric = UINT32_MAX;
+
+ tent_lfa_list = list_dup(filtered_lfa_list);
+
+ switch (tie_b->type) {
+ case LFA_TIEBREAKER_DOWNSTREAM:
+ for (ALL_LIST_ELEMENTS(tent_lfa_list, node, nnode,
+ lfa)) {
+ if (clfa_downstream_check(spftree, vertex,
+ lfa->sadj))
+ continue;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: LFA %s doesn't satisfy the downstream condition",
+ print_sys_hostname(
+ lfa->sadj->id));
+ listnode_delete(tent_lfa_list, lfa);
+ }
+ break;
+ case LFA_TIEBREAKER_LOWEST_METRIC:
+ /* Find the best metric first. */
+ for (ALL_LIST_ELEMENTS_RO(tent_lfa_list, node, lfa)) {
+ if (lfa->lfa_metric < best_metric)
+ best_metric = lfa->lfa_metric;
+ }
+
+ /* Remove LFAs that don't have the best metric. */
+ for (ALL_LIST_ELEMENTS(tent_lfa_list, node, nnode,
+ lfa)) {
+ if (lfa->lfa_metric == best_metric)
+ continue;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: LFA %s doesn't have the lowest cost metric",
+ print_sys_hostname(
+ lfa->sadj->id));
+ listnode_delete(tent_lfa_list, lfa);
+ }
+ break;
+ case LFA_TIEBREAKER_NODE_PROTECTING:
+ for (ALL_LIST_ELEMENTS(tent_lfa_list, node, nnode,
+ lfa)) {
+ if (clfa_node_protecting_check(spftree, vertex,
+ lfa->sadj,
+ sadj_primary))
+ continue;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: LFA %s doesn't provide node protection",
+ print_sys_hostname(
+ lfa->sadj->id));
+ listnode_delete(tent_lfa_list, lfa);
+ }
+ break;
+ }
+
+ /*
+ * Decide what to do next based on the number of remaining LFAs.
+ */
+ switch (listcount(tent_lfa_list)) {
+ case 0:
+ /*
+ * Ignore this tie-breaker since it excluded all LFAs.
+ * Move on to the next one (if any).
+ */
+ list_delete(&tent_lfa_list);
+ break;
+ case 1:
+ /* Finish tie-breaking once we get a single LFA. */
+ list_delete(&filtered_lfa_list);
+ filtered_lfa_list = tent_lfa_list;
+ return filtered_lfa_list;
+ default:
+ /*
+ * We still have two or more LFAs. Move on to the next
+ * tie-breaker (if any).
+ */
+ list_delete(&filtered_lfa_list);
+ filtered_lfa_list = tent_lfa_list;
+ break;
+ }
+ }
+
+ return filtered_lfa_list;
+}
+
+void isis_lfa_compute(struct isis_area *area, struct isis_circuit *circuit,
+ struct isis_spftree *spftree,
+ struct lfa_protected_resource *resource)
+{
+ struct isis_vertex *vertex, *parent_vertex;
+ struct listnode *vnode, *snode;
+ int level = spftree->level;
+
+ resource->type = LFA_LINK_PROTECTION;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: computing local LFAs for %s",
+ lfa_protected_resource2str(resource));
+
+ for (ALL_QUEUE_ELEMENTS_RO(&spftree->paths, vnode, vertex)) {
+ struct list *lfa_list;
+ struct list *filtered_lfa_list;
+ struct isis_spf_adj *sadj_N;
+ struct isis_vertex_adj *vadj_primary;
+ struct isis_spf_adj *sadj_primary;
+ bool allow_ecmp;
+ uint32_t prefix_metric, best_metric = UINT32_MAX;
+ char buf[VID2STR_BUFFER];
+
+ if (!VTYPE_IP(vertex->type))
+ continue;
+
+ vid2string(vertex, buf, sizeof(buf));
+
+ if (!spf_vertex_check_is_affected(vertex, spftree->sysid,
+ resource)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: %s %s unaffected by %s",
+ vtype2string(vertex->type), buf,
+ lfa_protected_resource2str(resource));
+ continue;
+ }
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: checking %s %s w.r.t %s",
+ vtype2string(vertex->type), buf,
+ lfa_protected_resource2str(resource));
+
+ if (vertex->N.ip.priority
+ > area->lfa_priority_limit[level - 1]) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: skipping computing LFAs due to low prefix priority");
+ continue;
+ }
+
+ vadj_primary = listnode_head(vertex->Adj_N);
+ sadj_primary = vadj_primary->sadj;
+
+ parent_vertex = listnode_head(vertex->parents);
+ prefix_metric = vertex->d_N - parent_vertex->d_N;
+
+ /*
+ * Loop over list of SPF adjacencies and compute a list of
+ * preliminary LFAs.
+ */
+ lfa_list = list_new();
+ lfa_list->del = isis_vertex_adj_free;
+ for (ALL_LIST_ELEMENTS_RO(spftree->sadj_list, snode, sadj_N)) {
+ uint32_t lfa_metric, path_metric;
+ struct isis_vertex_adj *lfa;
+ struct isis_prefix_sid *psid = NULL;
+ bool last_hop = false;
+
+ /* Skip pseudonodes. */
+ if (LSP_PSEUDO_ID(sadj_N->id))
+ continue;
+
+ /*
+ * Skip nexthops that are along a link whose cost is
+ * infinite.
+ */
+ if (CHECK_FLAG(sadj_N->flags,
+ F_ISIS_SPF_ADJ_METRIC_INFINITY))
+ continue;
+
+ /* Skip nexthops that have the overload bit set. */
+ if (spftree->mtid != ISIS_MT_IPV4_UNICAST) {
+ struct isis_mt_router_info *mt_router_info;
+
+ mt_router_info =
+ isis_tlvs_lookup_mt_router_info(
+ sadj_N->lsp->tlvs,
+ spftree->mtid);
+ if (mt_router_info && mt_router_info->overload)
+ continue;
+ } else if (ISIS_MASK_LSP_OL_BIT(
+ sadj_N->lsp->hdr.lsp_bits))
+ continue;
+
+ /* Skip primary nexthop. */
+ if (spf_adj_check_is_affected(sadj_N, resource, NULL,
+ false))
+ continue;
+
+ /* Skip excluded interfaces as per the configuration. */
+ if (circuit
+ && isis_lfa_excluded_iface_check(
+ circuit, level,
+ sadj_N->adj->circuit->interface->name))
+ continue;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: checking candidate LFA %s",
+ print_sys_hostname(sadj_N->id));
+
+ /* Check loop-free criterion. */
+ if (!clfa_loop_free_check(spftree, vertex, sadj_primary,
+ sadj_N, &path_metric)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: LFA condition not met for %s",
+ print_sys_hostname(sadj_N->id));
+ continue;
+ }
+
+ lfa_metric = path_metric + prefix_metric;
+ if (lfa_metric < best_metric)
+ best_metric = lfa_metric;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: %s is a valid loop-free alternate",
+ print_sys_hostname(sadj_N->id));
+
+ if (vertex->N.ip.sr.present) {
+ psid = &vertex->N.ip.sr.sid;
+ if (path_metric == sadj_N->metric)
+ last_hop = true;
+ }
+ lfa = isis_vertex_adj_add(spftree, vertex, lfa_list,
+ sadj_N, psid, last_hop);
+ lfa->lfa_metric = lfa_metric;
+ }
+
+ if (list_isempty(lfa_list)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: no valid local LFAs found");
+ list_delete(&lfa_list);
+ continue;
+ }
+
+ SET_FLAG(vertex->flags, F_ISIS_VERTEX_LFA_PROTECTED);
+
+ /* Check tie-breakers. */
+ filtered_lfa_list =
+ isis_lfa_tiebreakers(area, spftree, resource, vertex,
+ sadj_primary, lfa_list);
+
+ /* Create backup route using the best LFAs. */
+ allow_ecmp = area->lfa_load_sharing[level - 1];
+ isis_route_create(&vertex->N.ip.p.dest, &vertex->N.ip.p.src,
+ best_metric, vertex->depth, &vertex->N.ip.sr,
+ filtered_lfa_list, allow_ecmp, area,
+ spftree->route_table_backup);
+ spftree->lfa.protection_counters.lfa[vertex->N.ip.priority] +=
+ 1;
+
+ list_delete(&filtered_lfa_list);
+ list_delete(&lfa_list);
+ }
+}
+
+static void isis_spf_run_tilfa(struct isis_area *area,
+ struct isis_circuit *circuit,
+ struct isis_spftree *spftree,
+ struct isis_spftree *spftree_reverse,
+ struct lfa_protected_resource *resource)
+{
+ struct isis_spftree *spftree_pc_link;
+ struct isis_spftree *spftree_pc_node;
+
+ /* Compute node protecting repair paths first (if necessary). */
+ if (circuit->tilfa_node_protection[spftree->level - 1]) {
+ resource->type = LFA_NODE_PROTECTION;
+ spftree_pc_node = isis_tilfa_compute(area, spftree,
+ spftree_reverse, resource);
+ isis_spftree_del(spftree_pc_node);
+
+ /* don't do link protection unless link-fallback is configured
+ */
+ if (!circuit->tilfa_link_fallback[spftree->level - 1])
+ return;
+ }
+
+ /* Compute link protecting repair paths. */
+ resource->type = LFA_LINK_PROTECTION;
+ spftree_pc_link =
+ isis_tilfa_compute(area, spftree, spftree_reverse, resource);
+ isis_spftree_del(spftree_pc_link);
+}
+
+/**
+ * Run the LFA/RLFA/TI-LFA algorithms for all protected interfaces.
+ *
+ * @param area IS-IS area
+ * @param spftree IS-IS SPF tree
+ */
+void isis_spf_run_lfa(struct isis_area *area, struct isis_spftree *spftree)
+{
+ struct isis_spftree *spftree_reverse = NULL;
+ struct isis_circuit *circuit;
+ struct listnode *node;
+ int level = spftree->level;
+
+ /* Run reverse SPF locally. */
+ if (area->rlfa_protected_links[level - 1] > 0
+ || area->tilfa_protected_links[level - 1] > 0)
+ spftree_reverse = isis_spf_reverse_run(spftree);
+
+ /* Run forward SPF on all adjacent routers. */
+ isis_spf_run_neighbors(spftree);
+
+ /* Check which interfaces are protected. */
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
+ struct lfa_protected_resource resource = {};
+ struct isis_adjacency *adj;
+ static uint8_t null_sysid[ISIS_SYS_ID_LEN + 1];
+
+ if (!(circuit->is_type & level))
+ continue;
+
+ if (!circuit->lfa_protection[level - 1]
+ && !circuit->tilfa_protection[level - 1])
+ continue;
+
+ /* Fill in the protected resource. */
+ switch (circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ if (level == ISIS_LEVEL1)
+ memcpy(resource.adjacency,
+ circuit->u.bc.l1_desig_is,
+ ISIS_SYS_ID_LEN + 1);
+ else
+ memcpy(resource.adjacency,
+ circuit->u.bc.l2_desig_is,
+ ISIS_SYS_ID_LEN + 1);
+ /* Do nothing if no DR was elected yet. */
+ if (!memcmp(resource.adjacency, null_sysid,
+ ISIS_SYS_ID_LEN + 1))
+ continue;
+ break;
+ case CIRCUIT_T_P2P:
+ adj = circuit->u.p2p.neighbor;
+ if (!adj)
+ continue;
+ memcpy(resource.adjacency, adj->sysid, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(resource.adjacency) = 0;
+ break;
+ default:
+ continue;
+ }
+
+ if (circuit->lfa_protection[level - 1]) {
+ /* Run local LFA. */
+ isis_lfa_compute(area, circuit, spftree, &resource);
+
+ if (circuit->rlfa_protection[level - 1]) {
+ struct isis_spftree *spftree_pc;
+ uint32_t max_metric;
+
+ /* Run remote LFA. */
+ assert(spftree_reverse);
+ max_metric =
+ circuit->rlfa_max_metric[level - 1];
+ spftree_pc = isis_rlfa_compute(
+ area, spftree, spftree_reverse,
+ max_metric, &resource);
+ listnode_add(spftree->lfa.remote.pc_spftrees,
+ spftree_pc);
+ }
+ } else if (circuit->tilfa_protection[level - 1]) {
+ /* Run TI-LFA. */
+ assert(spftree_reverse);
+ isis_spf_run_tilfa(area, circuit, spftree,
+ spftree_reverse, &resource);
+ }
+ }
+
+ if (spftree_reverse)
+ isis_spftree_del(spftree_reverse);
+}
diff --git a/isisd/isis_lfa.h b/isisd/isis_lfa.h
new file mode 100644
index 0000000..0ba1c1c
--- /dev/null
+++ b/isisd/isis_lfa.h
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 NetDEF, Inc.
+ * Renato Westphal
+ */
+
+#ifndef _FRR_ISIS_LFA_H
+#define _FRR_ISIS_LFA_H
+
+#include "lib/typesafe.h"
+#include "lib/zclient.h"
+#include "lib/memory.h"
+
+DECLARE_MTYPE(ISIS_NEXTHOP_LABELS);
+
+PREDECL_RBTREE_UNIQ(lfa_tiebreaker_tree);
+PREDECL_RBTREE_UNIQ(rlfa_tree);
+
+enum lfa_tiebreaker_type {
+ LFA_TIEBREAKER_DOWNSTREAM = 0,
+ LFA_TIEBREAKER_LOWEST_METRIC,
+ LFA_TIEBREAKER_NODE_PROTECTING,
+};
+
+struct lfa_tiebreaker {
+ struct lfa_tiebreaker_tree_item entry;
+ uint8_t index;
+ enum lfa_tiebreaker_type type;
+ struct isis_area *area;
+};
+int lfa_tiebreaker_cmp(const struct lfa_tiebreaker *a,
+ const struct lfa_tiebreaker *b);
+DECLARE_RBTREE_UNIQ(lfa_tiebreaker_tree, struct lfa_tiebreaker, entry,
+ lfa_tiebreaker_cmp);
+
+struct rlfa {
+ struct rlfa_tree_item entry;
+ struct prefix prefix;
+ struct isis_vertex *vertex;
+ struct in_addr pq_address;
+};
+int rlfa_cmp(const struct rlfa *a, const struct rlfa *b);
+DECLARE_RBTREE_UNIQ(rlfa_tree, struct rlfa, entry, rlfa_cmp);
+
+enum isis_tilfa_sid_type {
+ TILFA_SID_PREFIX = 1,
+ TILFA_SID_ADJ,
+};
+
+struct isis_tilfa_sid {
+ enum isis_tilfa_sid_type type;
+ union {
+ struct {
+ uint32_t value;
+ bool remote;
+ uint8_t remote_sysid[ISIS_SYS_ID_LEN];
+ } index;
+ mpls_label_t label;
+ } value;
+};
+
+enum spf_prefix_priority {
+ SPF_PREFIX_PRIO_CRITICAL = 0,
+ SPF_PREFIX_PRIO_HIGH,
+ SPF_PREFIX_PRIO_MEDIUM,
+ SPF_PREFIX_PRIO_LOW,
+ SPF_PREFIX_PRIO_MAX,
+};
+
+struct spf_prefix_priority_acl {
+ char *name;
+ struct access_list *list_v4;
+ struct access_list *list_v6;
+};
+
+RB_HEAD(isis_spf_nodes, isis_spf_node);
+RB_PROTOTYPE(isis_spf_nodes, isis_spf_node, entry, isis_spf_node_compare)
+struct isis_spf_node {
+ RB_ENTRY(isis_spf_node) entry;
+
+ /* Node's System ID. */
+ uint8_t sysid[ISIS_SYS_ID_LEN];
+
+ /* Local adjacencies over which this node is reachable. */
+ struct list *adjacencies;
+
+ /* Best metric of all adjacencies used to reach this node. */
+ uint32_t best_metric;
+
+ struct {
+ /* Node's forward SPT. */
+ struct isis_spftree *spftree;
+
+ /* Node's reverse SPT. */
+ struct isis_spftree *spftree_reverse;
+
+ /* Node's P-space. */
+ struct isis_spf_nodes p_space;
+ } lfa;
+};
+
+enum lfa_protection_type {
+ LFA_LINK_PROTECTION = 1,
+ LFA_NODE_PROTECTION,
+};
+
+struct lfa_protected_resource {
+ /* The protection type. */
+ enum lfa_protection_type type;
+
+ /* The protected adjacency (might be a pseudonode). */
+ uint8_t adjacency[ISIS_SYS_ID_LEN + 1];
+
+ /* List of nodes reachable over the protected interface. */
+ struct isis_spf_nodes nodes;
+};
+
+/* Forward declaration(s). */
+struct isis_vertex;
+
+/* Prototypes. */
+void isis_spf_node_list_init(struct isis_spf_nodes *nodes);
+void isis_spf_node_list_clear(struct isis_spf_nodes *nodes);
+struct isis_spf_node *isis_spf_node_new(struct isis_spf_nodes *nodes,
+ const uint8_t *sysid);
+struct isis_spf_node *isis_spf_node_find(const struct isis_spf_nodes *nodes,
+ const uint8_t *sysid);
+void isis_lfa_tiebreakers_init(struct isis_area *area, int level);
+void isis_lfa_tiebreakers_clear(struct isis_area *area, int level);
+struct lfa_tiebreaker *isis_lfa_tiebreaker_add(struct isis_area *area,
+ int level, uint8_t index,
+ enum lfa_tiebreaker_type type);
+void isis_lfa_tiebreaker_delete(struct isis_area *area, int level,
+ struct lfa_tiebreaker *tie_b);
+void isis_lfa_excluded_ifaces_init(struct isis_circuit *circuit, int level);
+void isis_lfa_excluded_ifaces_clear(struct isis_circuit *circuit, int level);
+void isis_lfa_excluded_iface_add(struct isis_circuit *circuit, int level,
+ const char *ifname);
+void isis_lfa_excluded_iface_delete(struct isis_circuit *circuit, int level,
+ const char *ifname);
+bool isis_lfa_excluded_iface_check(struct isis_circuit *circuit, int level,
+ const char *ifname);
+bool isis_lfa_excise_adj_check(const struct isis_spftree *spftree,
+ const uint8_t *id);
+bool isis_lfa_excise_node_check(const struct isis_spftree *spftree,
+ const uint8_t *id);
+struct isis_spftree *isis_spf_reverse_run(const struct isis_spftree *spftree);
+int isis_spf_run_neighbors(struct isis_spftree *spftree);
+int isis_rlfa_activate(struct isis_spftree *spftree, struct rlfa *rlfa,
+ struct zapi_rlfa_response *response);
+void isis_rlfa_deactivate(struct isis_spftree *spftree, struct rlfa *rlfa);
+void isis_rlfa_list_init(struct isis_spftree *spftree);
+void isis_rlfa_list_clear(struct isis_spftree *spftree);
+void isis_rlfa_process_ldp_response(struct zapi_rlfa_response *response);
+void isis_ldp_rlfa_handle_client_close(struct zapi_client_close_info *info);
+void isis_rlfa_check(struct isis_spftree *spftree, struct isis_vertex *vertex);
+struct isis_spftree *isis_rlfa_compute(struct isis_area *area,
+ struct isis_spftree *spftree,
+ struct isis_spftree *spftree_reverse,
+ uint32_t max_metric,
+ struct lfa_protected_resource *resource);
+void isis_lfa_compute(struct isis_area *area, struct isis_circuit *circuit,
+ struct isis_spftree *spftree,
+ struct lfa_protected_resource *resource);
+void isis_spf_run_lfa(struct isis_area *area, struct isis_spftree *spftree);
+int isis_tilfa_check(struct isis_spftree *spftree, struct isis_vertex *vertex);
+struct isis_spftree *
+isis_tilfa_compute(struct isis_area *area, struct isis_spftree *spftree,
+ struct isis_spftree *spftree_reverse,
+ struct lfa_protected_resource *protected_resource);
+
+#endif /* _FRR_ISIS_LFA_H */
diff --git a/isisd/isis_lsp.c b/isisd/isis_lsp.c
new file mode 100644
index 0000000..1b3491f
--- /dev/null
+++ b/isisd/isis_lsp.c
@@ -0,0 +1,2522 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_lsp.c
+ * LSP processing
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ * Copyright (C) 2013-2015 Christian Franke <chris@opensourcerouting.org>
+ */
+
+#include <zebra.h>
+
+#include "linklist.h"
+#include "frrevent.h"
+#include "vty.h"
+#include "stream.h"
+#include "memory.h"
+#include "log.h"
+#include "prefix.h"
+#include "command.h"
+#include "hash.h"
+#include "if.h"
+#include "checksum.h"
+#include "md5.h"
+#include "table.h"
+#include "srcdest_table.h"
+#include "lib_errors.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_dynhn.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_spf.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_tlvs.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_sr.h"
+#include "isisd/fabricd.h"
+#include "isisd/isis_tx_queue.h"
+#include "isisd/isis_nb.h"
+#include "isisd/isis_flex_algo.h"
+
+DEFINE_MTYPE_STATIC(ISISD, ISIS_LSP, "ISIS LSP");
+
+static void lsp_refresh(struct event *thread);
+static void lsp_l1_refresh_pseudo(struct event *thread);
+static void lsp_l2_refresh_pseudo(struct event *thread);
+
+static void lsp_destroy(struct isis_lsp *lsp);
+
+static bool device_startup;
+
+int lsp_id_cmp(uint8_t *id1, uint8_t *id2)
+{
+ return memcmp(id1, id2, ISIS_SYS_ID_LEN + 2);
+}
+
+int lspdb_compare(const struct isis_lsp *a, const struct isis_lsp *b)
+{
+ return memcmp(a->hdr.lsp_id, b->hdr.lsp_id, sizeof(a->hdr.lsp_id));
+}
+
+void lsp_db_init(struct lspdb_head *head)
+{
+ lspdb_init(head);
+}
+
+void lsp_db_fini(struct lspdb_head *head)
+{
+ struct isis_lsp *lsp;
+
+ while ((lsp = lspdb_pop(head)))
+ lsp_destroy(lsp);
+ lspdb_fini(head);
+}
+
+struct isis_lsp *lsp_search(struct lspdb_head *head, const uint8_t *id)
+{
+ struct isis_lsp searchfor;
+ memcpy(searchfor.hdr.lsp_id, id, sizeof(searchfor.hdr.lsp_id));
+
+ return lspdb_find(head, &searchfor);
+}
+
+static void lsp_clear_data(struct isis_lsp *lsp)
+{
+ if (!lsp)
+ return;
+
+ isis_free_tlvs(lsp->tlvs);
+ lsp->tlvs = NULL;
+}
+
+static void lsp_remove_frags(struct lspdb_head *head, struct list *frags);
+
+static void lsp_destroy(struct isis_lsp *lsp)
+{
+ struct listnode *cnode;
+ struct isis_circuit *circuit;
+
+ if (!lsp)
+ return;
+
+ for (ALL_LIST_ELEMENTS_RO(lsp->area->circuit_list, cnode, circuit))
+ isis_tx_queue_del(circuit->tx_queue, lsp);
+
+ ISIS_FLAGS_CLEAR_ALL(lsp->SSNflags);
+
+ isis_te_lsp_event(lsp, LSP_DEL);
+
+ lsp_clear_data(lsp);
+
+ if (!LSP_FRAGMENT(lsp->hdr.lsp_id)) {
+ if (lsp->lspu.frags) {
+ lsp_remove_frags(&lsp->area->lspdb[lsp->level - 1],
+ lsp->lspu.frags);
+ list_delete(&lsp->lspu.frags);
+ }
+ } else {
+ if (lsp->lspu.zero_lsp
+ && lsp->lspu.zero_lsp->lspu.frags) {
+ listnode_delete(lsp->lspu.zero_lsp->lspu.frags, lsp);
+ }
+ }
+
+ isis_spf_schedule(lsp->area, lsp->level);
+
+ if (lsp->pdu)
+ stream_free(lsp->pdu);
+
+ fabricd_lsp_free(lsp);
+ XFREE(MTYPE_ISIS_LSP, lsp);
+}
+
+/*
+ * Remove all the frags belonging to the given lsp
+ */
+static void lsp_remove_frags(struct lspdb_head *head, struct list *frags)
+{
+ struct listnode *lnode, *lnnode;
+ struct isis_lsp *lsp;
+
+ for (ALL_LIST_ELEMENTS(frags, lnode, lnnode, lsp)) {
+ lsp = lsp_search(head, lsp->hdr.lsp_id);
+ lspdb_del(head, lsp);
+ lsp_destroy(lsp);
+ }
+}
+
+void lsp_search_and_destroy(struct lspdb_head *head, const uint8_t *id)
+{
+ struct isis_lsp *lsp;
+
+ lsp = lsp_search(head, id);
+ if (lsp) {
+ lspdb_del(head, lsp);
+ /*
+ * If this is a zero lsp, remove all the frags now
+ */
+ if (LSP_FRAGMENT(lsp->hdr.lsp_id) == 0) {
+ if (lsp->lspu.frags)
+ lsp_remove_frags(head, lsp->lspu.frags);
+ } else {
+ /*
+ * else just remove this frag, from the zero lsps' frag
+ * list
+ */
+ if (lsp->lspu.zero_lsp
+ && lsp->lspu.zero_lsp->lspu.frags)
+ listnode_delete(lsp->lspu.zero_lsp->lspu.frags,
+ lsp);
+ }
+ lsp_destroy(lsp);
+ }
+}
+
+/*
+ * Compares a LSP to given values
+ * Params are given in net order
+ */
+int lsp_compare(char *areatag, struct isis_lsp *lsp, uint32_t seqno,
+ uint16_t checksum, uint16_t rem_lifetime)
+{
+ if (lsp->hdr.seqno == seqno && lsp->hdr.checksum == checksum
+ && ((lsp->hdr.rem_lifetime == 0 && rem_lifetime == 0)
+ || (lsp->hdr.rem_lifetime != 0 && rem_lifetime != 0))) {
+ if (IS_DEBUG_SNP_PACKETS) {
+ zlog_debug(
+ "ISIS-Snp (%s): Compare LSP %pLS seq 0x%08x, cksum 0x%04hx, lifetime %hus",
+ areatag, lsp->hdr.lsp_id, lsp->hdr.seqno,
+ lsp->hdr.checksum, lsp->hdr.rem_lifetime);
+ zlog_debug(
+ "ISIS-Snp (%s): is equal to ours seq 0x%08x, cksum 0x%04hx, lifetime %hus",
+ areatag, seqno, checksum, rem_lifetime);
+ }
+ return LSP_EQUAL;
+ }
+
+ /*
+ * LSPs with identical checksums should only be treated as newer if:
+ * a) The current LSP has a remaining lifetime != 0 and the other LSP
+ * has a
+ * remaining lifetime == 0. In this case, we should participate in
+ * the purge
+ * and should not treat the current LSP with remaining lifetime == 0
+ * as older.
+ * b) The LSP has an incorrect checksum. In this case, we need to react
+ * as given
+ * in 7.3.16.2.
+ */
+ if (seqno > lsp->hdr.seqno
+ || (seqno == lsp->hdr.seqno
+ && ((lsp->hdr.rem_lifetime != 0 && rem_lifetime == 0)
+ || (lsp->hdr.checksum != checksum
+ && lsp->hdr.rem_lifetime)))) {
+ if (IS_DEBUG_SNP_PACKETS) {
+ zlog_debug(
+ "ISIS-Snp (%s): Compare LSP %pLS seq 0x%08x, cksum 0x%04hx, lifetime %hus",
+ areatag, lsp->hdr.lsp_id, seqno, checksum,
+ rem_lifetime);
+ zlog_debug(
+ "ISIS-Snp (%s): is newer than ours seq 0x%08x, cksum 0x%04hx, lifetime %hus",
+ areatag, lsp->hdr.seqno, lsp->hdr.checksum,
+ lsp->hdr.rem_lifetime);
+ }
+ return LSP_NEWER;
+ }
+ if (IS_DEBUG_SNP_PACKETS) {
+ zlog_debug(
+ "ISIS-Snp (%s): Compare LSP %pLS seq 0x%08x, cksum 0x%04hx, lifetime %hus",
+ areatag, lsp->hdr.lsp_id, seqno, checksum,
+ rem_lifetime);
+ zlog_debug(
+ "ISIS-Snp (%s): is older than ours seq 0x%08x, cksum 0x%04hx, lifetime %hus",
+ areatag, lsp->hdr.seqno, lsp->hdr.checksum,
+ lsp->hdr.rem_lifetime);
+ }
+
+ return LSP_OLDER;
+}
+
+static void put_lsp_hdr(struct isis_lsp *lsp, size_t *len_pointer, bool keep)
+{
+ uint8_t pdu_type =
+ (lsp->level == IS_LEVEL_1) ? L1_LINK_STATE : L2_LINK_STATE;
+ struct isis_lsp_hdr *hdr = &lsp->hdr;
+ struct stream *stream = lsp->pdu;
+ size_t orig_getp = 0, orig_endp = 0;
+
+ if (keep) {
+ orig_getp = stream_get_getp(lsp->pdu);
+ orig_endp = stream_get_endp(lsp->pdu);
+ }
+
+ stream_set_getp(lsp->pdu, 0);
+ stream_set_endp(lsp->pdu, 0);
+
+ fill_fixed_hdr(pdu_type, stream);
+
+ if (len_pointer)
+ *len_pointer = stream_get_endp(stream);
+ stream_putw(stream, hdr->pdu_len);
+ stream_putw(stream, hdr->rem_lifetime);
+ stream_put(stream, hdr->lsp_id, sizeof(hdr->lsp_id));
+ stream_putl(stream, hdr->seqno);
+ stream_putw(stream, hdr->checksum);
+ stream_putc(stream, hdr->lsp_bits);
+
+ if (keep) {
+ stream_set_endp(lsp->pdu, orig_endp);
+ stream_set_getp(lsp->pdu, orig_getp);
+ }
+}
+
+static void lsp_add_auth(struct isis_lsp *lsp)
+{
+ struct isis_passwd *passwd;
+ passwd = (lsp->level == IS_LEVEL_1) ? &lsp->area->area_passwd
+ : &lsp->area->domain_passwd;
+ isis_tlvs_add_auth(lsp->tlvs, passwd);
+}
+
+static void lsp_pack_pdu(struct isis_lsp *lsp)
+{
+ if (!lsp->tlvs)
+ lsp->tlvs = isis_alloc_tlvs();
+
+ lsp_add_auth(lsp);
+
+ size_t len_pointer;
+ put_lsp_hdr(lsp, &len_pointer, false);
+ isis_pack_tlvs(lsp->tlvs, lsp->pdu, len_pointer, false, true);
+
+ lsp->hdr.pdu_len = stream_get_endp(lsp->pdu);
+ lsp->hdr.checksum =
+ ntohs(fletcher_checksum(STREAM_DATA(lsp->pdu) + 12,
+ stream_get_endp(lsp->pdu) - 12, 12));
+}
+
+void lsp_inc_seqno(struct isis_lsp *lsp, uint32_t seqno)
+{
+ uint32_t newseq;
+
+ if (seqno == 0 || lsp->hdr.seqno > seqno)
+ newseq = lsp->hdr.seqno + 1;
+ else
+ newseq = seqno + 1;
+
+#ifndef FABRICD
+ /* check for overflow */
+ if (newseq < lsp->hdr.seqno) {
+ /* send northbound notification */
+ lsp->area->lsp_exceeded_max_counter++;
+ isis_notif_lsp_exceed_max(lsp->area, lsp->hdr.lsp_id);
+ }
+#endif /* ifndef FABRICD */
+
+ lsp->hdr.seqno = newseq;
+
+ lsp_pack_pdu(lsp);
+ isis_spf_schedule(lsp->area, lsp->level);
+ isis_te_lsp_event(lsp, LSP_INC);
+}
+
+static void lsp_purge_add_poi(struct isis_lsp *lsp,
+ const uint8_t *sender)
+{
+ if (lsp->area == NULL)
+ return;
+
+ if (!lsp->area->purge_originator)
+ return;
+
+ /* add purge originator identification */
+ if (!lsp->tlvs)
+ lsp->tlvs = isis_alloc_tlvs();
+ isis_tlvs_set_purge_originator(lsp->tlvs, lsp->area->isis->sysid,
+ sender);
+ isis_tlvs_set_dynamic_hostname(lsp->tlvs, cmd_hostname_get());
+}
+
+static void lsp_purge(struct isis_lsp *lsp, int level,
+ const uint8_t *sender)
+{
+ /* reset stream */
+ lsp_clear_data(lsp);
+ stream_reset(lsp->pdu);
+
+ /* update header */
+ lsp->hdr.checksum = 0;
+ lsp->hdr.rem_lifetime = 0;
+ lsp->level = level;
+ lsp->age_out = lsp->area->max_lsp_lifetime[level - 1];
+ lsp->area->lsp_purge_count[level - 1]++;
+
+ lsp_purge_add_poi(lsp, sender);
+
+ lsp_pack_pdu(lsp);
+ lsp_flood(lsp, NULL);
+}
+
+/*
+ * Generates checksum for LSP and its frags
+ */
+static void lsp_seqno_update(struct isis_lsp *lsp0)
+{
+ struct isis_lsp *lsp;
+ struct listnode *node;
+
+ lsp_inc_seqno(lsp0, 0);
+
+ if (!lsp0->lspu.frags)
+ return;
+
+ for (ALL_LIST_ELEMENTS_RO(lsp0->lspu.frags, node, lsp)) {
+ if (lsp->tlvs)
+ lsp_inc_seqno(lsp, 0);
+ else if (lsp->hdr.rem_lifetime) {
+ /* Purge should only be applied when the fragment has
+ * non-zero remaining lifetime.
+ */
+ lsp_purge(lsp, lsp0->level, NULL);
+ }
+ }
+
+ return;
+}
+
+bool isis_level2_adj_up(struct isis_area *area)
+{
+ struct listnode *node, *cnode;
+ struct isis_circuit *circuit;
+ struct list *adjdb;
+ struct isis_adjacency *adj;
+
+ if (area->is_type == IS_LEVEL_1)
+ return false;
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) {
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ adjdb = circuit->u.bc.adjdb[1];
+ if (!adjdb || !adjdb->count)
+ continue;
+
+ for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) {
+ if (adj->level != ISIS_ADJ_LEVEL1
+ && adj->adj_state == ISIS_ADJ_UP)
+ return true;
+ }
+ } else if (circuit->circ_type == CIRCUIT_T_P2P
+ && circuit->u.p2p.neighbor) {
+ adj = circuit->u.p2p.neighbor;
+ if (adj->level != ISIS_ADJ_LEVEL1
+ && adj->adj_state == ISIS_ADJ_UP)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Unset the overload bit after the timer expires
+ */
+void set_overload_on_start_timer(struct event *thread)
+{
+ struct isis_area *area = EVENT_ARG(thread);
+ assert(area);
+
+ area->t_overload_on_startup_timer = NULL;
+
+ /* Check if set-overload-bit is not currently configured */
+ if (!area->overload_configured)
+ isis_area_overload_bit_set(area, false);
+}
+
+static void isis_reset_attach_bit(struct isis_adjacency *adj)
+{
+ struct isis_area *area = adj->circuit->area;
+ struct lspdb_head *head;
+ struct isis_lsp *lsp;
+ uint8_t lspid[ISIS_SYS_ID_LEN + 2];
+
+ /*
+ * If an L2 adjacency changed its state in L-1-2 area, we have to:
+ * - set the attached bit in L1 LSPs if it's the first L2 adjacency
+ * - remove the attached bit in L1 LSPs if it's the last L2 adjacency
+ */
+
+ if (area->is_type != IS_LEVEL_1_AND_2 || adj->level == ISIS_ADJ_LEVEL1)
+ return;
+
+ if (!area->attached_bit_send)
+ return;
+
+ head = &area->lspdb[IS_LEVEL_1 - 1];
+ memset(lspid, 0, ISIS_SYS_ID_LEN + 2);
+ memcpy(lspid, area->isis->sysid, ISIS_SYS_ID_LEN);
+
+ lsp = lsp_search(head, lspid);
+ if (!lsp)
+ return;
+
+ if (adj->adj_state == ISIS_ADJ_UP
+ && !(lsp->hdr.lsp_bits & LSPBIT_ATT)) {
+ sched_debug("ISIS (%s): adj going up regenerate lsp-bits",
+ area->area_tag);
+ lsp_regenerate_schedule(area, IS_LEVEL_1, 0);
+ } else if (adj->adj_state == ISIS_ADJ_DOWN
+ && (lsp->hdr.lsp_bits & LSPBIT_ATT)
+ && !isis_level2_adj_up(area)) {
+ sched_debug("ISIS (%s): adj going down regenerate lsp-bits",
+ area->area_tag);
+ lsp_regenerate_schedule(area, IS_LEVEL_1, 0);
+ }
+}
+
+static uint8_t lsp_bits_generate(int level, int overload_bit, int attached_bit,
+ struct isis_area *area)
+{
+ uint8_t lsp_bits = 0;
+ if (area->is_type == IS_LEVEL_1)
+ lsp_bits = IS_LEVEL_1;
+ else
+ lsp_bits = IS_LEVEL_1_AND_2;
+ if (overload_bit)
+ lsp_bits |= overload_bit;
+
+ /* only set the attach bit if we are a level-1-2 router and this is
+ * a level-1 LSP and we have a level-2 adjacency up from another area
+ */
+ if (area->is_type == IS_LEVEL_1_AND_2 && level == IS_LEVEL_1
+ && attached_bit && isis_level2_adj_up(area))
+ lsp_bits |= LSPBIT_ATT;
+ return lsp_bits;
+}
+
+static void lsp_update_data(struct isis_lsp *lsp, struct isis_lsp_hdr *hdr,
+ struct isis_tlvs *tlvs, struct stream *stream,
+ struct isis_area *area, int level)
+{
+ /* free the old lsp data */
+ lsp_clear_data(lsp);
+
+ /* copying only the relevant part of our stream */
+ if (lsp->pdu != NULL)
+ stream_free(lsp->pdu);
+ lsp->pdu = stream_dup(stream);
+
+ memcpy(&lsp->hdr, hdr, sizeof(lsp->hdr));
+ lsp->area = area;
+ lsp->level = level;
+ lsp->age_out = ZERO_AGE_LIFETIME;
+ lsp->installed = time(NULL);
+
+ lsp->tlvs = tlvs;
+
+ if (area->dynhostname && lsp->tlvs->hostname
+ && lsp->hdr.rem_lifetime) {
+ isis_dynhn_insert(
+ area->isis, lsp->hdr.lsp_id, lsp->tlvs->hostname,
+ (lsp->hdr.lsp_bits & LSPBIT_IST) == IS_LEVEL_1_AND_2
+ ? IS_LEVEL_2
+ : IS_LEVEL_1);
+ }
+
+ return;
+}
+
+static void lsp_link_fragment(struct isis_lsp *lsp, struct isis_lsp *lsp0)
+{
+ if (!LSP_FRAGMENT(lsp->hdr.lsp_id)) {
+ /* zero lsp -> create list to store fragments */
+ lsp->lspu.frags = list_new();
+ } else {
+ /* fragment -> set backpointer and add to zero lsps list */
+ assert(lsp0);
+ lsp->lspu.zero_lsp = lsp0;
+ listnode_add(lsp0->lspu.frags, lsp);
+ }
+}
+
+void lsp_update(struct isis_lsp *lsp, struct isis_lsp_hdr *hdr,
+ struct isis_tlvs *tlvs, struct stream *stream,
+ struct isis_area *area, int level, bool confusion)
+{
+ if (lsp->own_lsp) {
+ flog_err(
+ EC_LIB_DEVELOPMENT,
+ "ISIS-Upd (%s): BUG updating LSP %pLS still marked as own LSP",
+ area->area_tag, lsp->hdr.lsp_id);
+ lsp_clear_data(lsp);
+ lsp->own_lsp = 0;
+ }
+
+ if (confusion) {
+ lsp_purge(lsp, level, NULL);
+ } else {
+ lsp_update_data(lsp, hdr, tlvs, stream, area, level);
+ }
+
+ if (LSP_FRAGMENT(lsp->hdr.lsp_id) && !lsp->lspu.zero_lsp) {
+ uint8_t lspid[ISIS_SYS_ID_LEN + 2];
+ struct isis_lsp *lsp0;
+
+ memcpy(lspid, lsp->hdr.lsp_id, ISIS_SYS_ID_LEN + 1);
+ LSP_FRAGMENT(lspid) = 0;
+ lsp0 = lsp_search(&area->lspdb[level - 1], lspid);
+ if (lsp0)
+ lsp_link_fragment(lsp, lsp0);
+ }
+
+ if (lsp->hdr.seqno) {
+ isis_spf_schedule(lsp->area, lsp->level);
+ isis_te_lsp_event(lsp, LSP_UPD);
+ }
+}
+
+/* creation of LSP directly from what we received */
+struct isis_lsp *lsp_new_from_recv(struct isis_lsp_hdr *hdr,
+ struct isis_tlvs *tlvs,
+ struct stream *stream, struct isis_lsp *lsp0,
+ struct isis_area *area, int level)
+{
+ struct isis_lsp *lsp;
+
+ lsp = XCALLOC(MTYPE_ISIS_LSP, sizeof(struct isis_lsp));
+ lsp_update_data(lsp, hdr, tlvs, stream, area, level);
+ lsp_link_fragment(lsp, lsp0);
+
+ return lsp;
+}
+
+static void lsp_adjust_stream(struct isis_lsp *lsp)
+{
+ if (lsp->pdu) {
+ if (STREAM_SIZE(lsp->pdu) == LLC_LEN + lsp->area->lsp_mtu)
+ return;
+ stream_free(lsp->pdu);
+ }
+
+ lsp->pdu = stream_new(LLC_LEN + lsp->area->lsp_mtu);
+}
+
+struct isis_lsp *lsp_new(struct isis_area *area, uint8_t *lsp_id,
+ uint16_t rem_lifetime, uint32_t seqno,
+ uint8_t lsp_bits, uint16_t checksum,
+ struct isis_lsp *lsp0, int level)
+{
+ struct isis_lsp *lsp;
+
+ lsp = XCALLOC(MTYPE_ISIS_LSP, sizeof(struct isis_lsp));
+ lsp->area = area;
+
+ lsp_adjust_stream(lsp);
+
+ /* Minimal LSP PDU size */
+ lsp->hdr.pdu_len = ISIS_FIXED_HDR_LEN + ISIS_LSP_HDR_LEN;
+ memcpy(lsp->hdr.lsp_id, lsp_id, sizeof(lsp->hdr.lsp_id));
+ lsp->hdr.checksum = checksum;
+ lsp->hdr.seqno = seqno;
+ lsp->hdr.rem_lifetime = rem_lifetime;
+ lsp->hdr.lsp_bits = lsp_bits;
+ lsp->level = level;
+ lsp->age_out = ZERO_AGE_LIFETIME;
+ lsp_link_fragment(lsp, lsp0);
+ put_lsp_hdr(lsp, NULL, false);
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("New LSP with ID %pLS len %d seqnum %08x", lsp_id,
+ lsp->hdr.pdu_len, lsp->hdr.seqno);
+
+ return lsp;
+}
+
+void lsp_insert(struct lspdb_head *head, struct isis_lsp *lsp)
+{
+ lspdb_add(head, lsp);
+ if (lsp->hdr.seqno) {
+ isis_spf_schedule(lsp->area, lsp->level);
+ isis_te_lsp_event(lsp, LSP_ADD);
+ }
+}
+
+/*
+ * Build a list of LSPs with non-zero ht and seqno bounded by start and stop ids
+ */
+void lsp_build_list_nonzero_ht(struct lspdb_head *head, const uint8_t *start_id,
+ const uint8_t *stop_id, struct list *list)
+{
+ struct isis_lsp searchfor;
+ struct isis_lsp *lsp, *start;
+
+ memcpy(&searchfor.hdr.lsp_id, start_id, sizeof(searchfor.hdr.lsp_id));
+
+ start = lspdb_find_gteq(head, &searchfor);
+ frr_each_from (lspdb, head, lsp, start) {
+ if (memcmp(lsp->hdr.lsp_id, stop_id,
+ ISIS_SYS_ID_LEN + 2) > 0)
+ break;
+
+ if (lsp->hdr.rem_lifetime && lsp->hdr.seqno)
+ listnode_add(list, lsp);
+ }
+}
+
+static void lsp_set_time(struct isis_lsp *lsp)
+{
+ assert(lsp);
+
+ if (lsp->hdr.rem_lifetime == 0) {
+ if (lsp->age_out > 0)
+ lsp->age_out--;
+ return;
+ }
+
+ lsp->hdr.rem_lifetime--;
+ if (lsp->pdu && stream_get_endp(lsp->pdu) >= 12)
+ stream_putw_at(lsp->pdu, 10, lsp->hdr.rem_lifetime);
+}
+
+void lspid_print(uint8_t *lsp_id, char *dest, size_t dest_len, char dynhost,
+ char frag, struct isis *isis)
+{
+ struct isis_dynhn *dyn = NULL;
+ char id[SYSID_STRLEN];
+
+ if (dynhost)
+ dyn = dynhn_find_by_id(isis, lsp_id);
+ else
+ dyn = NULL;
+
+ if (dyn)
+ snprintf(id, sizeof(id), "%.14s", dyn->hostname);
+ else if (!memcmp(isis->sysid, lsp_id, ISIS_SYS_ID_LEN) && dynhost)
+ snprintf(id, sizeof(id), "%.14s", cmd_hostname_get());
+ else
+ snprintf(id, sizeof(id), "%pSY", lsp_id);
+
+ if (frag)
+ snprintf(dest, dest_len, "%s.%02x-%02x", id,
+ LSP_PSEUDO_ID(lsp_id), LSP_FRAGMENT(lsp_id));
+ else
+ snprintf(dest, dest_len, "%s.%02x", id, LSP_PSEUDO_ID(lsp_id));
+}
+
+/* Convert the lsp attribute bits to attribute string */
+static const char *lsp_bits2string(uint8_t lsp_bits, char *buf, size_t buf_size)
+{
+ char *pos = buf;
+
+ if (!lsp_bits)
+ return " none";
+
+ if (buf_size < 2 * 3)
+ return " error";
+
+ /* we only focus on the default metric */
+ pos += snprintf(pos, buf_size, "%d/",
+ ISIS_MASK_LSP_ATT_BITS(lsp_bits) ? 1 : 0);
+
+ pos += snprintf(pos, buf_size, "%d/",
+ ISIS_MASK_LSP_PARTITION_BIT(lsp_bits) ? 1 : 0);
+
+ snprintf(pos, buf_size, "%d", ISIS_MASK_LSP_OL_BIT(lsp_bits) ? 1 : 0);
+
+ return buf;
+}
+
+/* this function prints the lsp on show isis database */
+void lsp_print_common(struct isis_lsp *lsp, struct vty *vty, struct json_object *json,
+ char dynhost, struct isis *isis)
+{
+ if (json) {
+ return lsp_print_json(lsp, json, dynhost, isis);
+ } else {
+ return lsp_print_vty(lsp, vty, dynhost, isis);
+ }
+}
+
+void lsp_print_json(struct isis_lsp *lsp, struct json_object *json,
+ char dynhost, struct isis *isis)
+{
+ char LSPid[255];
+ char age_out[8];
+ char b[200];
+ json_object *own_json;
+ char buf[256];
+
+ lspid_print(lsp->hdr.lsp_id, LSPid, sizeof(LSPid), dynhost, 1, isis);
+ own_json = json_object_new_object();
+ json_object_object_add(json, "lsp", own_json);
+ json_object_string_add(own_json, "id", LSPid);
+ json_object_string_add(own_json, "own", lsp->own_lsp ? "*" : " ");
+ json_object_int_add(json, "pdu-len", lsp->hdr.pdu_len);
+ snprintfrr(buf, sizeof(buf), "0x%08x", lsp->hdr.seqno);
+ json_object_string_add(json, "seq-number", buf);
+ snprintfrr(buf, sizeof(buf), "0x%04hx", lsp->hdr.checksum);
+ json_object_string_add(json, "chksum", buf);
+ if (lsp->hdr.rem_lifetime == 0) {
+ snprintf(age_out, sizeof(age_out), "(%d)", lsp->age_out);
+ age_out[7] = '\0';
+ json_object_string_add(json, "holdtime", age_out);
+ } else {
+ json_object_int_add(json, "holdtime", lsp->hdr.rem_lifetime);
+ }
+ json_object_string_add(
+ json, "att-p-ol", lsp_bits2string(lsp->hdr.lsp_bits, b, sizeof(b)));
+}
+
+void lsp_print_vty(struct isis_lsp *lsp, struct vty *vty,
+ char dynhost, struct isis *isis)
+{
+ char LSPid[255];
+ char age_out[8];
+ char b[200];
+
+ lspid_print(lsp->hdr.lsp_id, LSPid, sizeof(LSPid), dynhost, 1, isis);
+ vty_out(vty, "%-21s%c ", LSPid, lsp->own_lsp ? '*' : ' ');
+ vty_out(vty, "%5hu ", lsp->hdr.pdu_len);
+ vty_out(vty, "0x%08x ", lsp->hdr.seqno);
+ vty_out(vty, "0x%04hx ", lsp->hdr.checksum);
+ if (lsp->hdr.rem_lifetime == 0) {
+ snprintf(age_out, sizeof(age_out), "(%d)", lsp->age_out);
+ age_out[7] = '\0';
+ vty_out(vty, "%7s ", age_out);
+ } else
+ vty_out(vty, " %5hu ", lsp->hdr.rem_lifetime);
+ vty_out(vty, "%s\n", lsp_bits2string(lsp->hdr.lsp_bits, b, sizeof(b)));
+}
+
+void lsp_print_detail(struct isis_lsp *lsp, struct vty *vty,
+ struct json_object *json, char dynhost,
+ struct isis *isis)
+{
+ if (json) {
+ lsp_print_json(lsp, json, dynhost, isis);
+ if (lsp->tlvs) {
+ isis_format_tlvs(lsp->tlvs, json);
+ }
+ } else {
+ lsp_print_vty(lsp, vty, dynhost, isis);
+ if (lsp->tlvs)
+ vty_multiline(vty, " ", "%s",
+ isis_format_tlvs(lsp->tlvs, NULL));
+ vty_out(vty, "\n");
+ }
+}
+
+/* print all the lsps info in the local lspdb */
+int lsp_print_all(struct vty *vty, struct json_object *json,
+ struct lspdb_head *head, char detail, char dynhost,
+ struct isis *isis)
+{
+ struct isis_lsp *lsp;
+ int lsp_count = 0;
+
+ if (detail == ISIS_UI_LEVEL_BRIEF) {
+ frr_each (lspdb, head, lsp) {
+ lsp_print_common(lsp, vty, json, dynhost, isis);
+ lsp_count++;
+ }
+ } else if (detail == ISIS_UI_LEVEL_DETAIL) {
+ frr_each (lspdb, head, lsp) {
+ lsp_print_detail(lsp, vty, json, dynhost, isis);
+ lsp_count++;
+ }
+ }
+
+ return lsp_count;
+}
+
+static uint16_t lsp_rem_lifetime(struct isis_area *area, int level)
+{
+ uint16_t rem_lifetime;
+
+ /* Add jitter to configured LSP lifetime */
+ rem_lifetime =
+ isis_jitter(area->max_lsp_lifetime[level - 1], MAX_AGE_JITTER);
+
+ /* No jitter if the max refresh will be less than configure gen interval
+ */
+ /* N.B. this calucation is acceptable since rem_lifetime is in
+ * [332,65535] at
+ * this point */
+ if (area->lsp_gen_interval[level - 1] > (rem_lifetime - 300))
+ rem_lifetime = area->max_lsp_lifetime[level - 1];
+
+ return rem_lifetime;
+}
+
+static uint16_t lsp_refresh_time(struct isis_lsp *lsp, uint16_t rem_lifetime)
+{
+ struct isis_area *area = lsp->area;
+ int level = lsp->level;
+ uint16_t refresh_time;
+
+ /* Add jitter to LSP refresh time */
+ refresh_time =
+ isis_jitter(area->lsp_refresh[level - 1], MAX_LSP_GEN_JITTER);
+
+ /* RFC 4444 : make sure the refresh time is at least less than 300
+ * of the remaining lifetime and more than gen interval */
+ if (refresh_time <= area->lsp_gen_interval[level - 1]
+ || refresh_time > (rem_lifetime - 300))
+ refresh_time = rem_lifetime - 300;
+
+ /* In cornercases, refresh_time might be <= lsp_gen_interval, however
+ * we accept this violation to satisfy refresh_time <= rem_lifetime -
+ * 300 */
+
+ return refresh_time;
+}
+
+static void lsp_build_internal_reach_ipv4(struct isis_lsp *lsp,
+ struct isis_area *area,
+ struct prefix_ipv4 *ipv4,
+ uint32_t metric)
+{
+ struct sr_prefix_cfg *pcfgs[SR_ALGORITHM_COUNT] = {NULL};
+
+ if (area->oldmetric) {
+ lsp_debug(
+ "ISIS (%s): Adding old-style IP reachability for %pFX",
+ area->area_tag, ipv4);
+ isis_tlvs_add_oldstyle_ip_reach(lsp->tlvs, ipv4, metric);
+ }
+
+ if (area->newmetric) {
+ lsp_debug("ISIS (%s): Adding te-style IP reachability for %pFX",
+ area->area_tag, ipv4);
+
+ if (area->srdb.enabled)
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+#ifndef FABRICD
+ if (flex_algo_id_valid(i) &&
+ !isis_flex_algo_elected_supported(i, area))
+ continue;
+#endif /* ifndef FABRICD */
+ pcfgs[i] =
+ isis_sr_cfg_prefix_find(area, ipv4, i);
+ }
+
+ isis_tlvs_add_extended_ip_reach(lsp->tlvs, ipv4, metric, false,
+ pcfgs);
+ }
+}
+
+static void lsp_build_internal_reach_ipv6(struct isis_lsp *lsp,
+ struct isis_area *area,
+ struct prefix_ipv6 *ipv6,
+ uint32_t metric)
+{
+ struct sr_prefix_cfg *pcfgs[SR_ALGORITHM_COUNT] = {NULL};
+
+ lsp_debug("ISIS (%s): Adding IPv6 reachability for %pFX",
+ area->area_tag, ipv6);
+
+ if (area->srdb.enabled)
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+#ifndef FABRICD
+ if (flex_algo_id_valid(i) &&
+ !isis_flex_algo_elected_supported(i, area))
+ continue;
+#endif /* ifndef FABRICD */
+ pcfgs[i] = isis_sr_cfg_prefix_find(area, ipv6, i);
+ }
+
+ isis_tlvs_add_ipv6_reach(lsp->tlvs, isis_area_ipv6_topology(area), ipv6,
+ metric, false, pcfgs);
+}
+
+
+static void lsp_build_ext_reach_ipv4(struct isis_lsp *lsp,
+ struct isis_area *area)
+{
+ struct route_table *er_table = get_ext_reach(area, AF_INET, lsp->level);
+ if (!er_table)
+ return;
+
+ for (struct route_node *rn = route_top(er_table); rn;
+ rn = route_next(rn)) {
+ if (!rn->info)
+ continue;
+
+ struct prefix_ipv4 *ipv4 = (struct prefix_ipv4 *)&rn->p;
+ struct isis_ext_info *info = rn->info;
+
+ uint32_t metric = info->metric;
+ if (metric > MAX_WIDE_PATH_METRIC)
+ metric = MAX_WIDE_PATH_METRIC;
+ if (area->oldmetric && metric > 0x3f)
+ metric = 0x3f;
+
+ if (area->oldmetric)
+ isis_tlvs_add_oldstyle_ip_reach(lsp->tlvs, ipv4,
+ metric);
+ if (area->newmetric) {
+ struct sr_prefix_cfg *pcfgs[SR_ALGORITHM_COUNT] = {
+ NULL};
+
+ if (area->srdb.enabled)
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+#ifndef FABRICD
+ if (flex_algo_id_valid(i) &&
+ !isis_flex_algo_elected_supported(
+ i, area))
+ continue;
+#endif /* ifndef FABRICD */
+ pcfgs[i] = isis_sr_cfg_prefix_find(
+ area, ipv4, i);
+ }
+
+ isis_tlvs_add_extended_ip_reach(lsp->tlvs, ipv4, metric,
+ true, pcfgs);
+ }
+ }
+}
+
+static void lsp_build_ext_reach_ipv6(struct isis_lsp *lsp,
+ struct isis_area *area)
+{
+ struct route_table *er_table =
+ get_ext_reach(area, AF_INET6, lsp->level);
+ if (!er_table)
+ return;
+
+ for (struct route_node *rn = route_top(er_table); rn;
+ rn = srcdest_route_next(rn)) {
+ if (!rn->info)
+ continue;
+ struct isis_ext_info *info = rn->info;
+ struct prefix_ipv6 *p, *src_p;
+
+ srcdest_rnode_prefixes(rn, (const struct prefix **)&p,
+ (const struct prefix **)&src_p);
+
+ uint32_t metric = info->metric;
+ if (info->metric > MAX_WIDE_PATH_METRIC)
+ metric = MAX_WIDE_PATH_METRIC;
+
+ if (!src_p || !src_p->prefixlen) {
+ struct sr_prefix_cfg *pcfgs[SR_ALGORITHM_COUNT] = {
+ NULL};
+
+ if (area->srdb.enabled)
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+#ifndef FABRICD
+ if (flex_algo_id_valid(i) &&
+ !isis_flex_algo_elected_supported(
+ i, area))
+ continue;
+#endif /* ifndef FABRICD */
+ pcfgs[i] = isis_sr_cfg_prefix_find(
+ area, p, i);
+ }
+
+ isis_tlvs_add_ipv6_reach(lsp->tlvs,
+ isis_area_ipv6_topology(area),
+ p, metric, true, pcfgs);
+ } else if (isis_area_ipv6_dstsrc_enabled(area)) {
+ isis_tlvs_add_ipv6_dstsrc_reach(lsp->tlvs,
+ ISIS_MT_IPV6_DSTSRC,
+ p, src_p, metric);
+ }
+ }
+}
+
+static void lsp_build_ext_reach(struct isis_lsp *lsp, struct isis_area *area)
+{
+ lsp_build_ext_reach_ipv4(lsp, area);
+ lsp_build_ext_reach_ipv6(lsp, area);
+}
+
+static struct isis_lsp *lsp_next_frag(uint8_t frag_num, struct isis_lsp *lsp0,
+ struct isis_area *area, int level)
+{
+ struct isis_lsp *lsp;
+ uint8_t frag_id[ISIS_SYS_ID_LEN + 2];
+
+ memcpy(frag_id, lsp0->hdr.lsp_id, ISIS_SYS_ID_LEN + 1);
+ LSP_FRAGMENT(frag_id) = frag_num;
+
+ lsp = lsp_search(&area->lspdb[level - 1], frag_id);
+ if (lsp) {
+ lsp_clear_data(lsp);
+ if (!lsp->lspu.zero_lsp)
+ lsp_link_fragment(lsp, lsp0);
+ return lsp;
+ }
+
+ lsp = lsp_new(area, frag_id, lsp0->hdr.rem_lifetime, 0,
+ lsp_bits_generate(level, area->overload_bit,
+ area->attached_bit_send, area),
+ 0, lsp0, level);
+ lsp->own_lsp = 1;
+ lsp_insert(&area->lspdb[level - 1], lsp);
+ return lsp;
+}
+
+/*
+ * Builds the LSP data part. This func creates a new frag whenever
+ * area->lsp_frag_threshold is exceeded.
+ */
+static void lsp_build(struct isis_lsp *lsp, struct isis_area *area)
+{
+ int level = lsp->level;
+ struct listnode *node;
+ struct isis_lsp *frag;
+
+ lsp_clear_data(lsp);
+ for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag))
+ lsp_clear_data(frag);
+
+ lsp->tlvs = isis_alloc_tlvs();
+ lsp_debug("ISIS (%s): Constructing local system LSP for level %d",
+ area->area_tag, level);
+
+ lsp->hdr.lsp_bits = lsp_bits_generate(level, area->overload_bit,
+ area->attached_bit_send, area);
+
+ lsp_add_auth(lsp);
+
+ isis_tlvs_add_area_addresses(lsp->tlvs, area->area_addrs);
+
+ /* Protocols Supported */
+ if (area->ip_circuits > 0 || area->ipv6_circuits > 0) {
+ struct nlpids nlpids = {.count = 0};
+
+ if (area->ip_circuits > 0) {
+ lsp_debug(
+ "ISIS (%s): Found IPv4 circuit, adding IPv4 to NLPIDs",
+ area->area_tag);
+ nlpids.nlpids[nlpids.count] = NLPID_IP;
+ nlpids.count++;
+ }
+ if (area->ipv6_circuits > 0) {
+ lsp_debug(
+ "ISIS (%s): Found IPv6 circuit, adding IPv6 to NLPIDs",
+ area->area_tag);
+ nlpids.nlpids[nlpids.count] = NLPID_IPV6;
+ nlpids.count++;
+ }
+ isis_tlvs_set_protocols_supported(lsp->tlvs, &nlpids);
+ }
+
+ if (area_is_mt(area)) {
+ lsp_debug("ISIS (%s): Adding MT router tlv...", area->area_tag);
+
+ struct isis_area_mt_setting **mt_settings;
+ unsigned int mt_count;
+
+ mt_settings = area_mt_settings(area, &mt_count);
+ for (unsigned int i = 0; i < mt_count; i++) {
+ isis_tlvs_add_mt_router_info(
+ lsp->tlvs, mt_settings[i]->mtid,
+ mt_settings[i]->overload, false);
+ lsp_debug("ISIS (%s): MT %s", area->area_tag,
+ isis_mtid2str(mt_settings[i]->mtid));
+ }
+ } else {
+ lsp_debug("ISIS (%s): Not adding MT router tlv (disabled)",
+ area->area_tag);
+ }
+ /* Dynamic Hostname */
+ if (area->dynhostname) {
+ isis_tlvs_set_dynamic_hostname(lsp->tlvs, cmd_hostname_get());
+ lsp_debug("ISIS (%s): Adding dynamic hostname '%s'",
+ area->area_tag, cmd_hostname_get());
+ } else {
+ lsp_debug("ISIS (%s): Not adding dynamic hostname (disabled)",
+ area->area_tag);
+ }
+
+ /* Add Router Capability TLV. */
+ if (area->isis->router_id != 0) {
+ struct isis_router_cap *rcap;
+#ifndef FABRICD
+ struct isis_router_cap_fad *rcap_fad;
+ struct listnode *node;
+ struct flex_algo *fa;
+#endif /* ifndef FABRICD */
+
+ rcap = isis_tlvs_init_router_capability(lsp->tlvs);
+
+ rcap->router_id.s_addr = area->isis->router_id;
+
+#ifndef FABRICD
+ /* Set flex-algo definitions */
+ for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node,
+ fa)) {
+ if (!fa->advertise_definition)
+ continue;
+ lsp_debug("ISIS (%s): Flex-Algo Definition %u",
+ area->area_tag, fa->algorithm);
+ isis_tlvs_set_router_capability_fad(lsp->tlvs, fa,
+ fa->algorithm,
+ area->isis->sysid);
+ }
+#endif /* ifndef FABRICD */
+
+ /* Add SR Sub-TLVs if SR is enabled. */
+ if (area->srdb.enabled) {
+ struct isis_sr_db *srdb = &area->srdb;
+ uint32_t range_size;
+
+ /* SRGB first */
+ range_size = srdb->config.srgb_upper_bound
+ - srdb->config.srgb_lower_bound + 1;
+ rcap->srgb.flags = ISIS_SUBTLV_SRGB_FLAG_I |
+ ISIS_SUBTLV_SRGB_FLAG_V;
+ rcap->srgb.range_size = range_size;
+ rcap->srgb.lower_bound = srdb->config.srgb_lower_bound;
+ /* Then Algorithm */
+ rcap->algo[0] = SR_ALGORITHM_SPF;
+ rcap->algo[1] = SR_ALGORITHM_UNSET;
+#ifndef FABRICD
+ for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos,
+ node, fa)) {
+ if (fa->advertise_definition)
+ rcap_fad = rcap->fads[fa->algorithm];
+ else
+ rcap_fad = NULL;
+
+ if (!isis_flex_algo_elected_supported_local_fad(
+ fa->algorithm, area, &rcap_fad)) {
+ fa->state = false;
+ continue;
+ }
+ fa->state = true;
+ lsp_debug("ISIS (%s): SR Algorithm %u",
+ area->area_tag, fa->algorithm);
+ rcap->algo[fa->algorithm] = fa->algorithm;
+ }
+#endif /* ifndef FABRICD */
+ /* SRLB */
+ rcap->srlb.flags = 0;
+ range_size = srdb->config.srlb_upper_bound
+ - srdb->config.srlb_lower_bound + 1;
+ rcap->srlb.range_size = range_size;
+ rcap->srlb.lower_bound = srdb->config.srlb_lower_bound;
+ /* And finally MSD */
+ rcap->msd = srdb->config.msd;
+ }
+
+ /* Add SRv6 Sub-TLVs if SRv6 is enabled */
+ if (area->srv6db.config.enabled) {
+ struct isis_srv6_db *srv6db = &area->srv6db;
+
+ rcap->srv6_cap.is_srv6_capable = true;
+
+ /* SRv6 flags */
+ rcap->srv6_cap.flags = 0;
+
+ /* And finally MSDs */
+ rcap->srv6_msd.max_seg_left_msd =
+ srv6db->config.max_seg_left_msd;
+ rcap->srv6_msd.max_end_pop_msd =
+ srv6db->config.max_end_pop_msd;
+ rcap->srv6_msd.max_h_encaps_msd =
+ srv6db->config.max_h_encaps_msd;
+ rcap->srv6_msd.max_end_d_msd =
+ srv6db->config.max_end_d_msd;
+ } else {
+ rcap->srv6_cap.is_srv6_capable = false;
+ }
+ }
+
+ /* Add SRv6 Locator TLV. */
+ if (area->srv6db.config.enabled &&
+ !list_isempty(area->srv6db.srv6_locator_chunks)) {
+ struct isis_srv6_locator locator = {};
+ struct srv6_locator_chunk *chunk;
+
+ /* TODO: support more than one locator */
+ chunk = (struct srv6_locator_chunk *)listgetdata(
+ listhead(area->srv6db.srv6_locator_chunks));
+
+ locator.metric = 0;
+ locator.prefix = chunk->prefix;
+ locator.flags = 0;
+ locator.algorithm = 0;
+
+ struct listnode *sid_node;
+ struct isis_srv6_sid *sid;
+ locator.srv6_sid = list_new();
+ for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, sid_node,
+ sid)) {
+ listnode_add(locator.srv6_sid, sid);
+ }
+
+ isis_tlvs_add_srv6_locator(lsp->tlvs, 0, &locator);
+ lsp_debug("ISIS (%s): Adding SRv6 Locator information",
+ area->area_tag);
+
+ list_delete(&locator.srv6_sid);
+
+ isis_tlvs_add_ipv6_reach(lsp->tlvs,
+ isis_area_ipv6_topology(area),
+ &chunk->prefix, 0, false, NULL);
+ }
+
+ /* IPv4 address and TE router ID TLVs.
+ * In case of the first one we don't follow "C" vendor,
+ * but "J" vendor behavior - one IPv4 address is put
+ * into LSP. TE router ID will be the same if MPLS-TE
+ * is not activate or MPLS-TE router-id not specified
+ */
+ if (area->isis->router_id != 0) {
+ struct in_addr id = {.s_addr = area->isis->router_id};
+ lsp_debug("ISIS (%s): Adding router ID %pI4 as IPv4 tlv.",
+ area->area_tag, &id);
+ isis_tlvs_add_ipv4_address(lsp->tlvs, &id);
+
+ /* If new style TLV's are in use, add TE router ID TLV
+ * Check if MPLS-TE is activate and mpls-te router-id set
+ * otherwise add exactly same data as for IPv4 address
+ */
+ if (area->newmetric) {
+ if (IS_MPLS_TE(area->mta)
+ && area->mta->router_id.s_addr != INADDR_ANY)
+ id.s_addr = area->mta->router_id.s_addr;
+ lsp_debug(
+ "ISIS (%s): Adding router ID also as TE router ID tlv.",
+ area->area_tag);
+ isis_tlvs_set_te_router_id(lsp->tlvs, &id);
+ }
+ } else {
+ lsp_debug("ISIS (%s): Router ID is unset. Not adding tlv.",
+ area->area_tag);
+ }
+
+ if (IS_MPLS_TE(area->mta)
+ && !IN6_IS_ADDR_UNSPECIFIED(&area->mta->router_id_ipv6)) {
+ lsp_debug("ISIS (%s): Adding IPv6 TE Router ID tlv.",
+ area->area_tag);
+ isis_tlvs_set_te_router_id_ipv6(lsp->tlvs,
+ &area->mta->router_id_ipv6);
+ }
+
+ lsp_debug("ISIS (%s): Adding circuit specific information.",
+ area->area_tag);
+
+ if (fabricd) {
+ lsp_debug(
+ "ISIS (%s): Adding tier %hhu spine-leaf-extension tlv.",
+ area->area_tag, fabricd_tier(area));
+ isis_tlvs_add_spine_leaf(lsp->tlvs, fabricd_tier(area), true,
+ false, false, false);
+ }
+
+ struct isis_circuit *circuit;
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
+ if (!circuit->interface)
+ lsp_debug(
+ "ISIS (%s): Processing %s circuit %p with unknown interface",
+ area->area_tag,
+ circuit_type2string(circuit->circ_type),
+ circuit);
+ else
+ lsp_debug("ISIS (%s): Processing %s circuit %s",
+ area->area_tag,
+ circuit_type2string(circuit->circ_type),
+ circuit->interface->name);
+
+ if (circuit->state != C_STATE_UP) {
+ lsp_debug("ISIS (%s): Circuit is not up, ignoring.",
+ area->area_tag);
+ continue;
+ }
+
+ if (area->advertise_passive_only && !circuit->is_passive) {
+ lsp_debug(
+ "ISIS (%s): Circuit is not passive, ignoring.",
+ area->area_tag);
+ continue;
+ }
+
+ uint32_t metric = area->oldmetric
+ ? circuit->metric[level - 1]
+ : circuit->te_metric[level - 1];
+
+ if (circuit->ip_router && circuit->ip_addrs->count > 0) {
+ lsp_debug(
+ "ISIS (%s): Circuit has IPv4 active, adding respective TLVs.",
+ area->area_tag);
+ struct listnode *ipnode;
+ struct prefix_ipv4 *ipv4;
+ for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, ipnode,
+ ipv4))
+ lsp_build_internal_reach_ipv4(lsp, area, ipv4,
+ metric);
+ }
+
+ if (circuit->ipv6_router && circuit->ipv6_non_link->count > 0) {
+ struct listnode *ipnode;
+ struct prefix_ipv6 *ipv6;
+
+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link,
+ ipnode, ipv6))
+ lsp_build_internal_reach_ipv6(lsp, area, ipv6,
+ metric);
+ }
+
+ switch (circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ if (level & circuit->is_type) {
+ uint8_t *ne_id =
+ (level == IS_LEVEL_1)
+ ? circuit->u.bc.l1_desig_is
+ : circuit->u.bc.l2_desig_is;
+
+ if (LSP_PSEUDO_ID(ne_id)) {
+ if (area->oldmetric) {
+ lsp_debug(
+ "ISIS (%s): Adding DIS %pPN as old-style neighbor",
+ area->area_tag, ne_id);
+ isis_tlvs_add_oldstyle_reach(
+ lsp->tlvs, ne_id,
+ metric);
+ }
+ if (area->newmetric)
+ tlvs_add_mt_bcast(
+ lsp->tlvs, circuit,
+ level, ne_id, metric);
+ }
+ } else {
+ lsp_debug(
+ "ISIS (%s): Circuit is not active for current level. Not adding IS neighbors",
+ area->area_tag);
+ }
+ break;
+ case CIRCUIT_T_P2P: {
+ struct isis_adjacency *nei = circuit->u.p2p.neighbor;
+ if (nei && nei->adj_state == ISIS_ADJ_UP
+ && (level & nei->circuit_t)) {
+ uint8_t ne_id[7];
+ memcpy(ne_id, nei->sysid, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(ne_id) = 0;
+
+ if (area->oldmetric) {
+ lsp_debug(
+ "ISIS (%s): Adding old-style is reach for %pSY",
+ area->area_tag, ne_id);
+ isis_tlvs_add_oldstyle_reach(
+ lsp->tlvs, ne_id, metric);
+ }
+ if (area->newmetric) {
+ uint32_t neighbor_metric;
+ if (fabricd_tier(area) == 0) {
+ neighbor_metric = 0xffe;
+ } else {
+ neighbor_metric = metric;
+ }
+
+ tlvs_add_mt_p2p(lsp->tlvs, circuit,
+ ne_id, neighbor_metric);
+ }
+ } else {
+ lsp_debug(
+ "ISIS (%s): No adjacency for given level on this circuit. Not adding IS neighbors",
+ area->area_tag);
+ }
+ } break;
+ case CIRCUIT_T_LOOPBACK:
+ break;
+ default:
+ zlog_warn("lsp_area_create: unknown circuit type");
+ }
+ }
+
+ lsp_build_ext_reach(lsp, area);
+
+ struct isis_tlvs *tlvs = lsp->tlvs;
+ lsp->tlvs = NULL;
+
+ lsp_adjust_stream(lsp);
+ lsp_pack_pdu(lsp);
+ size_t tlv_space = STREAM_WRITEABLE(lsp->pdu) - LLC_LEN;
+ lsp_clear_data(lsp);
+
+ struct list *fragments = isis_fragment_tlvs(tlvs, tlv_space);
+ if (!fragments) {
+ zlog_warn("BUG: could not fragment own LSP:");
+ log_multiline(LOG_WARNING, " ", "%s",
+ isis_format_tlvs(tlvs, NULL));
+ isis_free_tlvs(tlvs);
+ return;
+ }
+ isis_free_tlvs(tlvs);
+
+ bool fragment_overflow = false;
+ frag = lsp;
+ for (ALL_LIST_ELEMENTS_RO(fragments, node, tlvs)) {
+ if (node != listhead(fragments)) {
+ if (LSP_FRAGMENT(frag->hdr.lsp_id) == 255) {
+ if (!fragment_overflow) {
+ fragment_overflow = true;
+ zlog_warn(
+ "ISIS (%s): Too much information for 256 fragments",
+ area->area_tag);
+ }
+ isis_free_tlvs(tlvs);
+ continue;
+ }
+
+ frag = lsp_next_frag(LSP_FRAGMENT(frag->hdr.lsp_id) + 1,
+ lsp, area, level);
+ lsp_adjust_stream(frag);
+ }
+ frag->tlvs = tlvs;
+ }
+
+ list_delete(&fragments);
+ lsp_debug("ISIS (%s): LSP construction is complete. Serializing...",
+ area->area_tag);
+ return;
+}
+
+/*
+ * 7.3.7 and 7.3.9 Generation on non-pseudonode LSPs
+ */
+int lsp_generate(struct isis_area *area, int level)
+{
+ struct isis_lsp *oldlsp, *newlsp;
+ uint32_t seq_num = 0;
+ uint8_t lspid[ISIS_SYS_ID_LEN + 2];
+ uint16_t rem_lifetime, refresh_time;
+ uint32_t overload_time;
+
+ if ((area == NULL) || (area->is_type & level) != level)
+ return ISIS_ERROR;
+
+ /* Check if config is still being processed */
+ if (event_is_scheduled(t_isis_cfg))
+ return ISIS_OK;
+
+ memset(&lspid, 0, ISIS_SYS_ID_LEN + 2);
+
+ memcpy(&lspid, area->isis->sysid, ISIS_SYS_ID_LEN);
+
+ /* Check if device should be overloaded on startup */
+ if (device_startup) {
+ overload_time = isis_restart_read_overload_time(area);
+ if (overload_time > 0) {
+ isis_area_overload_bit_set(area, true);
+ event_add_timer(master, set_overload_on_start_timer,
+ area, overload_time,
+ &area->t_overload_on_startup_timer);
+ }
+ device_startup = false;
+ }
+
+ /* only builds the lsp if the area shares the level */
+ oldlsp = lsp_search(&area->lspdb[level - 1], lspid);
+ if (oldlsp) {
+ /* FIXME: we should actually initiate a purge */
+ seq_num = oldlsp->hdr.seqno;
+ lsp_search_and_destroy(&area->lspdb[level - 1],
+ oldlsp->hdr.lsp_id);
+ }
+ rem_lifetime = lsp_rem_lifetime(area, level);
+ newlsp = lsp_new(area, lspid, rem_lifetime, seq_num,
+ lsp_bits_generate(area->is_type, area->overload_bit,
+ area->attached_bit_send, area),
+ 0, NULL, level);
+ newlsp->area = area;
+ newlsp->own_lsp = 1;
+
+ lsp_insert(&area->lspdb[level - 1], newlsp);
+ /* build_lsp_data (newlsp, area); */
+ lsp_build(newlsp, area);
+ /* time to calculate our checksum */
+ lsp_seqno_update(newlsp);
+ newlsp->last_generated = time(NULL);
+ lsp_flood(newlsp, NULL);
+ area->lsp_gen_count[level - 1]++;
+
+ refresh_time = lsp_refresh_time(newlsp, rem_lifetime);
+
+ EVENT_OFF(area->t_lsp_refresh[level - 1]);
+ area->lsp_regenerate_pending[level - 1] = 0;
+ event_add_timer(master, lsp_refresh, &area->lsp_refresh_arg[level - 1],
+ refresh_time, &area->t_lsp_refresh[level - 1]);
+
+ if (IS_DEBUG_UPDATE_PACKETS) {
+ zlog_debug(
+ "ISIS-Upd (%s): Building L%d LSP %pLS, len %hu, seq 0x%08x, cksum 0x%04hx, lifetime %hus refresh %hus",
+ area->area_tag, level, newlsp->hdr.lsp_id,
+ newlsp->hdr.pdu_len, newlsp->hdr.seqno,
+ newlsp->hdr.checksum, newlsp->hdr.rem_lifetime,
+ refresh_time);
+ }
+ sched_debug(
+ "ISIS (%s): Built L%d LSP. Set triggered regenerate to non-pending.",
+ area->area_tag, level);
+
+#ifndef FABRICD
+ /* send northbound notification */
+ isis_notif_lsp_gen(area, newlsp->hdr.lsp_id, newlsp->hdr.seqno,
+ newlsp->last_generated);
+#endif /* ifndef FABRICD */
+
+ return ISIS_OK;
+}
+
+/*
+ * Search own LSPs, update holding time and flood
+ */
+static int lsp_regenerate(struct isis_area *area, int level)
+{
+ struct lspdb_head *head;
+ struct isis_lsp *lsp, *frag;
+ struct listnode *node;
+ uint8_t lspid[ISIS_SYS_ID_LEN + 2];
+ uint16_t rem_lifetime, refresh_time;
+
+ if ((area == NULL) || (area->is_type & level) != level)
+ return ISIS_ERROR;
+
+ head = &area->lspdb[level - 1];
+ memset(lspid, 0, ISIS_SYS_ID_LEN + 2);
+ memcpy(lspid, area->isis->sysid, ISIS_SYS_ID_LEN);
+
+ lsp = lsp_search(head, lspid);
+
+ if (!lsp) {
+ flog_err(EC_LIB_DEVELOPMENT,
+ "ISIS-Upd (%s): lsp_regenerate: no L%d LSP found!",
+ area->area_tag, level);
+ return ISIS_ERROR;
+ }
+
+ lsp_clear_data(lsp);
+ lsp_build(lsp, area);
+ rem_lifetime = lsp_rem_lifetime(area, level);
+ lsp->hdr.rem_lifetime = rem_lifetime;
+ lsp->last_generated = time(NULL);
+ lsp_flood(lsp, NULL);
+ area->lsp_gen_count[level - 1]++;
+ for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) {
+ if (!frag->tlvs) {
+ /* Updating and flooding should only affect fragments
+ * carrying data
+ */
+ continue;
+ }
+
+ frag->hdr.lsp_bits =
+ lsp_bits_generate(level, area->overload_bit,
+ area->attached_bit_send, area);
+ /* Set the lifetime values of all the fragments to the same
+ * value,
+ * so that no fragment expires before the lsp is refreshed.
+ */
+ frag->hdr.rem_lifetime = rem_lifetime;
+ frag->age_out = ZERO_AGE_LIFETIME;
+ lsp_flood(frag, NULL);
+ }
+ lsp_seqno_update(lsp);
+
+ refresh_time = lsp_refresh_time(lsp, rem_lifetime);
+ event_add_timer(master, lsp_refresh, &area->lsp_refresh_arg[level - 1],
+ refresh_time, &area->t_lsp_refresh[level - 1]);
+ area->lsp_regenerate_pending[level - 1] = 0;
+
+ if (IS_DEBUG_UPDATE_PACKETS) {
+ zlog_debug(
+ "ISIS-Upd (%s): Refreshed our L%d LSP %pLS, len %hu, seq 0x%08x, cksum 0x%04hx, lifetime %hus refresh %hus",
+ area->area_tag, level, lsp->hdr.lsp_id,
+ lsp->hdr.pdu_len, lsp->hdr.seqno, lsp->hdr.checksum,
+ lsp->hdr.rem_lifetime, refresh_time);
+ }
+ sched_debug(
+ "ISIS (%s): Rebuilt L%d LSP. Set triggered regenerate to non-pending.",
+ area->area_tag, level);
+
+ return ISIS_OK;
+}
+
+/*
+ * Something has changed or periodic refresh -> regenerate LSP
+ */
+static void lsp_refresh(struct event *thread)
+{
+ struct lsp_refresh_arg *arg = EVENT_ARG(thread);
+
+ assert(arg);
+
+ struct isis_area *area = arg->area;
+
+ assert(area);
+
+ int level = arg->level;
+
+ area->t_lsp_refresh[level - 1] = NULL;
+ area->lsp_regenerate_pending[level - 1] = 0;
+
+ if ((area->is_type & level) == 0)
+ return;
+
+ /*
+ * Throttle regeneration of LSPs (but not when BFD signalled a 'down'
+ * message)
+ */
+ if (monotime_since(&area->last_lsp_refresh_event[level - 1], NULL)
+ < 100000L
+ && !(area->bfd_force_spf_refresh)) {
+ sched_debug("ISIS (%s): Still unstable, postpone LSP L%d refresh",
+ area->area_tag, level);
+ _lsp_regenerate_schedule(area, level, 0, false,
+ __func__, __FILE__, __LINE__);
+ return;
+ }
+
+ sched_debug(
+ "ISIS (%s): LSP L%d refresh timer expired. Refreshing LSP...",
+ area->area_tag, level);
+ lsp_regenerate(area, level);
+}
+
+int _lsp_regenerate_schedule(struct isis_area *area, int level,
+ int all_pseudo, bool postpone,
+ const char *func, const char *file,
+ int line)
+{
+ struct isis_lsp *lsp;
+ uint8_t id[ISIS_SYS_ID_LEN + 2];
+ time_t now, diff;
+ long timeout;
+ struct listnode *cnode;
+ struct isis_circuit *circuit;
+ int lvl;
+
+ if (area == NULL)
+ return ISIS_ERROR;
+
+ sched_debug(
+ "ISIS (%s): Scheduling regeneration of %s LSPs, %sincluding PSNs Caller: %s %s:%d",
+ area->area_tag, circuit_t2string(level),
+ all_pseudo ? "" : "not ",
+ func, file, line);
+
+ memcpy(id, area->isis->sysid, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(id) = LSP_FRAGMENT(id) = 0;
+ now = time(NULL);
+
+ for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) {
+ if (!((level & lvl) && (area->is_type & lvl)))
+ continue;
+
+ if (postpone) {
+ monotime(&area->last_lsp_refresh_event[lvl - 1]);
+ }
+
+ sched_debug(
+ "ISIS (%s): Checking whether L%d needs to be scheduled",
+ area->area_tag, lvl);
+
+ if (area->lsp_regenerate_pending[lvl - 1]
+ && !(area->bfd_signalled_down)) {
+ /*
+ * Note: in case of a BFD 'down' message the refresh is
+ * scheduled once again just to be sure
+ */
+ struct timeval remain = event_timer_remain(
+ area->t_lsp_refresh[lvl - 1]);
+ sched_debug(
+ "ISIS (%s): Regeneration is already pending, nothing todo. (Due in %lld.%03lld seconds)",
+ area->area_tag, (long long)remain.tv_sec,
+ (long long)remain.tv_usec / 1000);
+ continue;
+ }
+
+ lsp = lsp_search(&area->lspdb[lvl - 1], id);
+ if (!lsp) {
+ sched_debug(
+ "ISIS (%s): We do not have any LSPs to regenerate, nothing todo.",
+ area->area_tag);
+ continue;
+ }
+
+ /*
+ * Throttle avoidance
+ */
+ sched_debug(
+ "ISIS (%s): Will schedule regen timer. Last run was: %lld, Now is: %lld",
+ area->area_tag, (long long)lsp->last_generated,
+ (long long)now);
+ EVENT_OFF(area->t_lsp_refresh[lvl - 1]);
+ diff = now - lsp->last_generated;
+ if (diff < area->lsp_gen_interval[lvl - 1]
+ && !(area->bfd_signalled_down)) {
+ timeout =
+ 1000 * (area->lsp_gen_interval[lvl - 1] - diff);
+ sched_debug(
+ "ISIS (%s): Scheduling in %ld ms to match configured lsp_gen_interval",
+ area->area_tag, timeout);
+ } else {
+ /*
+ * Schedule LSP refresh ASAP
+ */
+ if (area->bfd_signalled_down) {
+ sched_debug(
+ "ISIS (%s): Scheduling immediately due to BFD 'down' message.",
+ area->area_tag);
+ area->bfd_signalled_down = false;
+ area->bfd_force_spf_refresh = true;
+ timeout = 0;
+ } else {
+ int64_t time_since_last = monotime_since(
+ &area->last_lsp_refresh_event[lvl - 1],
+ NULL);
+ timeout = time_since_last < 100000L
+ ? (100000L - time_since_last)/1000
+ : 0;
+ if (timeout > 0)
+ sched_debug(
+ "ISIS (%s): Last generation was more than lsp_gen_interval ago. Scheduling for execution in %ld ms due to the instability timer.",
+ area->area_tag, timeout);
+ else
+ sched_debug(
+ "ISIS (%s): Last generation was more than lsp_gen_interval ago. Scheduling for execution now.",
+ area->area_tag);
+ }
+ }
+
+ area->lsp_regenerate_pending[lvl - 1] = 1;
+ event_add_timer_msec(master, lsp_refresh,
+ &area->lsp_refresh_arg[lvl - 1], timeout,
+ &area->t_lsp_refresh[lvl - 1]);
+ }
+
+ if (all_pseudo) {
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit))
+ lsp_regenerate_schedule_pseudo(circuit, level);
+ }
+
+ return ISIS_OK;
+}
+
+/*
+ * Funcs for pseudonode LSPs
+ */
+
+/*
+ * 7.3.8 and 7.3.10 Generation of level 1 and 2 pseudonode LSPs
+ */
+static void lsp_build_pseudo(struct isis_lsp *lsp, struct isis_circuit *circuit,
+ int level)
+{
+ struct isis_adjacency *adj;
+ struct list *adj_list;
+ struct listnode *node;
+ struct isis_area *area = circuit->area;
+ uint16_t mtid;
+
+ lsp_clear_data(lsp);
+ lsp->tlvs = isis_alloc_tlvs();
+ lsp_debug(
+ "ISIS (%s): Constructing pseudo LSP %pLS for interface %s level %d",
+ area->area_tag, lsp->hdr.lsp_id, circuit->interface->name,
+ level);
+
+ lsp->level = level;
+ /* RFC3787 section 4 SHOULD not set overload bit in pseudo LSPs */
+ lsp->hdr.lsp_bits = lsp_bits_generate(
+ level, 0, circuit->area->attached_bit_send, area);
+
+ /*
+ * add self to IS neighbours
+ */
+ uint8_t ne_id[ISIS_SYS_ID_LEN + 1];
+
+ memcpy(ne_id, area->isis->sysid, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(ne_id) = 0;
+
+ if (circuit->area->oldmetric) {
+ isis_tlvs_add_oldstyle_reach(lsp->tlvs, ne_id, 0);
+ lsp_debug("ISIS (%s): Adding %pPN as old-style neighbor (self)",
+ area->area_tag, ne_id);
+ }
+ if (circuit->area->newmetric) {
+ if (area_is_mt(circuit->area))
+ mtid = ISIS_MT_IPV4_UNICAST;
+ else
+ mtid = ISIS_MT_DISABLE;
+ isis_tlvs_add_extended_reach(lsp->tlvs, mtid, ne_id, 0, NULL);
+ lsp_debug("ISIS (%s): Adding %pPN as te-style neighbor (self)",
+ area->area_tag, ne_id);
+ }
+
+ adj_list = list_new();
+ isis_adj_build_up_list(circuit->u.bc.adjdb[level - 1], adj_list);
+
+ for (ALL_LIST_ELEMENTS_RO(adj_list, node, adj)) {
+ if (!(adj->level & level)) {
+ lsp_debug(
+ "ISIS (%s): Ignoring neighbor %pSY, level does not intersect",
+ area->area_tag, adj->sysid);
+ continue;
+ }
+
+ if (!(level == IS_LEVEL_1
+ && adj->sys_type == ISIS_SYSTYPE_L1_IS)
+ && !(level == IS_LEVEL_1
+ && adj->sys_type == ISIS_SYSTYPE_L2_IS
+ && adj->adj_usage == ISIS_ADJ_LEVEL1AND2)
+ && !(level == IS_LEVEL_2
+ && adj->sys_type == ISIS_SYSTYPE_L2_IS)) {
+ lsp_debug(
+ "ISIS (%s): Ignoring neighbor %pSY, level does not match",
+ area->area_tag, adj->sysid);
+ continue;
+ }
+
+ memcpy(ne_id, adj->sysid, ISIS_SYS_ID_LEN);
+ if (circuit->area->oldmetric) {
+ isis_tlvs_add_oldstyle_reach(lsp->tlvs, ne_id, 0);
+ lsp_debug(
+ "ISIS (%s): Adding %pPN as old-style neighbor (peer)",
+ area->area_tag, ne_id);
+ }
+ if (circuit->area->newmetric) {
+ isis_tlvs_add_extended_reach(lsp->tlvs,
+ ISIS_MT_IPV4_UNICAST,
+ ne_id, 0, NULL);
+ lsp_debug(
+ "ISIS (%s): Adding %pPN as te-style neighbor (peer)",
+ area->area_tag, ne_id);
+ }
+ }
+ list_delete(&adj_list);
+ return;
+}
+
+int lsp_generate_pseudo(struct isis_circuit *circuit, int level)
+{
+ struct lspdb_head *head = &circuit->area->lspdb[level - 1];
+ struct isis_lsp *lsp;
+ uint8_t lsp_id[ISIS_SYS_ID_LEN + 2];
+ uint16_t rem_lifetime, refresh_time;
+
+ if ((circuit->is_type & level) != level
+ || (circuit->state != C_STATE_UP)
+ || (circuit->circ_type != CIRCUIT_T_BROADCAST)
+ || (circuit->u.bc.is_dr[level - 1] == 0))
+ return ISIS_ERROR;
+
+ memcpy(lsp_id, circuit->isis->sysid, ISIS_SYS_ID_LEN);
+ LSP_FRAGMENT(lsp_id) = 0;
+ LSP_PSEUDO_ID(lsp_id) = circuit->circuit_id;
+
+ /*
+ * If for some reason have a pseudo LSP in the db already -> regenerate
+ */
+ if (lsp_search(head, lsp_id))
+ return lsp_regenerate_schedule_pseudo(circuit, level);
+
+ rem_lifetime = lsp_rem_lifetime(circuit->area, level);
+ /* RFC3787 section 4 SHOULD not set overload bit in pseudo LSPs */
+ lsp = lsp_new(circuit->area, lsp_id, rem_lifetime, 1,
+ lsp_bits_generate(circuit->area->is_type, 0,
+ circuit->area->attached_bit_send,
+ circuit->area),
+ 0, NULL, level);
+ lsp->area = circuit->area;
+
+ lsp_build_pseudo(lsp, circuit, level);
+ lsp_pack_pdu(lsp);
+ lsp->own_lsp = 1;
+ lsp_insert(head, lsp);
+ lsp_flood(lsp, NULL);
+
+ refresh_time = lsp_refresh_time(lsp, rem_lifetime);
+ EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[level - 1]);
+ circuit->lsp_regenerate_pending[level - 1] = 0;
+ if (level == IS_LEVEL_1)
+ event_add_timer(master, lsp_l1_refresh_pseudo, circuit,
+ refresh_time,
+ &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]);
+ else if (level == IS_LEVEL_2)
+ event_add_timer(master, lsp_l2_refresh_pseudo, circuit,
+ refresh_time,
+ &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]);
+
+ if (IS_DEBUG_UPDATE_PACKETS) {
+ zlog_debug(
+ "ISIS-Upd (%s): Built L%d Pseudo LSP %pLS, len %hu, seq 0x%08x, cksum 0x%04hx, lifetime %hus, refresh %hus",
+ circuit->area->area_tag, level, lsp->hdr.lsp_id,
+ lsp->hdr.pdu_len, lsp->hdr.seqno, lsp->hdr.checksum,
+ lsp->hdr.rem_lifetime, refresh_time);
+ }
+
+ return ISIS_OK;
+}
+
+static int lsp_regenerate_pseudo(struct isis_circuit *circuit, int level)
+{
+ struct lspdb_head *head = &circuit->area->lspdb[level - 1];
+ struct isis_lsp *lsp;
+ uint8_t lsp_id[ISIS_SYS_ID_LEN + 2];
+ uint16_t rem_lifetime, refresh_time;
+
+ if ((circuit->is_type & level) != level
+ || (circuit->state != C_STATE_UP)
+ || (circuit->circ_type != CIRCUIT_T_BROADCAST)
+ || (circuit->u.bc.is_dr[level - 1] == 0))
+ return ISIS_ERROR;
+
+ memcpy(lsp_id, circuit->isis->sysid, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(lsp_id) = circuit->circuit_id;
+ LSP_FRAGMENT(lsp_id) = 0;
+
+ lsp = lsp_search(head, lsp_id);
+
+ if (!lsp) {
+ flog_err(EC_LIB_DEVELOPMENT,
+ "lsp_regenerate_pseudo: no l%d LSP %pLS found!", level,
+ lsp_id);
+ return ISIS_ERROR;
+ }
+
+ rem_lifetime = lsp_rem_lifetime(circuit->area, level);
+ lsp->hdr.rem_lifetime = rem_lifetime;
+ lsp_build_pseudo(lsp, circuit, level);
+ lsp_inc_seqno(lsp, 0);
+ lsp->last_generated = time(NULL);
+ lsp_flood(lsp, NULL);
+
+ refresh_time = lsp_refresh_time(lsp, rem_lifetime);
+ if (level == IS_LEVEL_1)
+ event_add_timer(master, lsp_l1_refresh_pseudo, circuit,
+ refresh_time,
+ &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]);
+ else if (level == IS_LEVEL_2)
+ event_add_timer(master, lsp_l2_refresh_pseudo, circuit,
+ refresh_time,
+ &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]);
+
+ if (IS_DEBUG_UPDATE_PACKETS) {
+ zlog_debug(
+ "ISIS-Upd (%s): Refreshed L%d Pseudo LSP %pLS, len %hu, seq 0x%08x, cksum 0x%04hx, lifetime %hus, refresh %hus",
+ circuit->area->area_tag, level, lsp->hdr.lsp_id,
+ lsp->hdr.pdu_len, lsp->hdr.seqno, lsp->hdr.checksum,
+ lsp->hdr.rem_lifetime, refresh_time);
+ }
+
+ return ISIS_OK;
+}
+
+/*
+ * Something has changed or periodic refresh -> regenerate pseudo LSP
+ */
+static void lsp_l1_refresh_pseudo(struct event *thread)
+{
+ struct isis_circuit *circuit;
+ uint8_t id[ISIS_SYS_ID_LEN + 2];
+
+ circuit = EVENT_ARG(thread);
+
+ circuit->u.bc.t_refresh_pseudo_lsp[0] = NULL;
+ circuit->lsp_regenerate_pending[0] = 0;
+
+ if ((circuit->u.bc.is_dr[0] == 0)
+ || (circuit->is_type & IS_LEVEL_1) == 0) {
+ memcpy(id, circuit->isis->sysid, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(id) = circuit->circuit_id;
+ LSP_FRAGMENT(id) = 0;
+ lsp_purge_pseudo(id, circuit, IS_LEVEL_1);
+ return;
+ }
+
+ lsp_regenerate_pseudo(circuit, IS_LEVEL_1);
+}
+
+static void lsp_l2_refresh_pseudo(struct event *thread)
+{
+ struct isis_circuit *circuit;
+ uint8_t id[ISIS_SYS_ID_LEN + 2];
+
+ circuit = EVENT_ARG(thread);
+
+ circuit->u.bc.t_refresh_pseudo_lsp[1] = NULL;
+ circuit->lsp_regenerate_pending[1] = 0;
+
+ if ((circuit->u.bc.is_dr[1] == 0)
+ || (circuit->is_type & IS_LEVEL_2) == 0) {
+ memcpy(id, circuit->isis->sysid, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(id) = circuit->circuit_id;
+ LSP_FRAGMENT(id) = 0;
+ lsp_purge_pseudo(id, circuit, IS_LEVEL_2);
+ return;
+ }
+
+ lsp_regenerate_pseudo(circuit, IS_LEVEL_2);
+}
+
+int lsp_regenerate_schedule_pseudo(struct isis_circuit *circuit, int level)
+{
+ struct isis_lsp *lsp;
+ uint8_t lsp_id[ISIS_SYS_ID_LEN + 2];
+ time_t now, diff;
+ long timeout;
+ int lvl;
+ struct isis_area *area = circuit->area;
+
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST
+ || circuit->state != C_STATE_UP)
+ return ISIS_OK;
+
+ sched_debug(
+ "ISIS (%s): Scheduling regeneration of %s pseudo LSP for interface %s",
+ area->area_tag, circuit_t2string(level),
+ circuit->interface->name);
+
+ memcpy(lsp_id, area->isis->sysid, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(lsp_id) = circuit->circuit_id;
+ LSP_FRAGMENT(lsp_id) = 0;
+ now = time(NULL);
+
+ for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) {
+ sched_debug(
+ "ISIS (%s): Checking whether L%d pseudo LSP needs to be scheduled",
+ area->area_tag, lvl);
+
+ if (!((level & lvl) && (circuit->is_type & lvl))) {
+ sched_debug("ISIS (%s): Level is not active on circuit",
+ area->area_tag);
+ continue;
+ }
+
+ if (circuit->u.bc.is_dr[lvl - 1] == 0) {
+ sched_debug(
+ "ISIS (%s): This IS is not DR, nothing to do.",
+ area->area_tag);
+ continue;
+ }
+
+ if (circuit->lsp_regenerate_pending[lvl - 1]) {
+ struct timeval remain = event_timer_remain(
+ circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]);
+ sched_debug(
+ "ISIS (%s): Regenerate is already pending, nothing todo. (Due in %lld.%03lld seconds)",
+ area->area_tag, (long long)remain.tv_sec,
+ (long long)remain.tv_usec / 1000);
+ continue;
+ }
+
+ lsp = lsp_search(&circuit->area->lspdb[lvl - 1], lsp_id);
+ if (!lsp) {
+ sched_debug(
+ "ISIS (%s): Pseudonode LSP does not exist yet, nothing to regenerate.",
+ area->area_tag);
+ continue;
+ }
+
+ /*
+ * Throttle avoidance
+ */
+ sched_debug(
+ "ISIS (%s): Will schedule PSN regen timer. Last run was: %lld, Now is: %lld",
+ area->area_tag, (long long)lsp->last_generated,
+ (long long)now);
+ EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]);
+ diff = now - lsp->last_generated;
+ if (diff < circuit->area->lsp_gen_interval[lvl - 1]) {
+ timeout =
+ 1000 * (circuit->area->lsp_gen_interval[lvl - 1]
+ - diff);
+ sched_debug(
+ "ISIS (%s): Sechduling in %ld ms to match configured lsp_gen_interval",
+ area->area_tag, timeout);
+ } else {
+ timeout = 100;
+ sched_debug(
+ "ISIS (%s): Last generation was more than lsp_gen_interval ago. Scheduling for execution in %ld ms.",
+ area->area_tag, timeout);
+ }
+
+ circuit->lsp_regenerate_pending[lvl - 1] = 1;
+
+ if (lvl == IS_LEVEL_1) {
+ event_add_timer_msec(
+ master, lsp_l1_refresh_pseudo, circuit, timeout,
+ &circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]);
+ } else if (lvl == IS_LEVEL_2) {
+ event_add_timer_msec(
+ master, lsp_l2_refresh_pseudo, circuit, timeout,
+ &circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]);
+ }
+ }
+
+ return ISIS_OK;
+}
+
+/*
+ * Walk through LSPs for an area
+ * - set remaining lifetime
+ */
+void lsp_tick(struct event *thread)
+{
+ struct isis_area *area;
+ struct isis_lsp *lsp;
+ int level;
+ uint16_t rem_lifetime;
+ bool fabricd_sync_incomplete = false;
+
+ area = EVENT_ARG(thread);
+ assert(area);
+ area->t_tick = NULL;
+ event_add_timer(master, lsp_tick, area, 1, &area->t_tick);
+
+ struct isis_circuit *fabricd_init_c = fabricd_initial_sync_circuit(area);
+
+ /*
+ * Remove LSPs which have aged out
+ */
+ for (level = 0; level < ISIS_LEVELS; level++) {
+ struct isis_lsp *next = lspdb_first(&area->lspdb[level]);
+ frr_each_from (lspdb, &area->lspdb[level], lsp, next) {
+ /*
+ * The lsp rem_lifetime is kept at 0 for MaxAge
+ * or
+ * ZeroAgeLifetime depending on explicit purge
+ * or
+ * natural age out. So schedule spf only once
+ * when
+ * the first time rem_lifetime becomes 0.
+ */
+ rem_lifetime = lsp->hdr.rem_lifetime;
+ lsp_set_time(lsp);
+
+ /*
+ * Schedule may run spf which should be done
+ * only after
+ * the lsp rem_lifetime becomes 0 for the first
+ * time.
+ * ISO 10589 - 7.3.16.4 first paragraph.
+ */
+ if (rem_lifetime == 1 && lsp->hdr.seqno != 0) {
+ /* 7.3.16.4 a) set SRM flags on all */
+ /* 7.3.16.4 b) retain only the header */
+ if (lsp->area->purge_originator)
+ lsp_purge(lsp, lsp->level, NULL);
+ else
+ lsp_flood(lsp, NULL);
+ /* 7.3.16.4 c) record the time to purge
+ * FIXME */
+ isis_spf_schedule(lsp->area, lsp->level);
+ isis_te_lsp_event(lsp, LSP_TICK);
+ }
+
+ if (lsp->age_out == 0) {
+ zlog_debug(
+ "ISIS-Upd (%s): L%u LSP %pLS seq 0x%08x aged out",
+ area->area_tag, lsp->level,
+ lsp->hdr.lsp_id, lsp->hdr.seqno);
+
+ /* if we're aging out fragment 0, lsp_destroy()
+ * below will delete all other fragments too,
+ * so we need to skip over those
+ */
+ if (!LSP_FRAGMENT(lsp->hdr.lsp_id))
+ while (next &&
+ !memcmp(next->hdr.lsp_id,
+ lsp->hdr.lsp_id,
+ ISIS_SYS_ID_LEN + 1))
+ next = lspdb_next(
+ &area->lspdb[level],
+ next);
+
+ lspdb_del(&area->lspdb[level], lsp);
+ lsp_destroy(lsp);
+ lsp = NULL;
+ }
+
+ if (fabricd_init_c && lsp) {
+ fabricd_sync_incomplete |=
+ ISIS_CHECK_FLAG(lsp->SSNflags,
+ fabricd_init_c);
+ }
+ }
+ }
+
+ if (fabricd_init_c
+ && !fabricd_sync_incomplete
+ && !isis_tx_queue_len(fabricd_init_c->tx_queue)) {
+ fabricd_initial_sync_finish(area);
+ }
+}
+
+void lsp_purge_pseudo(uint8_t *id, struct isis_circuit *circuit, int level)
+{
+ struct isis_lsp *lsp;
+
+ lsp = lsp_search(&circuit->area->lspdb[level - 1], id);
+ if (!lsp)
+ return;
+
+ lsp_purge(lsp, level, NULL);
+}
+
+/*
+ * Purge own LSP that is received and we don't have.
+ * -> Do as in 7.3.16.4
+ */
+void lsp_purge_non_exist(int level, struct isis_lsp_hdr *hdr,
+ struct isis_area *area)
+{
+ struct isis_lsp *lsp;
+
+ /*
+ * We need to create the LSP to be purged
+ */
+ lsp = XCALLOC(MTYPE_ISIS_LSP, sizeof(struct isis_lsp));
+ lsp->area = area;
+ lsp->level = level;
+ lsp_adjust_stream(lsp);
+ lsp->age_out = ZERO_AGE_LIFETIME;
+ lsp->area->lsp_purge_count[level - 1]++;
+
+ memcpy(&lsp->hdr, hdr, sizeof(lsp->hdr));
+ lsp->hdr.rem_lifetime = 0;
+
+ lsp_purge_add_poi(lsp, NULL);
+
+ lsp_pack_pdu(lsp);
+
+ lsp_insert(&area->lspdb[lsp->level - 1], lsp);
+ lsp_flood(lsp, NULL);
+
+ return;
+}
+
+void lsp_set_all_srmflags(struct isis_lsp *lsp, bool set)
+{
+ struct listnode *node;
+ struct isis_circuit *circuit;
+
+ assert(lsp);
+
+ if (!lsp->area)
+ return;
+
+ struct list *circuit_list = lsp->area->circuit_list;
+ for (ALL_LIST_ELEMENTS_RO(circuit_list, node, circuit)) {
+ if (set) {
+ isis_tx_queue_add(circuit->tx_queue, lsp,
+ TX_LSP_NORMAL);
+ } else {
+ isis_tx_queue_del(circuit->tx_queue, lsp);
+ }
+ }
+}
+
+void _lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit,
+ const char *func, const char *file, int line)
+{
+ if (IS_DEBUG_FLOODING) {
+ zlog_debug("Flooding LSP %pLS%s%s (From %s %s:%d)",
+ lsp->hdr.lsp_id, circuit ? " except on " : "",
+ circuit ? circuit->interface->name : "", func, file,
+ line);
+ }
+
+ if (!fabricd)
+ lsp_set_all_srmflags(lsp, true);
+ else
+ fabricd_lsp_flood(lsp, circuit);
+
+ if (circuit)
+ isis_tx_queue_del(circuit->tx_queue, lsp);
+}
+
+static int lsp_handle_adj_state_change(struct isis_adjacency *adj)
+{
+ lsp_regenerate_schedule(adj->circuit->area, IS_LEVEL_1 | IS_LEVEL_2, 0);
+
+ /* when an adjacency state changes determine if we need to
+ * change attach_bits in other area's LSPs
+ */
+ isis_reset_attach_bit(adj);
+
+ return 0;
+}
+
+/*
+ * Iterate over all IP reachability TLVs in a LSP (all fragments) of the given
+ * address-family and MT-ID.
+ */
+int isis_lsp_iterate_ip_reach(struct isis_lsp *lsp, int family, uint16_t mtid,
+ lsp_ip_reach_iter_cb cb, void *arg)
+{
+ bool pseudo_lsp = LSP_PSEUDO_ID(lsp->hdr.lsp_id);
+ struct isis_lsp *frag;
+ struct listnode *node;
+
+ if (lsp->hdr.seqno == 0 || lsp->hdr.rem_lifetime == 0)
+ return LSP_ITER_CONTINUE;
+
+ /* Parse LSP */
+ if (lsp->tlvs) {
+ if (!fabricd && !pseudo_lsp && family == AF_INET
+ && mtid == ISIS_MT_IPV4_UNICAST) {
+ struct isis_item_list *reachs[] = {
+ &lsp->tlvs->oldstyle_ip_reach,
+ &lsp->tlvs->oldstyle_ip_reach_ext};
+
+ for (unsigned int i = 0; i < array_size(reachs); i++) {
+ struct isis_oldstyle_ip_reach *r;
+
+ for (r = (struct isis_oldstyle_ip_reach *)
+ reachs[i]
+ ->head;
+ r; r = r->next) {
+ bool external = i ? true : false;
+
+ if ((*cb)((struct prefix *)&r->prefix,
+ r->metric, external, NULL,
+ arg)
+ == LSP_ITER_STOP)
+ return LSP_ITER_STOP;
+ }
+ }
+ }
+
+ if (!pseudo_lsp && family == AF_INET) {
+ struct isis_item_list *ipv4_reachs;
+
+ if (mtid == ISIS_MT_IPV4_UNICAST)
+ ipv4_reachs = &lsp->tlvs->extended_ip_reach;
+ else
+ ipv4_reachs = isis_lookup_mt_items(
+ &lsp->tlvs->mt_ip_reach, mtid);
+
+ struct isis_extended_ip_reach *r;
+ for (r = ipv4_reachs ? (struct isis_extended_ip_reach *)
+ ipv4_reachs->head
+ : NULL;
+ r; r = r->next) {
+ if ((*cb)((struct prefix *)&r->prefix,
+ r->metric, false, r->subtlvs, arg)
+ == LSP_ITER_STOP)
+ return LSP_ITER_STOP;
+ }
+ }
+
+ if (!pseudo_lsp && family == AF_INET6) {
+ struct isis_item_list *ipv6_reachs;
+ struct isis_ipv6_reach *r;
+
+ if (mtid == ISIS_MT_IPV4_UNICAST)
+ ipv6_reachs = &lsp->tlvs->ipv6_reach;
+ else
+ ipv6_reachs = isis_lookup_mt_items(
+ &lsp->tlvs->mt_ipv6_reach, mtid);
+
+ for (r = ipv6_reachs ? (struct isis_ipv6_reach *)
+ ipv6_reachs->head
+ : NULL;
+ r; r = r->next) {
+ if ((*cb)((struct prefix *)&r->prefix,
+ r->metric, r->external, r->subtlvs,
+ arg)
+ == LSP_ITER_STOP)
+ return LSP_ITER_STOP;
+ }
+ }
+ }
+
+ /* Parse LSP fragments if it is not a fragment itself */
+ if (!LSP_FRAGMENT(lsp->hdr.lsp_id))
+ for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) {
+ if (!frag->tlvs)
+ continue;
+
+ if (isis_lsp_iterate_ip_reach(frag, family, mtid, cb,
+ arg)
+ == LSP_ITER_STOP)
+ return LSP_ITER_STOP;
+ }
+
+ return LSP_ITER_CONTINUE;
+}
+
+/*
+ * Iterate over all IS reachability TLVs in a LSP (all fragments) of the given
+ * MT-ID.
+ */
+int isis_lsp_iterate_is_reach(struct isis_lsp *lsp, uint16_t mtid,
+ lsp_is_reach_iter_cb cb, void *arg)
+{
+ bool pseudo_lsp = LSP_PSEUDO_ID(lsp->hdr.lsp_id);
+ struct isis_lsp *frag;
+ struct listnode *node;
+ struct isis_item *head;
+ struct isis_item_list *te_neighs;
+
+ if (lsp->hdr.seqno == 0 || lsp->hdr.rem_lifetime == 0)
+ return LSP_ITER_CONTINUE;
+
+ /* Parse LSP */
+ if (lsp->tlvs) {
+ if (pseudo_lsp || mtid == ISIS_MT_IPV4_UNICAST) {
+ head = lsp->tlvs->oldstyle_reach.head;
+ for (struct isis_oldstyle_reach *reach =
+ (struct isis_oldstyle_reach *)head;
+ reach; reach = reach->next) {
+ if ((*cb)(reach->id, reach->metric, true, NULL,
+ arg)
+ == LSP_ITER_STOP)
+ return LSP_ITER_STOP;
+ }
+ }
+
+ if (pseudo_lsp || mtid == ISIS_MT_IPV4_UNICAST)
+ te_neighs = &lsp->tlvs->extended_reach;
+ else
+ te_neighs =
+ isis_get_mt_items(&lsp->tlvs->mt_reach, mtid);
+ if (te_neighs) {
+ head = te_neighs->head;
+ for (struct isis_extended_reach *reach =
+ (struct isis_extended_reach *)head;
+ reach; reach = reach->next) {
+ if ((*cb)(reach->id, reach->metric, false,
+ reach->subtlvs, arg)
+ == LSP_ITER_STOP)
+ return LSP_ITER_STOP;
+ }
+ }
+ }
+
+ /* Parse LSP fragments if it not a fragment itself. */
+ if (!LSP_FRAGMENT(lsp->hdr.lsp_id))
+ for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) {
+ if (!frag->tlvs)
+ continue;
+
+ if (isis_lsp_iterate_is_reach(frag, mtid, cb, arg)
+ == LSP_ITER_STOP)
+ return LSP_ITER_STOP;
+ }
+
+ return LSP_ITER_CONTINUE;
+}
+
+void lsp_init(void)
+{
+ device_startup = true;
+ hook_register(isis_adj_state_change_hook,
+ lsp_handle_adj_state_change);
+}
diff --git a/isisd/isis_lsp.h b/isisd/isis_lsp.h
new file mode 100644
index 0000000..3839a95
--- /dev/null
+++ b/isisd/isis_lsp.h
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_lsp.h
+ * LSP processing
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#ifndef _ZEBRA_ISIS_LSP_H
+#define _ZEBRA_ISIS_LSP_H
+
+#include "lib/typesafe.h"
+#include "isisd/isis_pdu.h"
+
+PREDECL_RBTREE_UNIQ(lspdb);
+
+struct isis;
+/* Structure for isis_lsp, this structure will only support the fixed
+ * System ID (Currently 6) (atleast for now). In order to support more
+ * We will have to split the header into two parts, and for readability
+ * sake it should better be avoided */
+struct isis_lsp {
+ struct lspdb_item dbe;
+
+ struct isis_lsp_hdr hdr;
+ struct stream *pdu; /* full pdu lsp */
+ union {
+ struct list *frags;
+ struct isis_lsp *zero_lsp;
+ } lspu;
+ uint32_t SSNflags[ISIS_MAX_CIRCUITS];
+ int level; /* L1 or L2? */
+ int scheduled; /* scheduled for sending */
+ time_t installed;
+ time_t last_generated;
+ int own_lsp;
+ /* used for 60 second counting when rem_lifetime is zero */
+ int age_out;
+ struct isis_area *area;
+ struct isis_tlvs *tlvs;
+
+ time_t flooding_time;
+ struct list *flooding_neighbors[TX_LSP_CIRCUIT_SCOPED + 1];
+ char *flooding_interface;
+ bool flooding_circuit_scoped;
+};
+
+extern int lspdb_compare(const struct isis_lsp *a, const struct isis_lsp *b);
+DECLARE_RBTREE_UNIQ(lspdb, struct isis_lsp, dbe, lspdb_compare);
+
+void lsp_db_init(struct lspdb_head *head);
+void lsp_db_fini(struct lspdb_head *head);
+void lsp_tick(struct event *thread);
+void set_overload_on_start_timer(struct event *thread);
+
+int lsp_generate(struct isis_area *area, int level);
+#define lsp_regenerate_schedule(area, level, all_pseudo) \
+ _lsp_regenerate_schedule((area), (level), (all_pseudo), true, \
+ __func__, __FILE__, __LINE__)
+int _lsp_regenerate_schedule(struct isis_area *area, int level,
+ int all_pseudo, bool postpone,
+ const char *func, const char *file, int line);
+int lsp_generate_pseudo(struct isis_circuit *circuit, int level);
+int lsp_regenerate_schedule_pseudo(struct isis_circuit *circuit, int level);
+
+bool isis_level2_adj_up(struct isis_area *area);
+
+struct isis_lsp *lsp_new(struct isis_area *area, uint8_t *lsp_id,
+ uint16_t rem_lifetime, uint32_t seq_num,
+ uint8_t lsp_bits, uint16_t checksum,
+ struct isis_lsp *lsp0, int level);
+struct isis_lsp *lsp_new_from_recv(struct isis_lsp_hdr *hdr,
+ struct isis_tlvs *tlvs,
+ struct stream *stream, struct isis_lsp *lsp0,
+ struct isis_area *area, int level);
+void lsp_insert(struct lspdb_head *head, struct isis_lsp *lsp);
+struct isis_lsp *lsp_search(struct lspdb_head *head, const uint8_t *id);
+
+void lsp_build_list(struct lspdb_head *head, const uint8_t *start_id,
+ const uint8_t *stop_id, uint8_t num_lsps,
+ struct list *list);
+void lsp_build_list_nonzero_ht(struct lspdb_head *head,
+ const uint8_t *start_id,
+ const uint8_t *stop_id, struct list *list);
+void lsp_search_and_destroy(struct lspdb_head *head, const uint8_t *id);
+void lsp_purge_pseudo(uint8_t *id, struct isis_circuit *circuit, int level);
+void lsp_purge_non_exist(int level, struct isis_lsp_hdr *hdr,
+ struct isis_area *area);
+
+#define LSP_EQUAL 1
+#define LSP_NEWER 2
+#define LSP_OLDER 3
+
+#define LSP_PSEUDO_ID(I) ((I)[ISIS_SYS_ID_LEN])
+#define LSP_FRAGMENT(I) ((I)[ISIS_SYS_ID_LEN + 1])
+#define OWNLSPID(I) \
+ memcpy((I), isis->sysid, ISIS_SYS_ID_LEN); \
+ (I)[ISIS_SYS_ID_LEN] = 0; \
+ (I)[ISIS_SYS_ID_LEN + 1] = 0
+int lsp_id_cmp(uint8_t *id1, uint8_t *id2);
+int lsp_compare(char *areatag, struct isis_lsp *lsp, uint32_t seqno,
+ uint16_t checksum, uint16_t rem_lifetime);
+void lsp_update(struct isis_lsp *lsp, struct isis_lsp_hdr *hdr,
+ struct isis_tlvs *tlvs, struct stream *stream,
+ struct isis_area *area, int level, bool confusion);
+void lsp_inc_seqno(struct isis_lsp *lsp, uint32_t seqno);
+void lspid_print(uint8_t *lsp_id, char *dest, size_t dest_len, char dynhost,
+ char frag, struct isis *isis);
+void lsp_print_common(struct isis_lsp *lsp, struct vty *vty,
+ struct json_object *json, char dynhost,
+ struct isis *isis);
+void lsp_print_vty(struct isis_lsp *lsp, struct vty *vty, char dynhost,
+ struct isis *isis);
+void lsp_print_json(struct isis_lsp *lsp, struct json_object *json,
+ char dynhost, struct isis *isis);
+void lsp_print_detail(struct isis_lsp *lsp, struct vty *vty,
+ struct json_object *json, char dynhost,
+ struct isis *isis);
+int lsp_print_all(struct vty *vty, struct json_object *json,
+ struct lspdb_head *head, char detail, char dynhost,
+ struct isis *isis);
+/* sets SRMflags for all active circuits of an lsp */
+void lsp_set_all_srmflags(struct isis_lsp *lsp, bool set);
+
+#define LSP_ITER_CONTINUE 0
+#define LSP_ITER_STOP -1
+
+/* Callback used by isis_lsp_iterate_ip_reach() function. */
+struct isis_subtlvs;
+typedef int (*lsp_ip_reach_iter_cb)(const struct prefix *prefix,
+ uint32_t metric, bool external,
+ struct isis_subtlvs *subtlvs, void *arg);
+
+/* Callback used by isis_lsp_iterate_is_reach() function. */
+typedef int (*lsp_is_reach_iter_cb)(const uint8_t *id, uint32_t metric,
+ bool oldmetric,
+ struct isis_ext_subtlvs *subtlvs,
+ void *arg);
+
+int isis_lsp_iterate_ip_reach(struct isis_lsp *lsp, int family, uint16_t mtid,
+ lsp_ip_reach_iter_cb cb, void *arg);
+int isis_lsp_iterate_is_reach(struct isis_lsp *lsp, uint16_t mtid,
+ lsp_is_reach_iter_cb cb, void *arg);
+
+#define lsp_flood(lsp, circuit) \
+ _lsp_flood((lsp), (circuit), __func__, __FILE__, __LINE__)
+void _lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit,
+ const char *func, const char *file, int line);
+void lsp_init(void);
+
+#endif /* ISIS_LSP */
diff --git a/isisd/isis_main.c b/isisd/isis_main.c
new file mode 100644
index 0000000..da4c7bc
--- /dev/null
+++ b/isisd/isis_main.c
@@ -0,0 +1,318 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_main.c
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+
+#include "getopt.h"
+#include "frrevent.h"
+#include "log.h"
+#include <lib/version.h>
+#include "command.h"
+#include "vty.h"
+#include "memory.h"
+#include "stream.h"
+#include "if.h"
+#include "privs.h"
+#include "sigevent.h"
+#include "filter.h"
+#include "plist.h"
+#include "zclient.h"
+#include "vrf.h"
+#include "qobj.h"
+#include "libfrr.h"
+#include "routemap.h"
+#include "affinitymap.h"
+
+#include "isisd/isis_affinitymap.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_dynhn.h"
+#include "isisd/isis_spf.h"
+#include "isisd/isis_route.h"
+#include "isisd/isis_routemap.h"
+#include "isisd/isis_zebra.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_errors.h"
+#include "isisd/isis_bfd.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_mt.h"
+#include "isisd/fabricd.h"
+#include "isisd/isis_nb.h"
+#include "isisd/isis_ldp_sync.h"
+
+/* Default configuration file name */
+#define ISISD_DEFAULT_CONFIG "isisd.conf"
+/* Default vty port */
+#define ISISD_VTY_PORT 2608
+#define FABRICD_VTY_PORT 2618
+
+/* isisd privileges */
+zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_SYS_ADMIN};
+
+struct zebra_privs_t isisd_privs = {
+#if defined(FRR_USER)
+ .user = FRR_USER,
+#endif
+#if defined FRR_GROUP
+ .group = FRR_GROUP,
+#endif
+#ifdef VTY_GROUP
+ .vty_group = VTY_GROUP,
+#endif
+ .caps_p = _caps_p,
+ .cap_num_p = array_size(_caps_p),
+ .cap_num_i = 0};
+
+/* isisd options */
+static const struct option longopts[] = {
+ {"int_num", required_argument, NULL, 'I'},
+ {0}};
+
+/* Master of threads. */
+struct event_loop *master;
+
+/*
+ * Prototypes.
+ */
+void sighup(void);
+void sigint(void);
+void sigterm(void);
+void sigusr1(void);
+
+
+static __attribute__((__noreturn__)) void terminate(int i)
+{
+ isis_terminate();
+ isis_sr_term();
+ isis_srv6_term();
+ isis_zebra_stop();
+ exit(i);
+}
+
+/*
+ * Signal handlers
+ */
+#ifdef FABRICD
+void sighup(void)
+{
+ zlog_notice("SIGHUP/reload is not implemented for fabricd");
+ return;
+}
+#else
+static struct frr_daemon_info isisd_di;
+void sighup(void)
+{
+ zlog_info("SIGHUP received");
+
+ /* Reload config file. */
+ vty_read_config(NULL, isisd_di.config_file, config_default);
+}
+
+#endif
+
+__attribute__((__noreturn__)) void sigint(void)
+{
+ zlog_notice("Terminating on signal SIGINT");
+ terminate(0);
+}
+
+__attribute__((__noreturn__)) void sigterm(void)
+{
+ zlog_notice("Terminating on signal SIGTERM");
+ terminate(0);
+}
+
+void sigusr1(void)
+{
+ zlog_debug("SIGUSR1 received");
+ zlog_rotate();
+}
+
+struct frr_signal_t isisd_signals[] = {
+ {
+ .signal = SIGHUP,
+ .handler = &sighup,
+ },
+ {
+ .signal = SIGUSR1,
+ .handler = &sigusr1,
+ },
+ {
+ .signal = SIGINT,
+ .handler = &sigint,
+ },
+ {
+ .signal = SIGTERM,
+ .handler = &sigterm,
+ },
+};
+
+
+/* clang-format off */
+static const struct frr_yang_module_info *const isisd_yang_modules[] = {
+ &frr_filter_info,
+ &frr_interface_info,
+#ifndef FABRICD
+ &frr_isisd_info,
+#endif /* ifndef FABRICD */
+ &frr_route_map_info,
+ &frr_affinity_map_info,
+ &frr_vrf_info,
+};
+/* clang-format on */
+
+
+/* Max wait time for config to load before generating LSPs */
+#define ISIS_PRE_CONFIG_MAX_WAIT_SECONDS 600
+
+static void isis_config_finish(struct event *t)
+{
+ struct listnode *node, *inode;
+ struct isis *isis;
+ struct isis_area *area;
+
+ for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area))
+ config_end_lsp_generate(area);
+ }
+}
+
+static void isis_config_end_timeout(struct event *t)
+{
+ zlog_err("IS-IS configuration end timer expired after %d seconds.",
+ ISIS_PRE_CONFIG_MAX_WAIT_SECONDS);
+ isis_config_finish(t);
+}
+
+static void isis_config_start(void)
+{
+ EVENT_OFF(t_isis_cfg);
+ event_add_timer(im->master, isis_config_end_timeout, NULL,
+ ISIS_PRE_CONFIG_MAX_WAIT_SECONDS, &t_isis_cfg);
+}
+
+static void isis_config_end(void)
+{
+ /* If ISIS config processing thread isn't running, then
+ * we can return and rely it's properly handled.
+ */
+ if (!event_is_scheduled(t_isis_cfg))
+ return;
+
+ EVENT_OFF(t_isis_cfg);
+ isis_config_finish(t_isis_cfg);
+}
+
+#ifdef FABRICD
+FRR_DAEMON_INFO(fabricd, OPEN_FABRIC, .vty_port = FABRICD_VTY_PORT,
+
+ .proghelp = "Implementation of the OpenFabric routing protocol.",
+#else
+FRR_DAEMON_INFO(isisd, ISIS, .vty_port = ISISD_VTY_PORT,
+
+ .proghelp = "Implementation of the IS-IS routing protocol.",
+#endif
+ .copyright =
+ "Copyright (c) 2001-2002 Sampo Saaristo, Ofer Wald and Hannes Gredler",
+
+ .signals = isisd_signals,
+ .n_signals = array_size(isisd_signals),
+
+ .privs = &isisd_privs, .yang_modules = isisd_yang_modules,
+ .n_yang_modules = array_size(isisd_yang_modules),
+);
+
+/*
+ * Main routine of isisd. Parse arguments and handle IS-IS state machine.
+ */
+int main(int argc, char **argv, char **envp)
+{
+ int opt;
+ int instance = 1;
+
+#ifdef FABRICD
+ frr_preinit(&fabricd_di, argc, argv);
+#else
+ frr_preinit(&isisd_di, argc, argv);
+#endif
+ frr_opt_add(
+ "I:", longopts,
+ " -I, --int_num Set instance number (label-manager)\n");
+
+ /* Command line argument treatment. */
+ while (1) {
+ opt = frr_getopt(argc, argv, NULL);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 0:
+ break;
+ case 'I':
+ instance = atoi(optarg);
+ if (instance < 1 || instance > (unsigned short)-1)
+ zlog_err("Instance %i out of range (1..%u)",
+ instance, (unsigned short)-1);
+ break;
+ default:
+ frr_help_exit(1);
+ }
+ }
+
+ /* thread master */
+ isis_master_init(frr_init());
+ master = im->master;
+ /*
+ * initializations
+ */
+ cmd_init_config_callbacks(isis_config_start, isis_config_end);
+ isis_error_init();
+ access_list_init();
+ access_list_add_hook(isis_filter_update);
+ access_list_delete_hook(isis_filter_update);
+ isis_vrf_init();
+ prefix_list_init();
+ prefix_list_add_hook(isis_prefix_list_update);
+ prefix_list_delete_hook(isis_prefix_list_update);
+ isis_init();
+ isis_circuit_init();
+#ifdef FABRICD
+ isis_vty_daemon_init();
+#endif /* FABRICD */
+#ifndef FABRICD
+ isis_cli_init();
+#endif /* ifndef FABRICD */
+ isis_spf_init();
+ isis_redist_init();
+ isis_route_map_init();
+ isis_mpls_te_init();
+ isis_sr_init();
+ isis_srv6_init();
+ lsp_init();
+ mt_init();
+
+#ifndef FABRICD
+ isis_affinity_map_init();
+#endif /* ifndef FABRICD */
+
+ isis_zebra_init(master, instance);
+ isis_bfd_init(master);
+ isis_ldp_sync_init();
+ fabricd_init();
+
+ frr_config_fork();
+ frr_run(master);
+
+ /* Not reached. */
+ exit(0);
+}
diff --git a/isisd/isis_misc.c b/isisd/isis_misc.c
new file mode 100644
index 0000000..e4ef6c8
--- /dev/null
+++ b/isisd/isis_misc.c
@@ -0,0 +1,528 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_misc.h
+ * Miscellanous routines
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+
+#include "printfrr.h"
+#include "stream.h"
+#include "vty.h"
+#include "hash.h"
+#include "if.h"
+#include "command.h"
+#include "network.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_misc.h"
+
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_dynhn.h"
+
+/* staticly assigned vars for printing purposes */
+static char sys_hostname[ISO_SYSID_STRLEN];
+struct in_addr new_prefix;
+/* len of xxYxxMxWxdxxhxxmxxs + place for #0 termination */
+char datestring[20];
+char nlpidstring[30];
+
+/*
+ * Returns 0 on error, length of buff on ok
+ * extract dot from the dotted str, and insert all the number in a buff
+ */
+int dotformat2buff(uint8_t *buff, const char *dotted)
+{
+ int dotlen, len = 0;
+ const char *pos = dotted;
+ uint8_t number[3];
+ int nextdotpos = 2;
+
+ number[2] = '\0';
+ dotlen = strlen(dotted);
+ if (dotlen > 50) {
+ /* this can't be an iso net, its too long */
+ return 0;
+ }
+
+ while ((pos - dotted) < dotlen && len < 20) {
+ if (*pos == '.') {
+ /* we expect the . at 2, and than every 5 */
+ if ((pos - dotted) != nextdotpos) {
+ len = 0;
+ break;
+ }
+ nextdotpos += 5;
+ pos++;
+ continue;
+ }
+ /* we must have at least two chars left here */
+ if (dotlen - (pos - dotted) < 2) {
+ len = 0;
+ break;
+ }
+
+ if ((isxdigit((unsigned char)*pos)) &&
+ (isxdigit((unsigned char)*(pos + 1)))) {
+ memcpy(number, pos, 2);
+ pos += 2;
+ } else {
+ len = 0;
+ break;
+ }
+
+ *(buff + len) = (char)strtol((char *)number, NULL, 16);
+ len++;
+ }
+
+ return len;
+}
+
+/*
+ * conversion of XXXX.XXXX.XXXX to memory
+ */
+int sysid2buff(uint8_t *buff, const char *dotted)
+{
+ int len = 0;
+ const char *pos = dotted;
+ uint8_t number[3];
+
+ number[2] = '\0';
+ // surely not a sysid_string if not 14 length
+ if (strlen(dotted) != 14) {
+ return 0;
+ }
+
+ while (len < ISIS_SYS_ID_LEN) {
+ if (*pos == '.') {
+ /* the . is not positioned correctly */
+ if (((pos - dotted) != 4) && ((pos - dotted) != 9)) {
+ len = 0;
+ break;
+ }
+ pos++;
+ continue;
+ }
+ if ((isxdigit((unsigned char)*pos)) &&
+ (isxdigit((unsigned char)*(pos + 1)))) {
+ memcpy(number, pos, 2);
+ pos += 2;
+ } else {
+ len = 0;
+ break;
+ }
+
+ *(buff + len) = (char)strtol((char *)number, NULL, 16);
+ len++;
+ }
+
+ return len;
+}
+
+const char *nlpid2str(uint8_t nlpid)
+{
+ static char buf[4];
+ switch (nlpid) {
+ case NLPID_IP:
+ return "IPv4";
+ case NLPID_IPV6:
+ return "IPv6";
+ case NLPID_SNAP:
+ return "SNAP";
+ case NLPID_CLNP:
+ return "CLNP";
+ case NLPID_ESIS:
+ return "ES-IS";
+ default:
+ snprintf(buf, sizeof(buf), "%hhu", nlpid);
+ return buf;
+ }
+}
+
+/*
+ * converts the nlpids struct (filled by TLV #129)
+ * into a string
+ */
+
+char *nlpid2string(struct nlpids *nlpids)
+{
+ int i;
+ char tbuf[256];
+ nlpidstring[0] = '\0';
+
+ for (i = 0; i < nlpids->count; i++) {
+ snprintf(tbuf, sizeof(tbuf), "%s",
+ nlpid2str(nlpids->nlpids[i]));
+ strlcat(nlpidstring, tbuf, sizeof(nlpidstring));
+ if (nlpids->count - i > 1)
+ strlcat(nlpidstring, ", ", sizeof(nlpidstring));
+ }
+
+ return nlpidstring;
+}
+
+/*
+ * Returns 0 on error, IS-IS Circuit Type on ok
+ */
+int string2circuit_t(const char *str)
+{
+
+ if (!str)
+ return 0;
+
+ if (!strcmp(str, "level-1"))
+ return IS_LEVEL_1;
+
+ if (!strcmp(str, "level-2-only") || !strcmp(str, "level-2"))
+ return IS_LEVEL_2;
+
+ if (!strcmp(str, "level-1-2"))
+ return IS_LEVEL_1_AND_2;
+
+ return 0;
+}
+
+const char *circuit_state2string(int state)
+{
+
+ switch (state) {
+ case C_STATE_INIT:
+ return "Init";
+ case C_STATE_CONF:
+ return "Config";
+ case C_STATE_UP:
+ return "Up";
+ default:
+ return "Unknown";
+ }
+ return NULL;
+}
+
+const char *circuit_type2string(int type)
+{
+
+ switch (type) {
+ case CIRCUIT_T_P2P:
+ return "p2p";
+ case CIRCUIT_T_BROADCAST:
+ return "lan";
+ case CIRCUIT_T_LOOPBACK:
+ return "loopback";
+ default:
+ return "Unknown";
+ }
+ return NULL;
+}
+
+const char *circuit_t2string(int circuit_t)
+{
+ switch (circuit_t) {
+ case IS_LEVEL_1:
+ return "L1";
+ case IS_LEVEL_2:
+ return "L2";
+ case IS_LEVEL_1_AND_2:
+ return "L1L2";
+ default:
+ return "??";
+ }
+
+ return NULL; /* not reached */
+}
+
+const char *syst2string(int type)
+{
+ switch (type) {
+ case ISIS_SYSTYPE_ES:
+ return "ES";
+ case ISIS_SYSTYPE_IS:
+ return "IS";
+ case ISIS_SYSTYPE_L1_IS:
+ return "1";
+ case ISIS_SYSTYPE_L2_IS:
+ return "2";
+ default:
+ return "??";
+ }
+
+ return NULL; /* not reached */
+}
+
+const char *isis_hello_padding2string(int hello_padding_type)
+{
+ switch (hello_padding_type) {
+ case ISIS_HELLO_PADDING_DISABLED:
+ return "no";
+ case ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION:
+ return "during-adjacency-formation";
+ case ISIS_HELLO_PADDING_ALWAYS:
+ return "yes";
+ }
+ return NULL; /* not reached */
+}
+
+const char *time2string(uint32_t time)
+{
+ uint32_t rest;
+ char tbuf[32];
+ datestring[0] = '\0';
+
+ if (time == 0)
+ return "-";
+
+ if (time / SECS_PER_YEAR) {
+ snprintf(tbuf, sizeof(tbuf), "%uY", time / SECS_PER_YEAR);
+ strlcat(datestring, tbuf, sizeof(datestring));
+ }
+ rest = time % SECS_PER_YEAR;
+ if (rest / SECS_PER_MONTH) {
+ snprintf(tbuf, sizeof(tbuf), "%uM", rest / SECS_PER_MONTH);
+ strlcat(datestring, tbuf, sizeof(datestring));
+ }
+ rest = rest % SECS_PER_MONTH;
+ if (rest / SECS_PER_WEEK) {
+ snprintf(tbuf, sizeof(tbuf), "%uw", rest / SECS_PER_WEEK);
+ strlcat(datestring, tbuf, sizeof(datestring));
+ }
+ rest = rest % SECS_PER_WEEK;
+ if (rest / SECS_PER_DAY) {
+ snprintf(tbuf, sizeof(tbuf), "%ud", rest / SECS_PER_DAY);
+ strlcat(datestring, tbuf, sizeof(datestring));
+ }
+ rest = rest % SECS_PER_DAY;
+ if (rest / SECS_PER_HOUR) {
+ snprintf(tbuf, sizeof(tbuf), "%uh", rest / SECS_PER_HOUR);
+ strlcat(datestring, tbuf, sizeof(datestring));
+ }
+ rest = rest % SECS_PER_HOUR;
+ if (rest / SECS_PER_MINUTE) {
+ snprintf(tbuf, sizeof(tbuf), "%um", rest / SECS_PER_MINUTE);
+ strlcat(datestring, tbuf, sizeof(datestring));
+ }
+ rest = rest % SECS_PER_MINUTE;
+ if (rest) {
+ snprintf(tbuf, sizeof(tbuf), "%us", rest);
+ strlcat(datestring, tbuf, sizeof(datestring));
+ }
+
+ return datestring;
+}
+
+/*
+ * routine to decrement a timer by a random
+ * number
+ *
+ * first argument is the timer and the second is
+ * the jitter
+ */
+unsigned long isis_jitter(unsigned long timer, unsigned long jitter)
+{
+ int j, k;
+
+ if (jitter >= 100)
+ return timer;
+
+ if (timer == 1)
+ return timer;
+ /*
+ * randomizing just the percent value provides
+ * no good random numbers - hence the spread
+ * to RANDOM_SPREAD (100000), which is ok as
+ * most IS-IS timers are no longer than 16 bit
+ */
+
+ j = 1 + (int)((RANDOM_SPREAD * frr_weak_random()) / (RAND_MAX + 1.0));
+
+ k = timer - (timer * (100 - jitter)) / 100;
+
+ timer = timer - (k * j / RANDOM_SPREAD);
+
+ return timer;
+}
+
+struct in_addr newprefix2inaddr(uint8_t *prefix_start, uint8_t prefix_masklen)
+{
+ memset(&new_prefix, 0, sizeof(new_prefix));
+ memcpy(&new_prefix, prefix_start,
+ (prefix_masklen & 0x3F)
+ ? ((((prefix_masklen & 0x3F) - 1) >> 3) + 1)
+ : 0);
+ return new_prefix;
+}
+
+/*
+ * Returns the dynamic hostname associated with the passed system ID.
+ * If no dynamic hostname found then returns formatted system ID.
+ */
+const char *print_sys_hostname(const uint8_t *sysid)
+{
+ struct isis_dynhn *dyn;
+ struct isis *isis = NULL;
+ struct listnode *node;
+
+ if (!sysid)
+ return "nullsysid";
+
+ /* For our system ID return our host name */
+ isis = isis_lookup_by_sysid(sysid);
+ if (isis && !CHECK_FLAG(im->options, F_ISIS_UNIT_TEST))
+ return cmd_hostname_get();
+
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) {
+ dyn = dynhn_find_by_id(isis, sysid);
+ if (dyn)
+ return dyn->hostname;
+ }
+
+ snprintfrr(sys_hostname, ISO_SYSID_STRLEN, "%pSY", sysid);
+ return sys_hostname;
+}
+
+/*
+ * This function is a generic utility that logs data of given length.
+ * Move this to a shared lib so that any protocol can use it.
+ */
+void zlog_dump_data(void *data, int len)
+{
+ int i;
+ unsigned char *p;
+ unsigned char c;
+ char bytestr[4];
+ char addrstr[10];
+ char hexstr[16 * 3 + 5];
+ char charstr[16 * 1 + 5];
+
+ p = data;
+ memset(bytestr, 0, sizeof(bytestr));
+ memset(addrstr, 0, sizeof(addrstr));
+ memset(hexstr, 0, sizeof(hexstr));
+ memset(charstr, 0, sizeof(charstr));
+
+ for (i = 1; i <= len; i++) {
+ c = *p;
+ if (isalnum(c) == 0)
+ c = '.';
+
+ /* store address for this line */
+ if ((i % 16) == 1)
+ snprintf(addrstr, sizeof(addrstr), "%p", p);
+
+ /* store hex str (for left side) */
+ snprintf(bytestr, sizeof(bytestr), "%02X ", *p);
+ strlcat(hexstr, bytestr, sizeof(hexstr) - strlen(hexstr) - 1);
+
+ /* store char str (for right side) */
+ snprintf(bytestr, sizeof(bytestr), "%c", c);
+ strlcat(charstr, bytestr,
+ sizeof(charstr) - strlen(charstr) - 1);
+
+ if ((i % 16) == 0) {
+ /* line completed */
+ zlog_debug("[%8.8s] %-50.50s %s", addrstr, hexstr,
+ charstr);
+ hexstr[0] = 0;
+ charstr[0] = 0;
+ } else if ((i % 8) == 0) {
+ /* half line: add whitespaces */
+ strlcat(hexstr, " ",
+ sizeof(hexstr) - strlen(hexstr) - 1);
+ strlcat(charstr, " ",
+ sizeof(charstr) - strlen(charstr) - 1);
+ }
+ p++; /* next byte */
+ }
+
+ /* print rest of buffer if not empty */
+ if (strlen(hexstr) > 0)
+ zlog_debug("[%8.8s] %-50.50s %s", addrstr, hexstr, charstr);
+ return;
+}
+
+void log_multiline(int priority, const char *prefix, const char *format, ...)
+{
+ char shortbuf[256];
+ va_list ap;
+ char *p;
+
+ va_start(ap, format);
+ p = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap);
+ va_end(ap);
+
+ if (!p)
+ return;
+
+ char *saveptr = NULL;
+ for (char *line = strtok_r(p, "\n", &saveptr); line;
+ line = strtok_r(NULL, "\n", &saveptr)) {
+ zlog(priority, "%s%s", prefix, line);
+ }
+
+ if (p != shortbuf)
+ XFREE(MTYPE_TMP, p);
+}
+
+char *log_uptime(time_t uptime, char *buf, size_t nbuf)
+{
+ struct tm tm;
+ time_t difftime = time(NULL);
+ difftime -= uptime;
+ gmtime_r(&difftime, &tm);
+
+ if (difftime < ONE_DAY_SECOND)
+ snprintf(buf, nbuf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min,
+ tm.tm_sec);
+ else if (difftime < ONE_WEEK_SECOND)
+ snprintf(buf, nbuf, "%dd%02dh%02dm", tm.tm_yday, tm.tm_hour,
+ tm.tm_min);
+ else
+ snprintf(buf, nbuf, "%02dw%dd%02dh", tm.tm_yday / 7,
+ tm.tm_yday - ((tm.tm_yday / 7) * 7), tm.tm_hour);
+
+ return buf;
+}
+
+void vty_multiline(struct vty *vty, const char *prefix, const char *format, ...)
+{
+ char shortbuf[256];
+ va_list ap;
+ char *p;
+
+ va_start(ap, format);
+ p = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap);
+ va_end(ap);
+
+ if (!p)
+ return;
+
+ char *saveptr = NULL;
+ for (char *line = strtok_r(p, "\n", &saveptr); line;
+ line = strtok_r(NULL, "\n", &saveptr)) {
+ vty_out(vty, "%s%s\n", prefix, line);
+ }
+
+ if (p != shortbuf)
+ XFREE(MTYPE_TMP, p);
+}
+
+void vty_out_timestr(struct vty *vty, time_t uptime)
+{
+ time_t difftime = time(NULL);
+ char buf[MONOTIME_STRLEN];
+
+ difftime -= uptime;
+
+ frrtime_to_interval(difftime, buf, sizeof(buf));
+
+ vty_out(vty, "%s ago", buf);
+}
diff --git a/isisd/isis_misc.h b/isisd/isis_misc.h
new file mode 100644
index 0000000..3a1d136
--- /dev/null
+++ b/isisd/isis_misc.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_misc.h
+ * Miscellanous routines
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#ifndef _ZEBRA_ISIS_MISC_H
+#define _ZEBRA_ISIS_MISC_H
+
+int string2circuit_t(const char *);
+const char *circuit_t2string(int);
+const char *circuit_state2string(int state);
+const char *circuit_type2string(int type);
+const char *syst2string(int);
+const char *isis_hello_padding2string(int hello_padding_type);
+struct in_addr newprefix2inaddr(uint8_t *prefix_start, uint8_t prefix_masklen);
+/*
+ * Converting input to memory stored format
+ * return value of 0 indicates wrong input
+ */
+int dotformat2buff(uint8_t *, const char *);
+int sysid2buff(uint8_t *, const char *);
+
+/*
+ * Printing functions
+ */
+const char *time2string(uint32_t);
+const char *nlpid2str(uint8_t nlpid);
+/* typedef struct nlpids nlpids; */
+char *nlpid2string(struct nlpids *);
+const char *print_sys_hostname(const uint8_t *sysid);
+void zlog_dump_data(void *data, int len);
+
+/*
+ * misc functions
+ */
+unsigned long isis_jitter(unsigned long timer, unsigned long jitter);
+
+/*
+ * macros
+ */
+#define GETSYSID(A) \
+ (A->area_addr + (A->addr_len - (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN)))
+
+/* used for calculating nice string representation instead of plain seconds */
+
+#define SECS_PER_MINUTE 60
+#define SECS_PER_HOUR 3600
+#define SECS_PER_DAY 86400
+#define SECS_PER_WEEK 604800
+#define SECS_PER_MONTH 2628000
+#define SECS_PER_YEAR 31536000
+
+enum { ISIS_UI_LEVEL_BRIEF,
+ ISIS_UI_LEVEL_DETAIL,
+ ISIS_UI_LEVEL_EXTENSIVE,
+};
+
+#include "lib/log.h"
+void log_multiline(int priority, const char *prefix, const char *format, ...)
+ PRINTFRR(3, 4);
+char *log_uptime(time_t uptime, char *buf, size_t nbuf);
+struct vty;
+void vty_multiline(struct vty *vty, const char *prefix, const char *format, ...)
+ PRINTFRR(3, 4);
+void vty_out_timestr(struct vty *vty, time_t uptime);
+#endif
diff --git a/isisd/isis_mt.c b/isisd/isis_mt.c
new file mode 100644
index 0000000..d04a24d
--- /dev/null
+++ b/isisd/isis_mt.c
@@ -0,0 +1,557 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - Multi Topology Support
+ *
+ * Copyright (C) 2017 Christian Franke
+ *
+ * This file is part of FRRouting (FRR)
+ */
+#include <zebra.h>
+#include "isisd/isisd.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_tlvs.h"
+
+DEFINE_MTYPE_STATIC(ISISD, MT_AREA_SETTING, "ISIS MT Area Setting");
+DEFINE_MTYPE_STATIC(ISISD, MT_CIRCUIT_SETTING, "ISIS MT Circuit Setting");
+DEFINE_MTYPE_STATIC(ISISD, MT_ADJ_INFO, "ISIS MT Adjacency Info");
+
+bool isis_area_ipv6_dstsrc_enabled(struct isis_area *area)
+{
+ struct isis_area_mt_setting *area_mt_setting;
+ area_mt_setting = area_lookup_mt_setting(area, ISIS_MT_IPV6_DSTSRC);
+
+ return (area_mt_setting && area_mt_setting->enabled);
+}
+
+uint16_t isis_area_ipv6_topology(struct isis_area *area)
+{
+ struct isis_area_mt_setting *area_mt_setting;
+ area_mt_setting = area_lookup_mt_setting(area, ISIS_MT_IPV6_UNICAST);
+
+ if (area_mt_setting && area_mt_setting->enabled)
+ return ISIS_MT_IPV6_UNICAST;
+ return ISIS_MT_IPV4_UNICAST;
+}
+
+/* MT naming api */
+const char *isis_mtid2str(uint16_t mtid)
+{
+ static char buf[sizeof("65535")];
+
+ switch (mtid) {
+ case ISIS_MT_STANDARD:
+ return "standard";
+ case ISIS_MT_IPV4_MGMT:
+ return "ipv4-mgmt";
+ case ISIS_MT_IPV6_UNICAST:
+ return "ipv6-unicast";
+ case ISIS_MT_IPV4_MULTICAST:
+ return "ipv4-multicast";
+ case ISIS_MT_IPV6_MULTICAST:
+ return "ipv6-multicast";
+ case ISIS_MT_IPV6_MGMT:
+ return "ipv6-mgmt";
+ case ISIS_MT_IPV6_DSTSRC:
+ return "ipv6-dstsrc";
+ default:
+ snprintf(buf, sizeof(buf), "%hu", mtid);
+ return buf;
+ }
+}
+
+uint16_t isis_str2mtid(const char *name)
+{
+ if (!strcmp(name, "ipv4-unicast"))
+ return ISIS_MT_IPV4_UNICAST;
+ if (!strcmp(name, "standard"))
+ return ISIS_MT_STANDARD;
+ if (!strcmp(name, "ipv4-mgmt"))
+ return ISIS_MT_IPV4_MGMT;
+ if (!strcmp(name, "ipv6-unicast"))
+ return ISIS_MT_IPV6_UNICAST;
+ if (!strcmp(name, "ipv4-multicast"))
+ return ISIS_MT_IPV4_MULTICAST;
+ if (!strcmp(name, "ipv6-multicast"))
+ return ISIS_MT_IPV6_MULTICAST;
+ if (!strcmp(name, "ipv6-mgmt"))
+ return ISIS_MT_IPV6_MGMT;
+ if (!strcmp(name, "ipv6-dstsrc"))
+ return ISIS_MT_IPV6_DSTSRC;
+ return -1;
+}
+
+/* General MT settings api */
+
+struct mt_setting {
+ ISIS_MT_INFO_FIELDS;
+};
+
+static void *lookup_mt_setting(struct list *mt_list, uint16_t mtid)
+{
+ struct listnode *node;
+ struct mt_setting *setting;
+
+ for (ALL_LIST_ELEMENTS_RO(mt_list, node, setting)) {
+ if (setting->mtid == mtid)
+ return setting;
+ }
+ return NULL;
+}
+
+static void add_mt_setting(struct list **mt_list, void *setting)
+{
+ if (!*mt_list)
+ *mt_list = list_new();
+ listnode_add(*mt_list, setting);
+}
+
+/* Area specific MT settings api */
+
+struct isis_area_mt_setting *area_lookup_mt_setting(struct isis_area *area,
+ uint16_t mtid)
+{
+ return lookup_mt_setting(area->mt_settings, mtid);
+}
+
+struct isis_area_mt_setting *area_new_mt_setting(struct isis_area *area,
+ uint16_t mtid)
+{
+ struct isis_area_mt_setting *setting;
+
+ setting = XCALLOC(MTYPE_MT_AREA_SETTING, sizeof(*setting));
+ setting->mtid = mtid;
+ return setting;
+}
+
+static void area_free_mt_setting(void *setting)
+{
+ XFREE(MTYPE_MT_AREA_SETTING, setting);
+}
+
+void area_add_mt_setting(struct isis_area *area,
+ struct isis_area_mt_setting *setting)
+{
+ add_mt_setting(&area->mt_settings, setting);
+}
+
+void area_mt_init(struct isis_area *area)
+{
+ struct isis_area_mt_setting *v4_unicast_setting;
+
+ /* MTID 0 is always enabled */
+ v4_unicast_setting = area_new_mt_setting(area, ISIS_MT_IPV4_UNICAST);
+ v4_unicast_setting->enabled = true;
+ add_mt_setting(&area->mt_settings, v4_unicast_setting);
+ area->mt_settings->del = area_free_mt_setting;
+}
+
+void area_mt_finish(struct isis_area *area)
+{
+ list_delete(&area->mt_settings);
+}
+
+struct isis_area_mt_setting *area_get_mt_setting(struct isis_area *area,
+ uint16_t mtid)
+{
+ struct isis_area_mt_setting *setting;
+
+ setting = area_lookup_mt_setting(area, mtid);
+ if (!setting) {
+ setting = area_new_mt_setting(area, mtid);
+ area_add_mt_setting(area, setting);
+ }
+ return setting;
+}
+
+int area_write_mt_settings(struct isis_area *area, struct vty *vty)
+{
+ int written = 0;
+ struct listnode *node;
+ struct isis_area_mt_setting *setting;
+
+ for (ALL_LIST_ELEMENTS_RO(area->mt_settings, node, setting)) {
+ const char *name = isis_mtid2str(setting->mtid);
+ if (name && setting->enabled) {
+ if (setting->mtid == ISIS_MT_IPV4_UNICAST)
+ continue; /* always enabled, no need to write
+ out config */
+ vty_out(vty, " topology %s%s\n", name,
+ setting->overload ? " overload" : "");
+ written++;
+ }
+ }
+ return written;
+}
+
+bool area_is_mt(struct isis_area *area)
+{
+ struct listnode *node, *node2;
+ struct isis_area_mt_setting *setting;
+ struct isis_circuit *circuit;
+ struct isis_circuit_mt_setting *csetting;
+
+ for (ALL_LIST_ELEMENTS_RO(area->mt_settings, node, setting)) {
+ if (setting->enabled && setting->mtid != ISIS_MT_IPV4_UNICAST)
+ return true;
+ }
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
+ for (ALL_LIST_ELEMENTS_RO(circuit->mt_settings, node2,
+ csetting)) {
+ if (!csetting->enabled
+ && csetting->mtid == ISIS_MT_IPV4_UNICAST)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+struct isis_area_mt_setting **area_mt_settings(struct isis_area *area,
+ unsigned int *mt_count)
+{
+ static unsigned int size = 0;
+ static struct isis_area_mt_setting **rv = NULL;
+
+ unsigned int count = 0;
+ struct listnode *node;
+ struct isis_area_mt_setting *setting;
+
+ for (ALL_LIST_ELEMENTS_RO(area->mt_settings, node, setting)) {
+ if (!setting->enabled)
+ continue;
+
+ count++;
+ if (count > size) {
+ rv = XREALLOC(MTYPE_TMP, rv, count * sizeof(*rv));
+ size = count;
+ }
+ rv[count - 1] = setting;
+ }
+
+ *mt_count = count;
+ return rv;
+}
+
+/* Circuit specific MT settings api */
+
+struct isis_circuit_mt_setting *
+circuit_lookup_mt_setting(struct isis_circuit *circuit, uint16_t mtid)
+{
+ return lookup_mt_setting(circuit->mt_settings, mtid);
+}
+
+struct isis_circuit_mt_setting *
+circuit_new_mt_setting(struct isis_circuit *circuit, uint16_t mtid)
+{
+ struct isis_circuit_mt_setting *setting;
+
+ setting = XCALLOC(MTYPE_MT_CIRCUIT_SETTING, sizeof(*setting));
+ setting->mtid = mtid;
+ setting->enabled = true; /* Enabled is default for circuit */
+ return setting;
+}
+
+static void circuit_free_mt_setting(void *setting)
+{
+ XFREE(MTYPE_MT_CIRCUIT_SETTING, setting);
+}
+
+void circuit_add_mt_setting(struct isis_circuit *circuit,
+ struct isis_circuit_mt_setting *setting)
+{
+ add_mt_setting(&circuit->mt_settings, setting);
+}
+
+void circuit_mt_init(struct isis_circuit *circuit)
+{
+ circuit->mt_settings = list_new();
+ circuit->mt_settings->del = circuit_free_mt_setting;
+}
+
+void circuit_mt_finish(struct isis_circuit *circuit)
+{
+ list_delete(&circuit->mt_settings);
+}
+
+struct isis_circuit_mt_setting *
+circuit_get_mt_setting(struct isis_circuit *circuit, uint16_t mtid)
+{
+ struct isis_circuit_mt_setting *setting;
+
+ setting = circuit_lookup_mt_setting(circuit, mtid);
+ if (!setting) {
+ setting = circuit_new_mt_setting(circuit, mtid);
+ circuit_add_mt_setting(circuit, setting);
+ }
+ return setting;
+}
+
+#ifdef FABRICD
+static int circuit_write_mt_settings(struct isis_circuit *circuit,
+ struct vty *vty)
+{
+ int written = 0;
+ struct listnode *node;
+ struct isis_circuit_mt_setting *setting;
+
+ for (ALL_LIST_ELEMENTS_RO(circuit->mt_settings, node, setting)) {
+ const char *name = isis_mtid2str(setting->mtid);
+ if (name && !setting->enabled) {
+ vty_out(vty, " no " PROTO_NAME " topology %s\n", name);
+ written++;
+ }
+ }
+ return written;
+}
+#endif
+
+struct isis_circuit_mt_setting **
+circuit_mt_settings(struct isis_circuit *circuit, unsigned int *mt_count)
+{
+ static unsigned int size = 0;
+ static struct isis_circuit_mt_setting **rv = NULL;
+
+ struct isis_area_mt_setting **area_settings;
+ unsigned int area_count;
+
+ unsigned int count = 0;
+
+ struct listnode *node;
+ struct isis_circuit_mt_setting *setting;
+
+ area_settings = area_mt_settings(circuit->area, &area_count);
+
+ for (unsigned int i = 0; i < area_count; i++) {
+ for (ALL_LIST_ELEMENTS_RO(circuit->mt_settings, node,
+ setting)) {
+ if (setting->mtid != area_settings[i]->mtid)
+ continue;
+ break;
+ }
+ if (!setting)
+ setting = circuit_get_mt_setting(
+ circuit, area_settings[i]->mtid);
+
+ if (!setting->enabled)
+ continue;
+
+ count++;
+ if (count > size) {
+ rv = XREALLOC(MTYPE_TMP, rv, count * sizeof(*rv));
+ size = count;
+ }
+ rv[count - 1] = setting;
+ }
+
+ *mt_count = count;
+ return rv;
+}
+
+/* ADJ specific MT API */
+static void adj_mt_set(struct isis_adjacency *adj, unsigned int index,
+ uint16_t mtid)
+{
+ if (adj->mt_count < index + 1) {
+ adj->mt_set = XREALLOC(MTYPE_MT_ADJ_INFO, adj->mt_set,
+ (index + 1) * sizeof(*adj->mt_set));
+ adj->mt_count = index + 1;
+ }
+ adj->mt_set[index] = mtid;
+}
+
+bool tlvs_to_adj_mt_set(struct isis_tlvs *tlvs, bool v4_usable, bool v6_usable,
+ struct isis_adjacency *adj)
+{
+ struct isis_circuit_mt_setting **mt_settings;
+ unsigned int circuit_mt_count;
+
+ unsigned int intersect_count = 0;
+
+ uint16_t *old_mt_set = NULL;
+ unsigned int old_mt_count;
+
+ old_mt_count = adj->mt_count;
+ if (old_mt_count) {
+ old_mt_set =
+ XCALLOC(MTYPE_TMP, old_mt_count * sizeof(*old_mt_set));
+ memcpy(old_mt_set, adj->mt_set,
+ old_mt_count * sizeof(*old_mt_set));
+ }
+
+ mt_settings = circuit_mt_settings(adj->circuit, &circuit_mt_count);
+ for (unsigned int i = 0; i < circuit_mt_count; i++) {
+ if (!tlvs->mt_router_info.count
+ && !tlvs->mt_router_info_empty) {
+ /* Other end does not have MT enabled */
+ if (mt_settings[i]->mtid == ISIS_MT_IPV4_UNICAST
+ && (v4_usable || v6_usable))
+ adj_mt_set(adj, intersect_count++,
+ ISIS_MT_IPV4_UNICAST);
+ } else {
+ struct isis_mt_router_info *info_head;
+
+ info_head = (struct isis_mt_router_info *)
+ tlvs->mt_router_info.head;
+ for (struct isis_mt_router_info *info = info_head; info;
+ info = info->next) {
+ if (mt_settings[i]->mtid == info->mtid) {
+ bool usable;
+ switch (info->mtid) {
+ case ISIS_MT_IPV4_UNICAST:
+ case ISIS_MT_IPV4_MGMT:
+ case ISIS_MT_IPV4_MULTICAST:
+ usable = v4_usable;
+ break;
+ case ISIS_MT_IPV6_UNICAST:
+ case ISIS_MT_IPV6_MGMT:
+ case ISIS_MT_IPV6_MULTICAST:
+ usable = v6_usable;
+ break;
+ default:
+ usable = true;
+ break;
+ }
+ if (usable)
+ adj_mt_set(adj,
+ intersect_count++,
+ info->mtid);
+ }
+ }
+ }
+ }
+ adj->mt_count = intersect_count;
+
+ bool changed = false;
+
+ if (adj->mt_count != old_mt_count)
+ changed = true;
+
+ if (!changed && old_mt_count
+ && memcmp(adj->mt_set, old_mt_set,
+ old_mt_count * sizeof(*old_mt_set)))
+ changed = true;
+
+ if (old_mt_count)
+ XFREE(MTYPE_TMP, old_mt_set);
+
+ return changed;
+}
+
+bool adj_has_mt(struct isis_adjacency *adj, uint16_t mtid)
+{
+ for (unsigned int i = 0; i < adj->mt_count; i++)
+ if (adj->mt_set[i] == mtid)
+ return true;
+ return false;
+}
+
+void adj_mt_finish(struct isis_adjacency *adj)
+{
+ XFREE(MTYPE_MT_ADJ_INFO, adj->mt_set);
+ adj->mt_count = 0;
+}
+
+static void mt_set_add(uint16_t **mt_set, unsigned int *size,
+ unsigned int *index, uint16_t mtid)
+{
+ for (unsigned int i = 0; i < *index; i++) {
+ if ((*mt_set)[i] == mtid)
+ return;
+ }
+
+ if (*index >= *size) {
+ *mt_set = XREALLOC(MTYPE_TMP, *mt_set,
+ sizeof(**mt_set) * ((*index) + 1));
+ *size = (*index) + 1;
+ }
+
+ (*mt_set)[*index] = mtid;
+ *index = (*index) + 1;
+}
+
+static uint16_t *circuit_bcast_mt_set(struct isis_circuit *circuit, int level,
+ unsigned int *mt_count)
+{
+ static uint16_t *rv;
+ static unsigned int size;
+ struct listnode *node;
+ struct isis_adjacency *adj;
+
+ unsigned int count = 0;
+
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST) {
+ *mt_count = 0;
+ return NULL;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(circuit->u.bc.adjdb[level - 1], node, adj)) {
+ if (adj->adj_state != ISIS_ADJ_UP)
+ continue;
+ for (unsigned int i = 0; i < adj->mt_count; i++)
+ mt_set_add(&rv, &size, &count, adj->mt_set[i]);
+ }
+
+ *mt_count = count;
+ return rv;
+}
+
+static void tlvs_add_mt_set(struct isis_area *area, struct isis_tlvs *tlvs,
+ unsigned int mt_count, uint16_t *mt_set,
+ uint8_t *id, uint32_t metric,
+ struct isis_ext_subtlvs *ext)
+{
+ /* Check if MT is enable for this area */
+ if (!area_is_mt(area)) {
+ lsp_debug(
+ "ISIS (%s): Adding %pPN as te-style neighbor (MT disable)",
+ area->area_tag, id);
+ isis_tlvs_add_extended_reach(tlvs, ISIS_MT_DISABLE, id, metric,
+ ext);
+ return;
+ }
+
+ /* Process Multi-Topology */
+ for (unsigned int i = 0; i < mt_count; i++) {
+ uint16_t mtid = mt_set[i];
+ if (mt_set[i] == ISIS_MT_IPV4_UNICAST) {
+ lsp_debug("ISIS (%s): Adding %pPN as te-style neighbor",
+ area->area_tag, id);
+ } else {
+ lsp_debug(
+ "ISIS (%s): Adding %pPN as mt-style neighbor for %s",
+ area->area_tag, id, isis_mtid2str(mtid));
+ }
+ isis_tlvs_add_extended_reach(tlvs, mtid, id, metric, ext);
+ }
+}
+
+void tlvs_add_mt_bcast(struct isis_tlvs *tlvs, struct isis_circuit *circuit,
+ int level, uint8_t *id, uint32_t metric)
+{
+ unsigned int mt_count;
+ uint16_t *mt_set = circuit_bcast_mt_set(circuit, level, &mt_count);
+
+ tlvs_add_mt_set(circuit->area, tlvs, mt_count, mt_set, id, metric,
+ circuit->ext);
+}
+
+void tlvs_add_mt_p2p(struct isis_tlvs *tlvs, struct isis_circuit *circuit,
+ uint8_t *id, uint32_t metric)
+{
+ struct isis_adjacency *adj = circuit->u.p2p.neighbor;
+
+ tlvs_add_mt_set(circuit->area, tlvs, adj->mt_count, adj->mt_set, id,
+ metric, circuit->ext);
+}
+
+void mt_init(void)
+{
+#ifdef FABRICD
+ hook_register(isis_circuit_config_write,
+ circuit_write_mt_settings);
+#endif
+}
diff --git a/isisd/isis_mt.h b/isisd/isis_mt.h
new file mode 100644
index 0000000..9b0df9b
--- /dev/null
+++ b/isisd/isis_mt.h
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - Multi Topology Support
+ *
+ * Copyright (C) 2017 Christian Franke
+ *
+ * This file is part of FRRouting (FRR)
+ */
+#ifndef ISIS_MT_H
+#define ISIS_MT_H
+
+#define ISIS_MT_MASK 0x0fff
+#define ISIS_MT_OL_MASK 0x8000
+#define ISIS_MT_AT_MASK 0x4000
+
+#define ISIS_MT_IPV4_UNICAST 0
+#define ISIS_MT_STANDARD ISIS_MT_IPV4_UNICAST
+#define ISIS_MT_IPV4_MGMT 1
+#define ISIS_MT_IPV6_UNICAST 2
+#define ISIS_MT_IPV4_MULTICAST 3
+#define ISIS_MT_IPV6_MULTICAST 4
+#define ISIS_MT_IPV6_MGMT 5
+#define ISIS_MT_IPV6_DSTSRC 3996 /* FIXME: IANA */
+/* Use first Reserved Flag to indicate that there is no MT Topology active */
+#define ISIS_MT_DISABLE 4096
+
+#define ISIS_MT_NAMES \
+ "<standard" \
+ "|ipv4-mgmt" \
+ "|ipv6-unicast" \
+ "|ipv4-multicast" \
+ "|ipv6-multicast" \
+ "|ipv6-mgmt" \
+ "|ipv6-dstsrc" \
+ ">"
+
+#define ISIS_MT_DESCRIPTIONS \
+ "IPv4 unicast topology\n" \
+ "IPv4 management topology\n" \
+ "IPv6 unicast topology\n" \
+ "IPv4 multicast topology\n" \
+ "IPv6 multicast topology\n" \
+ "IPv6 management topology\n" \
+ "IPv6 dst-src topology\n" \
+ ""
+
+#define ISIS_MT_INFO_FIELDS uint16_t mtid;
+
+struct list;
+
+struct isis_area_mt_setting {
+ ISIS_MT_INFO_FIELDS
+ bool enabled;
+ bool overload;
+};
+
+struct isis_circuit_mt_setting {
+ ISIS_MT_INFO_FIELDS
+ bool enabled;
+};
+
+const char *isis_mtid2str(uint16_t mtid);
+uint16_t isis_str2mtid(const char *name);
+
+struct isis_adjacency;
+struct isis_area;
+struct isis_circuit;
+struct tlvs;
+struct te_is_neigh;
+struct isis_tlvs;
+
+bool isis_area_ipv6_dstsrc_enabled(struct isis_area *area);
+
+uint16_t isis_area_ipv6_topology(struct isis_area *area);
+
+struct isis_area_mt_setting *area_lookup_mt_setting(struct isis_area *area,
+ uint16_t mtid);
+struct isis_area_mt_setting *area_new_mt_setting(struct isis_area *area,
+ uint16_t mtid);
+void area_add_mt_setting(struct isis_area *area,
+ struct isis_area_mt_setting *setting);
+
+void area_mt_init(struct isis_area *area);
+void area_mt_finish(struct isis_area *area);
+struct isis_area_mt_setting *area_get_mt_setting(struct isis_area *area,
+ uint16_t mtid);
+int area_write_mt_settings(struct isis_area *area, struct vty *vty);
+bool area_is_mt(struct isis_area *area);
+struct isis_area_mt_setting **area_mt_settings(struct isis_area *area,
+ unsigned int *mt_count);
+
+struct isis_circuit_mt_setting *
+circuit_lookup_mt_setting(struct isis_circuit *circuit, uint16_t mtid);
+struct isis_circuit_mt_setting *
+circuit_new_mt_setting(struct isis_circuit *circuit, uint16_t mtid);
+void circuit_add_mt_setting(struct isis_circuit *circuit,
+ struct isis_circuit_mt_setting *setting);
+void circuit_mt_init(struct isis_circuit *circuit);
+void circuit_mt_finish(struct isis_circuit *circuit);
+struct isis_circuit_mt_setting *
+circuit_get_mt_setting(struct isis_circuit *circuit, uint16_t mtid);
+struct isis_circuit_mt_setting **
+circuit_mt_settings(struct isis_circuit *circuit, unsigned int *mt_count);
+bool tlvs_to_adj_mt_set(struct isis_tlvs *tlvs, bool v4_usable, bool v6_usable,
+ struct isis_adjacency *adj);
+bool adj_has_mt(struct isis_adjacency *adj, uint16_t mtid);
+void adj_mt_finish(struct isis_adjacency *adj);
+void tlvs_add_mt_bcast(struct isis_tlvs *tlvs, struct isis_circuit *circuit,
+ int level, uint8_t *id, uint32_t metric);
+void tlvs_add_mt_p2p(struct isis_tlvs *tlvs, struct isis_circuit *circuit,
+ uint8_t *id, uint32_t metric);
+void mt_init(void);
+#endif
diff --git a/isisd/isis_nb.c b/isisd/isis_nb.c
new file mode 100644
index 0000000..16cafa2
--- /dev/null
+++ b/isisd/isis_nb.c
@@ -0,0 +1,1468 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 Volta Networks
+ * Emanuele Di Pascale
+ */
+
+#include <zebra.h>
+
+#include "northbound.h"
+#include "libfrr.h"
+
+#include "isisd/isis_nb.h"
+
+/* clang-format off */
+const struct frr_yang_module_info frr_isisd_info = {
+ .name = "frr-isisd",
+ .nodes = {
+ {
+ .xpath = "/frr-isisd:isis/instance",
+ .cbs = {
+ .cli_show = cli_show_router_isis,
+ .cli_show_end = cli_show_router_isis_end,
+ .create = isis_instance_create,
+ .destroy = isis_instance_destroy,
+ },
+ .priority = NB_DFLT_PRIORITY - 1,
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/is-type",
+ .cbs = {
+ .cli_show = cli_show_isis_is_type,
+ .modify = isis_instance_is_type_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/area-address",
+ .cbs = {
+ .cli_show = cli_show_isis_area_address,
+ .create = isis_instance_area_address_create,
+ .destroy = isis_instance_area_address_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/dynamic-hostname",
+ .cbs = {
+ .cli_show = cli_show_isis_dynamic_hostname,
+ .modify = isis_instance_dynamic_hostname_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/attach-send",
+ .cbs = {
+ .cli_show = cli_show_isis_attached_send,
+ .modify = isis_instance_attached_send_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/attach-receive-ignore",
+ .cbs = {
+ .cli_show = cli_show_isis_attached_receive,
+ .modify = isis_instance_attached_receive_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/attached",
+ .cbs = {
+ .modify = isis_instance_attached_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/overload/enabled",
+ .cbs = {
+ .cli_show = cli_show_isis_overload,
+ .modify = isis_instance_overload_enabled_modify,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/overload/on-startup",
+ .cbs = {
+ .cli_show = cli_show_isis_overload_on_startup,
+ .modify = isis_instance_overload_on_startup_modify,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/advertise-high-metrics",
+ .cbs = {
+ .cli_show = cli_show_advertise_high_metrics,
+ .modify = isis_instance_advertise_high_metrics_modify,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/metric-style",
+ .cbs = {
+ .cli_show = cli_show_isis_metric_style,
+ .modify = isis_instance_metric_style_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/purge-originator",
+ .cbs = {
+ .cli_show = cli_show_isis_purge_origin,
+ .modify = isis_instance_purge_originator_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/admin-group-send-zero",
+ .cbs = {
+ .cli_show = cli_show_isis_admin_group_send_zero,
+ .modify = isis_instance_admin_group_send_zero_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/asla-legacy-flag",
+ .cbs = {
+ .cli_show = cli_show_isis_asla_legacy_flag,
+ .modify = isis_instance_asla_legacy_flag_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/lsp/mtu",
+ .cbs = {
+ .cli_show = cli_show_isis_lsp_mtu,
+ .modify = isis_instance_lsp_mtu_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/advertise-passive-only",
+ .cbs = {
+ .cli_show = cli_show_advertise_passive_only,
+ .modify = isis_instance_advertise_passive_only_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/lsp/timers",
+ .cbs = {
+ .cli_show = cli_show_isis_lsp_timers,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/lsp/timers/level-1/refresh-interval",
+ .cbs = {
+ .modify = isis_instance_lsp_refresh_interval_level_1_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/lsp/timers/level-1/maximum-lifetime",
+ .cbs = {
+ .modify = isis_instance_lsp_maximum_lifetime_level_1_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/lsp/timers/level-1/generation-interval",
+ .cbs = {
+ .modify = isis_instance_lsp_generation_interval_level_1_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/lsp/timers/level-2/refresh-interval",
+ .cbs = {
+ .modify = isis_instance_lsp_refresh_interval_level_2_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/lsp/timers/level-2/maximum-lifetime",
+ .cbs = {
+ .modify = isis_instance_lsp_maximum_lifetime_level_2_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/lsp/timers/level-2/generation-interval",
+ .cbs = {
+ .modify = isis_instance_lsp_generation_interval_level_2_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay",
+ .cbs = {
+ .apply_finish = ietf_backoff_delay_apply_finish,
+ .cli_show = cli_show_isis_spf_ietf_backoff,
+ .create = isis_instance_spf_ietf_backoff_delay_create,
+ .destroy = isis_instance_spf_ietf_backoff_delay_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/init-delay",
+ .cbs = {
+ .modify = isis_instance_spf_ietf_backoff_delay_init_delay_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/short-delay",
+ .cbs = {
+ .modify = isis_instance_spf_ietf_backoff_delay_short_delay_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/long-delay",
+ .cbs = {
+ .modify = isis_instance_spf_ietf_backoff_delay_long_delay_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/hold-down",
+ .cbs = {
+ .modify = isis_instance_spf_ietf_backoff_delay_hold_down_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/time-to-learn",
+ .cbs = {
+ .modify = isis_instance_spf_ietf_backoff_delay_time_to_learn_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/minimum-interval",
+ .cbs = {
+ .cli_show = cli_show_isis_spf_min_interval,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/minimum-interval/level-1",
+ .cbs = {
+ .modify = isis_instance_spf_minimum_interval_level_1_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/minimum-interval/level-2",
+ .cbs = {
+ .modify = isis_instance_spf_minimum_interval_level_2_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/prefix-priorities/critical/access-list-name",
+ .cbs = {
+ .cli_show = cli_show_isis_spf_prefix_priority,
+ .modify = isis_instance_spf_prefix_priorities_critical_access_list_name_modify,
+ .destroy = isis_instance_spf_prefix_priorities_critical_access_list_name_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/prefix-priorities/high/access-list-name",
+ .cbs = {
+ .cli_show = cli_show_isis_spf_prefix_priority,
+ .modify = isis_instance_spf_prefix_priorities_high_access_list_name_modify,
+ .destroy = isis_instance_spf_prefix_priorities_high_access_list_name_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/spf/prefix-priorities/medium/access-list-name",
+ .cbs = {
+ .cli_show = cli_show_isis_spf_prefix_priority,
+ .modify = isis_instance_spf_prefix_priorities_medium_access_list_name_modify,
+ .destroy = isis_instance_spf_prefix_priorities_medium_access_list_name_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/area-password",
+ .cbs = {
+ .apply_finish = area_password_apply_finish,
+ .cli_show = cli_show_isis_area_pwd,
+ .create = isis_instance_area_password_create,
+ .destroy = isis_instance_area_password_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/area-password/password",
+ .cbs = {
+ .modify = isis_instance_area_password_password_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/area-password/password-type",
+ .cbs = {
+ .modify = isis_instance_area_password_password_type_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/area-password/authenticate-snp",
+ .cbs = {
+ .modify = isis_instance_area_password_authenticate_snp_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/domain-password",
+ .cbs = {
+ .apply_finish = domain_password_apply_finish,
+ .cli_show = cli_show_isis_domain_pwd,
+ .create = isis_instance_domain_password_create,
+ .destroy = isis_instance_domain_password_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/domain-password/password",
+ .cbs = {
+ .modify = isis_instance_domain_password_password_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/domain-password/password-type",
+ .cbs = {
+ .modify = isis_instance_domain_password_password_type_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/domain-password/authenticate-snp",
+ .cbs = {
+ .modify = isis_instance_domain_password_authenticate_snp_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4",
+ .cbs = {
+ .apply_finish = default_info_origin_ipv4_apply_finish,
+ .cli_show = cli_show_isis_def_origin_ipv4,
+ .create = isis_instance_default_information_originate_ipv4_create,
+ .destroy = isis_instance_default_information_originate_ipv4_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4/always",
+ .cbs = {
+ .modify = isis_instance_default_information_originate_ipv4_always_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4/route-map",
+ .cbs = {
+ .destroy = isis_instance_default_information_originate_ipv4_route_map_destroy,
+ .modify = isis_instance_default_information_originate_ipv4_route_map_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4/metric",
+ .cbs = {
+ .modify = isis_instance_default_information_originate_ipv4_metric_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6",
+ .cbs = {
+ .apply_finish = default_info_origin_ipv6_apply_finish,
+ .cli_show = cli_show_isis_def_origin_ipv6,
+ .create = isis_instance_default_information_originate_ipv6_create,
+ .destroy = isis_instance_default_information_originate_ipv6_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6/always",
+ .cbs = {
+ .modify = isis_instance_default_information_originate_ipv6_always_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6/route-map",
+ .cbs = {
+ .destroy = isis_instance_default_information_originate_ipv6_route_map_destroy,
+ .modify = isis_instance_default_information_originate_ipv6_route_map_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6/metric",
+ .cbs = {
+ .modify = isis_instance_default_information_originate_ipv6_metric_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv4",
+ .cbs = {
+ .apply_finish = redistribute_ipv4_apply_finish,
+ .cli_show = cli_show_isis_redistribute_ipv4,
+ .create = isis_instance_redistribute_ipv4_create,
+ .destroy = isis_instance_redistribute_ipv4_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/route-map",
+ .cbs = {
+ .destroy = isis_instance_redistribute_ipv4_route_map_destroy,
+ .modify = isis_instance_redistribute_ipv4_route_map_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/metric",
+ .cbs = {
+ .destroy = isis_instance_redistribute_ipv4_metric_destroy,
+ .modify = isis_instance_redistribute_ipv4_metric_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/table",
+ .cbs = {
+ .cli_show = cli_show_isis_redistribute_ipv4_table,
+ .cli_cmp = cli_cmp_isis_redistribute_table,
+ .create = isis_instance_redistribute_ipv4_table_create,
+ .destroy = isis_instance_redistribute_ipv4_table_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/table/route-map",
+ .cbs = {
+ .destroy = isis_instance_redistribute_ipv4_route_map_destroy,
+ .modify = isis_instance_redistribute_ipv4_route_map_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/table/metric",
+ .cbs = {
+ .modify = isis_instance_redistribute_ipv4_metric_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv6",
+ .cbs = {
+ .apply_finish = redistribute_ipv6_apply_finish,
+ .cli_show = cli_show_isis_redistribute_ipv6,
+ .create = isis_instance_redistribute_ipv6_create,
+ .destroy = isis_instance_redistribute_ipv6_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/route-map",
+ .cbs = {
+ .destroy = isis_instance_redistribute_ipv6_route_map_destroy,
+ .modify = isis_instance_redistribute_ipv6_route_map_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/metric",
+ .cbs = {
+ .destroy = isis_instance_redistribute_ipv6_metric_destroy,
+ .modify = isis_instance_redistribute_ipv6_metric_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/table",
+ .cbs = {
+ .cli_show = cli_show_isis_redistribute_ipv6_table,
+ .cli_cmp = cli_cmp_isis_redistribute_table,
+ .create = isis_instance_redistribute_ipv6_table_create,
+ .destroy = isis_instance_redistribute_ipv6_table_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/table/route-map",
+ .cbs = {
+ .destroy = isis_instance_redistribute_ipv6_route_map_destroy,
+ .modify = isis_instance_redistribute_ipv6_route_map_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/table/metric",
+ .cbs = {
+ .modify = isis_instance_redistribute_ipv6_metric_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-multicast",
+ .cbs = {
+ .cli_show = cli_show_isis_mt_ipv4_multicast,
+ .create = isis_instance_multi_topology_ipv4_multicast_create,
+ .destroy = isis_instance_multi_topology_ipv4_multicast_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-multicast/overload",
+ .cbs = {
+ .modify = isis_instance_multi_topology_ipv4_multicast_overload_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-management",
+ .cbs = {
+ .cli_show = cli_show_isis_mt_ipv4_mgmt,
+ .create = isis_instance_multi_topology_ipv4_management_create,
+ .destroy = isis_instance_multi_topology_ipv4_management_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-management/overload",
+ .cbs = {
+ .modify = isis_instance_multi_topology_ipv4_management_overload_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-unicast",
+ .cbs = {
+ .cli_show = cli_show_isis_mt_ipv6_unicast,
+ .create = isis_instance_multi_topology_ipv6_unicast_create,
+ .destroy = isis_instance_multi_topology_ipv6_unicast_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-unicast/overload",
+ .cbs = {
+ .modify = isis_instance_multi_topology_ipv6_unicast_overload_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-multicast",
+ .cbs = {
+ .cli_show = cli_show_isis_mt_ipv6_multicast,
+ .create = isis_instance_multi_topology_ipv6_multicast_create,
+ .destroy = isis_instance_multi_topology_ipv6_multicast_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-multicast/overload",
+ .cbs = {
+ .modify = isis_instance_multi_topology_ipv6_multicast_overload_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-management",
+ .cbs = {
+ .cli_show = cli_show_isis_mt_ipv6_mgmt,
+ .create = isis_instance_multi_topology_ipv6_management_create,
+ .destroy = isis_instance_multi_topology_ipv6_management_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-management/overload",
+ .cbs = {
+ .modify = isis_instance_multi_topology_ipv6_management_overload_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-dstsrc",
+ .cbs = {
+ .cli_show = cli_show_isis_mt_ipv6_dstsrc,
+ .create = isis_instance_multi_topology_ipv6_dstsrc_create,
+ .destroy = isis_instance_multi_topology_ipv6_dstsrc_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-dstsrc/overload",
+ .cbs = {
+ .modify = isis_instance_multi_topology_ipv6_dstsrc_overload_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/fast-reroute/level-1/lfa/load-sharing",
+ .cbs = {
+ .cli_show = cli_show_isis_frr_lfa_load_sharing,
+ .modify = isis_instance_fast_reroute_level_1_lfa_load_sharing_modify,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/fast-reroute/level-1/lfa/priority-limit",
+ .cbs = {
+ .cli_show = cli_show_isis_frr_lfa_priority_limit,
+ .modify = isis_instance_fast_reroute_level_1_lfa_priority_limit_modify,
+ .destroy = isis_instance_fast_reroute_level_1_lfa_priority_limit_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/fast-reroute/level-1/lfa/tiebreaker",
+ .cbs = {
+ .cli_show = cli_show_isis_frr_lfa_tiebreaker,
+ .create = isis_instance_fast_reroute_level_1_lfa_tiebreaker_create,
+ .destroy = isis_instance_fast_reroute_level_1_lfa_tiebreaker_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/fast-reroute/level-1/lfa/tiebreaker/type",
+ .cbs = {
+ .modify = isis_instance_fast_reroute_level_1_lfa_tiebreaker_type_modify,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/fast-reroute/level-1/remote-lfa/prefix-list",
+ .cbs = {
+ .cli_show = cli_show_isis_frr_remote_lfa_plist,
+ .modify = isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_modify,
+ .destroy = isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/fast-reroute/level-2/lfa/load-sharing",
+ .cbs = {
+ .cli_show = cli_show_isis_frr_lfa_load_sharing,
+ .modify = isis_instance_fast_reroute_level_2_lfa_load_sharing_modify,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/fast-reroute/level-2/lfa/priority-limit",
+ .cbs = {
+ .cli_show = cli_show_isis_frr_lfa_priority_limit,
+ .modify = isis_instance_fast_reroute_level_2_lfa_priority_limit_modify,
+ .destroy = isis_instance_fast_reroute_level_2_lfa_priority_limit_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/fast-reroute/level-2/lfa/tiebreaker",
+ .cbs = {
+ .cli_show = cli_show_isis_frr_lfa_tiebreaker,
+ .create = isis_instance_fast_reroute_level_2_lfa_tiebreaker_create,
+ .destroy = isis_instance_fast_reroute_level_2_lfa_tiebreaker_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/fast-reroute/level-2/lfa/tiebreaker/type",
+ .cbs = {
+ .modify = isis_instance_fast_reroute_level_2_lfa_tiebreaker_type_modify,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/fast-reroute/level-2/remote-lfa/prefix-list",
+ .cbs = {
+ .cli_show = cli_show_isis_frr_remote_lfa_plist,
+ .modify = isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_modify,
+ .destroy = isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/log-adjacency-changes",
+ .cbs = {
+ .cli_show = cli_show_isis_log_adjacency,
+ .modify = isis_instance_log_adjacency_changes_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/log-pdu-drops",
+ .cbs = {
+ .cli_show = cli_show_isis_log_pdu_drops,
+ .modify = isis_instance_log_pdu_drops_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/mpls-te",
+ .cbs = {
+ .cli_show = cli_show_isis_mpls_te,
+ .create = isis_instance_mpls_te_create,
+ .destroy = isis_instance_mpls_te_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/mpls-te/router-address",
+ .cbs = {
+ .cli_show = cli_show_isis_mpls_te_router_addr,
+ .destroy = isis_instance_mpls_te_router_address_destroy,
+ .modify = isis_instance_mpls_te_router_address_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/mpls-te/router-address-v6",
+ .cbs = {
+ .cli_show = cli_show_isis_mpls_te_router_addr_ipv6,
+ .destroy = isis_instance_mpls_te_router_address_ipv6_destroy,
+ .modify = isis_instance_mpls_te_router_address_ipv6_modify,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/mpls-te/export",
+ .cbs = {
+ .cli_show = cli_show_isis_mpls_te_export,
+ .modify = isis_instance_mpls_te_export_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/enabled",
+ .cbs = {
+ .modify = isis_instance_segment_routing_enabled_modify,
+ .cli_show = cli_show_isis_sr_enabled,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks",
+ .cbs = {
+ .pre_validate = isis_instance_segment_routing_label_blocks_pre_validate,
+ .cli_show = cli_show_isis_label_blocks,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srgb",
+ .cbs = {
+ .apply_finish = isis_instance_segment_routing_srgb_apply_finish,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srgb/lower-bound",
+ .cbs = {
+ .modify = isis_instance_segment_routing_srgb_lower_bound_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srgb/upper-bound",
+ .cbs = {
+ .modify = isis_instance_segment_routing_srgb_upper_bound_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srlb",
+ .cbs = {
+ .apply_finish = isis_instance_segment_routing_srlb_apply_finish,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srlb/lower-bound",
+ .cbs = {
+ .modify = isis_instance_segment_routing_srlb_lower_bound_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srlb/upper-bound",
+ .cbs = {
+ .modify = isis_instance_segment_routing_srlb_upper_bound_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/msd/node-msd",
+ .cbs = {
+ .modify = isis_instance_segment_routing_msd_node_msd_modify,
+ .destroy = isis_instance_segment_routing_msd_node_msd_destroy,
+ .cli_show = cli_show_isis_node_msd,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid",
+ .cbs = {
+ .create = isis_instance_segment_routing_prefix_sid_map_prefix_sid_create,
+ .destroy = isis_instance_segment_routing_prefix_sid_map_prefix_sid_destroy,
+ .pre_validate = isis_instance_segment_routing_prefix_sid_map_prefix_sid_pre_validate,
+ .apply_finish = isis_instance_segment_routing_prefix_sid_map_prefix_sid_apply_finish,
+ .cli_show = cli_show_isis_prefix_sid,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/sid-value-type",
+ .cbs = {
+ .modify = isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_type_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/sid-value",
+ .cbs = {
+ .modify = isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/last-hop-behavior",
+ .cbs = {
+ .modify = isis_instance_segment_routing_prefix_sid_map_prefix_sid_last_hop_behavior_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/n-flag-clear",
+ .cbs = {
+ .modify = isis_instance_segment_routing_prefix_sid_map_prefix_sid_n_flag_clear_modify,
+ }
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid",
+ .cbs = {
+ .create = isis_instance_segment_routing_algorithm_prefix_sid_create,
+ .destroy = isis_instance_segment_routing_algorithm_prefix_sid_destroy,
+ .pre_validate = isis_instance_segment_routing_algorithm_prefix_sid_pre_validate,
+ .apply_finish = isis_instance_segment_routing_algorithm_prefix_sid_apply_finish,
+ .cli_show = cli_show_isis_prefix_sid_algorithm,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/sid-value-type",
+ .cbs = {
+ .modify = isis_instance_segment_routing_algorithm_prefix_sid_sid_value_type_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/sid-value",
+ .cbs = {
+ .modify = isis_instance_segment_routing_algorithm_prefix_sid_sid_value_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/last-hop-behavior",
+ .cbs = {
+ .modify = isis_instance_segment_routing_algorithm_prefix_sid_last_hop_behavior_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/n-flag-clear",
+ .cbs = {
+ .modify = isis_instance_segment_routing_algorithm_prefix_sid_n_flag_clear_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo",
+ .cbs = {
+ .cli_show = cli_show_isis_flex_algo,
+ .cli_show_end = cli_show_isis_flex_algo_end,
+ .create = isis_instance_flex_algo_create,
+ .destroy = isis_instance_flex_algo_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/advertise-definition",
+ .cbs = {
+ .modify = isis_instance_flex_algo_advertise_definition_modify,
+ .destroy = isis_instance_flex_algo_advertise_definition_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/affinity-include-alls/affinity-include-all",
+ .cbs = {
+ .create = isis_instance_flex_algo_affinity_include_all_create,
+ .destroy = isis_instance_flex_algo_affinity_include_all_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/affinity-include-anies/affinity-include-any",
+ .cbs = {
+ .create = isis_instance_flex_algo_affinity_include_any_create,
+ .destroy = isis_instance_flex_algo_affinity_include_any_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/affinity-exclude-anies/affinity-exclude-any",
+ .cbs = {
+ .create = isis_instance_flex_algo_affinity_exclude_any_create,
+ .destroy = isis_instance_flex_algo_affinity_exclude_any_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/prefix-metric",
+ .cbs = {
+ .create = isis_instance_flex_algo_prefix_metric_create,
+ .destroy = isis_instance_flex_algo_prefix_metric_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/metric-type",
+ .cbs = {
+ .modify = isis_instance_flex_algo_metric_type_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/dplane-sr-mpls",
+ .cbs = {
+ .create = isis_instance_flex_algo_dplane_sr_mpls_create,
+ .destroy = isis_instance_flex_algo_dplane_sr_mpls_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/dplane-srv6",
+ .cbs = {
+ .create = isis_instance_flex_algo_dplane_srv6_create,
+ .destroy = isis_instance_flex_algo_dplane_srv6_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/dplane-ip",
+ .cbs = {
+ .create = isis_instance_flex_algo_dplane_ip_create,
+ .destroy = isis_instance_flex_algo_dplane_ip_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/priority",
+ .cbs = {
+ .modify = isis_instance_flex_algo_priority_modify,
+ .destroy = isis_instance_flex_algo_priority_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/enabled",
+ .cbs = {
+ .modify = isis_instance_segment_routing_srv6_enabled_modify,
+ .cli_show = cli_show_isis_srv6_enabled,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/locator",
+ .cbs = {
+ .modify = isis_instance_segment_routing_srv6_locator_modify,
+ .destroy = isis_instance_segment_routing_srv6_locator_destroy,
+ .cli_show = cli_show_isis_srv6_locator,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-segs-left",
+ .cbs = {
+ .modify = isis_instance_segment_routing_srv6_msd_node_msd_max_segs_left_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-pop",
+ .cbs = {
+ .modify = isis_instance_segment_routing_srv6_msd_node_msd_max_end_pop_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-h-encaps",
+ .cbs = {
+ .modify = isis_instance_segment_routing_srv6_msd_node_msd_max_h_encaps_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-d",
+ .cbs = {
+ .modify = isis_instance_segment_routing_srv6_msd_node_msd_max_end_d_modify,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd",
+ .cbs = {
+ .cli_show = cli_show_isis_srv6_node_msd,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/interface",
+ .cbs = {
+ .modify = isis_instance_segment_routing_srv6_interface_modify,
+ .cli_show = cli_show_isis_srv6_interface,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/mpls/ldp-sync",
+ .cbs = {
+ .cli_show = cli_show_isis_mpls_ldp_sync,
+ .create = isis_instance_mpls_ldp_sync_create,
+ .destroy = isis_instance_mpls_ldp_sync_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-isisd:isis/instance/mpls/ldp-sync/holddown",
+ .cbs = {
+ .cli_show = cli_show_isis_mpls_ldp_sync_holddown,
+ .modify = isis_instance_mpls_ldp_sync_holddown_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis",
+ .cbs = {
+ .create = lib_interface_isis_create,
+ .destroy = lib_interface_isis_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/area-tag",
+ .cbs = {
+ .modify = lib_interface_isis_area_tag_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/circuit-type",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_circ_type,
+ .modify = lib_interface_isis_circuit_type_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/ipv4-routing",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_ipv4,
+ .modify = lib_interface_isis_ipv4_routing_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/ipv6-routing",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_ipv6,
+ .modify = lib_interface_isis_ipv6_routing_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring",
+ .cbs = {
+ .apply_finish = lib_interface_isis_bfd_monitoring_apply_finish,
+ .cli_show = cli_show_ip_isis_bfd_monitoring,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring/enabled",
+ .cbs = {
+ .modify = lib_interface_isis_bfd_monitoring_enabled_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring/profile",
+ .cbs = {
+ .modify = lib_interface_isis_bfd_monitoring_profile_modify,
+ .destroy = lib_interface_isis_bfd_monitoring_profile_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_csnp_interval,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-1",
+ .cbs = {
+ .modify = lib_interface_isis_csnp_interval_level_1_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-2",
+ .cbs = {
+ .modify = lib_interface_isis_csnp_interval_level_2_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_psnp_interval,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-1",
+ .cbs = {
+ .modify = lib_interface_isis_psnp_interval_level_1_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-2",
+ .cbs = {
+ .modify = lib_interface_isis_psnp_interval_level_2_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/padding",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_hello_padding,
+ .modify = lib_interface_isis_hello_padding_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/interval",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_hello_interval,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-1",
+ .cbs = {
+ .modify = lib_interface_isis_hello_interval_level_1_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-2",
+ .cbs = {
+ .modify = lib_interface_isis_hello_interval_level_2_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_hello_multi,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-1",
+ .cbs = {
+ .modify = lib_interface_isis_hello_multiplier_level_1_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-2",
+ .cbs = {
+ .modify = lib_interface_isis_hello_multiplier_level_2_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/metric",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_metric,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/metric/level-1",
+ .cbs = {
+ .modify = lib_interface_isis_metric_level_1_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/metric/level-2",
+ .cbs = {
+ .modify = lib_interface_isis_metric_level_2_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/priority",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_priority,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/priority/level-1",
+ .cbs = {
+ .modify = lib_interface_isis_priority_level_1_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/priority/level-2",
+ .cbs = {
+ .modify = lib_interface_isis_priority_level_2_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/network-type",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_network_type,
+ .modify = lib_interface_isis_network_type_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/passive",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_passive,
+ .modify = lib_interface_isis_passive_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/password",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_password,
+ .create = lib_interface_isis_password_create,
+ .destroy = lib_interface_isis_password_destroy,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/password/password",
+ .cbs = {
+ .modify = lib_interface_isis_password_password_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/password/password-type",
+ .cbs = {
+ .modify = lib_interface_isis_password_password_type_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/disable-three-way-handshake",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_threeway_shake,
+ .modify = lib_interface_isis_disable_three_way_handshake_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/standard",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_mt_standard,
+ .modify = lib_interface_isis_multi_topology_standard_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-multicast",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_mt_ipv4_multicast,
+ .modify = lib_interface_isis_multi_topology_ipv4_multicast_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-management",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_mt_ipv4_mgmt,
+ .modify = lib_interface_isis_multi_topology_ipv4_management_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-unicast",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_mt_ipv6_unicast,
+ .modify = lib_interface_isis_multi_topology_ipv6_unicast_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-multicast",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_mt_ipv6_multicast,
+ .modify = lib_interface_isis_multi_topology_ipv6_multicast_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-management",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_mt_ipv6_mgmt,
+ .modify = lib_interface_isis_multi_topology_ipv6_management_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-dstsrc",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_mt_ipv6_dstsrc,
+ .modify = lib_interface_isis_multi_topology_ipv6_dstsrc_modify,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute",
+ .cbs = {
+ .cli_show = cli_show_ip_isis_frr,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/lfa/enable",
+ .cbs = {
+ .modify = lib_interface_isis_fast_reroute_level_1_lfa_enable_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/lfa/exclude-interface",
+ .cbs = {
+ .cli_show = cli_show_frr_lfa_exclude_interface,
+ .create = lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_create,
+ .destroy = lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/remote-lfa/enable",
+ .cbs = {
+ .modify = lib_interface_isis_fast_reroute_level_1_remote_lfa_enable_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/remote-lfa/maximum-metric",
+ .cbs = {
+ .cli_show = cli_show_frr_remote_lfa_max_metric,
+ .modify = lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_modify,
+ .destroy = lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/enable",
+ .cbs = {
+ .modify = lib_interface_isis_fast_reroute_level_1_ti_lfa_enable_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/node-protection",
+ .cbs = {
+ .modify = lib_interface_isis_fast_reroute_level_1_ti_lfa_node_protection_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/link-fallback",
+ .cbs = {
+ .modify = lib_interface_isis_fast_reroute_level_1_ti_lfa_link_fallback_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/lfa/enable",
+ .cbs = {
+ .modify = lib_interface_isis_fast_reroute_level_2_lfa_enable_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/lfa/exclude-interface",
+ .cbs = {
+ .cli_show = cli_show_frr_lfa_exclude_interface,
+ .create = lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_create,
+ .destroy = lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/remote-lfa/enable",
+ .cbs = {
+ .modify = lib_interface_isis_fast_reroute_level_2_remote_lfa_enable_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/remote-lfa/maximum-metric",
+ .cbs = {
+ .cli_show = cli_show_frr_remote_lfa_max_metric,
+ .modify = lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_modify,
+ .destroy = lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/enable",
+ .cbs = {
+ .modify = lib_interface_isis_fast_reroute_level_2_ti_lfa_enable_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/node-protection",
+ .cbs = {
+ .modify = lib_interface_isis_fast_reroute_level_2_ti_lfa_node_protection_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/link-fallback",
+ .cbs = {
+ .modify = lib_interface_isis_fast_reroute_level_2_ti_lfa_link_fallback_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency",
+ .cbs = {
+ .get_next = lib_interface_state_isis_adjacencies_adjacency_get_next,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-sys-type",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_neighbor_sys_type_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-sysid",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_neighbor_sysid_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-extended-circuit-id",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_neighbor_extended_circuit_id_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-snpa",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_neighbor_snpa_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/hold-timer",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_hold_timer_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-priority",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_neighbor_priority_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/state",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_state_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid",
+ .cbs = {
+ .get_next = lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_get_next,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/af",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_af_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/value",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_value_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/weight",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_weight_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/protection-requested",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_protection_requested_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid",
+ .cbs = {
+ .get_next = lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_get_next,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/af",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_af_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/value",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_value_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/weight",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_weight_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/protection-requested",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_protection_requested_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-changes",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_event_counters_adjacency_changes_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-number",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_event_counters_adjacency_number_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/init-fails",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_event_counters_init_fails_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-rejects",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_event_counters_adjacency_rejects_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/id-len-mismatch",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_event_counters_id_len_mismatch_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/max-area-addresses-mismatch",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_event_counters_max_area_addresses_mismatch_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/authentication-type-fails",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_event_counters_authentication_type_fails_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/authentication-fails",
+ .cbs = {
+ .get_elem = lib_interface_state_isis_event_counters_authentication_fails_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/mpls/ldp-sync",
+ .cbs = {
+ .cli_show = cli_show_isis_mpls_if_ldp_sync,
+ .modify = lib_interface_isis_mpls_ldp_sync_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-isisd:isis/mpls/holddown",
+ .cbs = {
+ .cli_show = cli_show_isis_mpls_if_ldp_sync_holddown,
+ .modify = lib_interface_isis_mpls_holddown_modify,
+ .destroy = lib_interface_isis_mpls_holddown_destroy,
+ }
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
diff --git a/isisd/isis_nb.h b/isisd/isis_nb.h
new file mode 100644
index 0000000..c04a006
--- /dev/null
+++ b/isisd/isis_nb.h
@@ -0,0 +1,841 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 Volta Networks
+ * Emanuele Di Pascale
+ */
+
+#ifndef ISISD_ISIS_NB_H_
+#define ISISD_ISIS_NB_H_
+
+extern const struct frr_yang_module_info frr_isisd_info;
+
+/* Forward declaration(s). */
+struct isis_area;
+struct isis_circuit;
+struct isis_adjacency;
+
+/* Mandatory callbacks. */
+int isis_instance_create(struct nb_cb_create_args *args);
+int isis_instance_destroy(struct nb_cb_destroy_args *args);
+int isis_instance_is_type_modify(struct nb_cb_modify_args *args);
+int isis_instance_area_address_create(struct nb_cb_create_args *args);
+int isis_instance_area_address_destroy(struct nb_cb_destroy_args *args);
+int isis_instance_dynamic_hostname_modify(struct nb_cb_modify_args *args);
+int isis_instance_attached_send_modify(struct nb_cb_modify_args *args);
+int isis_instance_attached_receive_modify(struct nb_cb_modify_args *args);
+int isis_instance_attached_modify(struct nb_cb_modify_args *args);
+int isis_instance_overload_enabled_modify(struct nb_cb_modify_args *args);
+int isis_instance_overload_on_startup_modify(struct nb_cb_modify_args *args);
+int isis_instance_advertise_high_metrics_modify(struct nb_cb_modify_args *args);
+int isis_instance_metric_style_modify(struct nb_cb_modify_args *args);
+int isis_instance_purge_originator_modify(struct nb_cb_modify_args *args);
+int isis_instance_admin_group_send_zero_modify(struct nb_cb_modify_args *args);
+int isis_instance_asla_legacy_flag_modify(struct nb_cb_modify_args *args);
+int isis_instance_lsp_mtu_modify(struct nb_cb_modify_args *args);
+int isis_instance_advertise_passive_only_modify(struct nb_cb_modify_args *args);
+int isis_instance_lsp_refresh_interval_level_1_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_lsp_refresh_interval_level_2_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_lsp_maximum_lifetime_level_1_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_lsp_maximum_lifetime_level_2_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_lsp_generation_interval_level_1_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_lsp_generation_interval_level_2_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_spf_ietf_backoff_delay_create(struct nb_cb_create_args *args);
+int isis_instance_spf_ietf_backoff_delay_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_spf_ietf_backoff_delay_init_delay_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_spf_ietf_backoff_delay_short_delay_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_spf_ietf_backoff_delay_long_delay_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_spf_ietf_backoff_delay_hold_down_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_spf_ietf_backoff_delay_time_to_learn_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_spf_minimum_interval_level_1_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_spf_minimum_interval_level_2_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_spf_prefix_priorities_critical_access_list_name_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_spf_prefix_priorities_critical_access_list_name_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_spf_prefix_priorities_high_access_list_name_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_spf_prefix_priorities_high_access_list_name_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_spf_prefix_priorities_medium_access_list_name_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_spf_prefix_priorities_medium_access_list_name_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_area_password_create(struct nb_cb_create_args *args);
+int isis_instance_area_password_destroy(struct nb_cb_destroy_args *args);
+int isis_instance_area_password_password_modify(struct nb_cb_modify_args *args);
+int isis_instance_area_password_password_type_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_area_password_authenticate_snp_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_domain_password_create(struct nb_cb_create_args *args);
+int isis_instance_domain_password_destroy(struct nb_cb_destroy_args *args);
+int isis_instance_domain_password_password_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_domain_password_password_type_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_domain_password_authenticate_snp_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_default_information_originate_ipv4_create(
+ struct nb_cb_create_args *args);
+int isis_instance_default_information_originate_ipv4_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_default_information_originate_ipv4_always_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_default_information_originate_ipv4_route_map_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_default_information_originate_ipv4_route_map_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_default_information_originate_ipv4_metric_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_default_information_originate_ipv6_create(
+ struct nb_cb_create_args *args);
+int isis_instance_default_information_originate_ipv6_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_default_information_originate_ipv6_always_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_default_information_originate_ipv6_route_map_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_default_information_originate_ipv6_route_map_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_default_information_originate_ipv6_metric_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_redistribute_ipv4_create(struct nb_cb_create_args *args);
+int isis_instance_redistribute_ipv4_destroy(struct nb_cb_destroy_args *args);
+int isis_instance_redistribute_ipv4_route_map_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_redistribute_ipv4_route_map_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_redistribute_ipv4_metric_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_redistribute_ipv4_metric_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_redistribute_ipv4_table_create(struct nb_cb_create_args *args);
+int isis_instance_redistribute_ipv4_table_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_redistribute_ipv6_create(struct nb_cb_create_args *args);
+int isis_instance_redistribute_ipv6_destroy(struct nb_cb_destroy_args *args);
+int isis_instance_redistribute_ipv6_route_map_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_redistribute_ipv6_route_map_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_redistribute_ipv6_metric_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_redistribute_ipv6_metric_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_redistribute_ipv6_table_create(struct nb_cb_create_args *args);
+int isis_instance_redistribute_ipv6_table_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_multi_topology_ipv4_multicast_create(
+ struct nb_cb_create_args *args);
+int isis_instance_multi_topology_ipv4_multicast_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_multi_topology_ipv4_multicast_overload_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_multi_topology_ipv4_management_create(
+ struct nb_cb_create_args *args);
+int isis_instance_multi_topology_ipv4_management_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_multi_topology_ipv4_management_overload_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_multi_topology_ipv6_unicast_create(
+ struct nb_cb_create_args *args);
+int isis_instance_multi_topology_ipv6_unicast_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_multi_topology_ipv6_unicast_overload_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_multi_topology_ipv6_multicast_create(
+ struct nb_cb_create_args *args);
+int isis_instance_multi_topology_ipv6_multicast_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_multi_topology_ipv6_multicast_overload_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_multi_topology_ipv6_management_create(
+ struct nb_cb_create_args *args);
+int isis_instance_multi_topology_ipv6_management_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_multi_topology_ipv6_management_overload_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_multi_topology_ipv6_dstsrc_create(
+ struct nb_cb_create_args *args);
+int isis_instance_multi_topology_ipv6_dstsrc_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_multi_topology_ipv6_dstsrc_overload_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_fast_reroute_level_1_lfa_load_sharing_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_fast_reroute_level_1_lfa_priority_limit_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_fast_reroute_level_1_lfa_priority_limit_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_fast_reroute_level_1_lfa_tiebreaker_create(
+ struct nb_cb_create_args *args);
+int isis_instance_fast_reroute_level_1_lfa_tiebreaker_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_fast_reroute_level_1_lfa_tiebreaker_type_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_fast_reroute_level_2_lfa_load_sharing_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_fast_reroute_level_2_lfa_priority_limit_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_fast_reroute_level_2_lfa_priority_limit_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_fast_reroute_level_2_lfa_tiebreaker_create(
+ struct nb_cb_create_args *args);
+int isis_instance_fast_reroute_level_2_lfa_tiebreaker_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_fast_reroute_level_2_lfa_tiebreaker_type_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_log_adjacency_changes_modify(struct nb_cb_modify_args *args);
+int isis_instance_log_pdu_drops_modify(struct nb_cb_modify_args *args);
+int isis_instance_mpls_te_create(struct nb_cb_create_args *args);
+int isis_instance_mpls_te_destroy(struct nb_cb_destroy_args *args);
+int isis_instance_mpls_te_router_address_modify(struct nb_cb_modify_args *args);
+int isis_instance_mpls_te_router_address_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_mpls_te_router_address_ipv6_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_mpls_te_router_address_ipv6_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_mpls_te_export_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_create(struct nb_cb_create_args *args);
+int lib_interface_isis_destroy(struct nb_cb_destroy_args *args);
+int lib_interface_isis_area_tag_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_ipv4_routing_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_ipv6_routing_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_circuit_type_modify(struct nb_cb_modify_args *args);
+void lib_interface_isis_bfd_monitoring_apply_finish(
+ struct nb_cb_apply_finish_args *args);
+int lib_interface_isis_bfd_monitoring_enabled_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_bfd_monitoring_profile_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_bfd_monitoring_profile_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_segment_routing_enabled_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_enabled_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_srgb_lower_bound_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_srgb_upper_bound_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_srlb_lower_bound_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_srlb_upper_bound_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_msd_node_msd_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_msd_node_msd_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_create(
+ struct nb_cb_create_args *args);
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_type_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_last_hop_behavior_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_n_flag_clear_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_algorithm_prefix_sid_create(
+ struct nb_cb_create_args *args);
+int isis_instance_segment_routing_algorithm_prefix_sid_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_segment_routing_algorithm_prefix_sid_pre_validate(
+ struct nb_cb_pre_validate_args *args);
+void isis_instance_segment_routing_algorithm_prefix_sid_apply_finish(
+ struct nb_cb_apply_finish_args *args);
+int isis_instance_segment_routing_algorithm_prefix_sid_sid_value_type_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_algorithm_prefix_sid_sid_value_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_algorithm_prefix_sid_last_hop_behavior_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_algorithm_prefix_sid_n_flag_clear_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_flex_algo_create(struct nb_cb_create_args *args);
+int isis_instance_flex_algo_destroy(struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_advertise_definition_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_flex_algo_advertise_definition_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_affinity_include_any_create(
+ struct nb_cb_create_args *args);
+int isis_instance_flex_algo_affinity_include_any_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_affinity_include_all_create(
+ struct nb_cb_create_args *args);
+int isis_instance_flex_algo_affinity_include_all_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_affinity_exclude_any_create(
+ struct nb_cb_create_args *args);
+int isis_instance_flex_algo_affinity_exclude_any_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_prefix_metric_create(
+ struct nb_cb_create_args *args);
+int isis_instance_flex_algo_prefix_metric_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_dplane_sr_mpls_create(
+ struct nb_cb_create_args *args);
+int isis_instance_flex_algo_dplane_sr_mpls_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_dplane_srv6_create(struct nb_cb_create_args *args);
+int isis_instance_flex_algo_dplane_srv6_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_dplane_ip_create(struct nb_cb_create_args *args);
+int isis_instance_flex_algo_dplane_ip_destroy(struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_metric_type_modify(struct nb_cb_modify_args *args);
+int isis_instance_flex_algo_priority_modify(struct nb_cb_modify_args *args);
+int isis_instance_flex_algo_priority_destroy(struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_frr_disable_modify(struct nb_cb_modify_args *args);
+int isis_instance_flex_algo_frr_disable_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_affinity_mapping_create(
+ struct nb_cb_create_args *args);
+int isis_instance_flex_algo_affinity_mapping_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_flex_algo_affinity_mapping_value_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_flex_algo_affinity_mapping_value_destroy(
+ struct nb_cb_destroy_args *args);
+int isis_instance_segment_routing_srv6_enabled_modify(
+ struct nb_cb_modify_args *args);
+void cli_show_isis_srv6_enabled(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+int isis_instance_segment_routing_srv6_locator_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_srv6_locator_destroy(
+ struct nb_cb_destroy_args *args);
+void cli_show_isis_srv6_locator(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+int isis_instance_segment_routing_srv6_msd_node_msd_max_segs_left_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_srv6_msd_node_msd_max_end_pop_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_srv6_msd_node_msd_max_h_encaps_modify(
+ struct nb_cb_modify_args *args);
+int isis_instance_segment_routing_srv6_msd_node_msd_max_end_d_modify(
+ struct nb_cb_modify_args *args);
+void cli_show_isis_srv6_node_msd(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+int isis_instance_segment_routing_srv6_interface_modify(
+ struct nb_cb_modify_args *args);
+void cli_show_isis_srv6_interface(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+int isis_instance_mpls_ldp_sync_destroy(struct nb_cb_destroy_args *args);
+int isis_instance_mpls_ldp_sync_create(struct nb_cb_create_args *args);
+int isis_instance_mpls_ldp_sync_holddown_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_csnp_interval_level_1_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_csnp_interval_level_2_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_psnp_interval_level_1_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_psnp_interval_level_2_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_hello_padding_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_hello_interval_level_1_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_hello_interval_level_2_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_hello_multiplier_level_1_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_hello_multiplier_level_2_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_metric_level_1_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_metric_level_2_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_priority_level_1_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_priority_level_2_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_network_type_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_passive_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_password_create(struct nb_cb_create_args *args);
+int lib_interface_isis_password_destroy(struct nb_cb_destroy_args *args);
+int lib_interface_isis_password_password_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_password_password_type_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_disable_three_way_handshake_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_multi_topology_standard_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_multi_topology_ipv4_multicast_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_multi_topology_ipv4_management_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_multi_topology_ipv6_unicast_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_multi_topology_ipv6_multicast_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_multi_topology_ipv6_management_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_multi_topology_ipv6_dstsrc_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_mpls_ldp_sync_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_mpls_holddown_modify(struct nb_cb_modify_args *args);
+int lib_interface_isis_mpls_holddown_destroy(struct nb_cb_destroy_args *args);
+int lib_interface_isis_fast_reroute_level_1_lfa_enable_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_create(
+ struct nb_cb_create_args *args);
+int lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_destroy(
+ struct nb_cb_destroy_args *args);
+int lib_interface_isis_fast_reroute_level_1_remote_lfa_enable_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_destroy(
+ struct nb_cb_destroy_args *args);
+int lib_interface_isis_fast_reroute_level_1_ti_lfa_enable_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_fast_reroute_level_1_ti_lfa_node_protection_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_fast_reroute_level_1_ti_lfa_link_fallback_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_fast_reroute_level_2_lfa_enable_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_create(
+ struct nb_cb_create_args *args);
+int lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_destroy(
+ struct nb_cb_destroy_args *args);
+int lib_interface_isis_fast_reroute_level_2_remote_lfa_enable_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_destroy(
+ struct nb_cb_destroy_args *args);
+int lib_interface_isis_fast_reroute_level_2_ti_lfa_enable_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_fast_reroute_level_2_ti_lfa_node_protection_modify(
+ struct nb_cb_modify_args *args);
+int lib_interface_isis_fast_reroute_level_2_ti_lfa_link_fallback_modify(
+ struct nb_cb_modify_args *args);
+struct yang_data *
+lib_interface_state_isis_get_elem(struct nb_cb_get_elem_args *args);
+const void *lib_interface_state_isis_adjacencies_adjacency_get_next(
+ struct nb_cb_get_next_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_neighbor_sys_type_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_neighbor_sysid_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_neighbor_extended_circuit_id_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_neighbor_snpa_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_hold_timer_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_neighbor_priority_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *lib_interface_state_isis_adjacencies_adjacency_state_get_elem(
+ struct nb_cb_get_elem_args *args);
+const void *
+lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_get_next(
+ struct nb_cb_get_next_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_af_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_value_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_weight_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_protection_requested_get_elem(
+ struct nb_cb_get_elem_args *args);
+const void *
+lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_get_next(
+ struct nb_cb_get_next_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_af_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_value_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_weight_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_protection_requested_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_event_counters_adjacency_changes_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_event_counters_adjacency_number_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *lib_interface_state_isis_event_counters_init_fails_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_event_counters_adjacency_rejects_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_event_counters_id_len_mismatch_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_event_counters_max_area_addresses_mismatch_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_event_counters_authentication_type_fails_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+lib_interface_state_isis_event_counters_authentication_fails_get_elem(
+ struct nb_cb_get_elem_args *args);
+
+/* Optional 'pre_validate' callbacks. */
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_pre_validate(
+ struct nb_cb_pre_validate_args *args);
+int isis_instance_segment_routing_label_blocks_pre_validate(
+ struct nb_cb_pre_validate_args *args);
+
+/* Optional 'apply_finish' callbacks. */
+void ietf_backoff_delay_apply_finish(struct nb_cb_apply_finish_args *args);
+void area_password_apply_finish(struct nb_cb_apply_finish_args *args);
+void domain_password_apply_finish(struct nb_cb_apply_finish_args *args);
+void default_info_origin_apply_finish(const struct lyd_node *dnode, int family);
+void default_info_origin_ipv4_apply_finish(
+ struct nb_cb_apply_finish_args *args);
+void default_info_origin_ipv6_apply_finish(
+ struct nb_cb_apply_finish_args *args);
+void redistribute_apply_finish(const struct lyd_node *dnode, int family);
+void redistribute_ipv4_apply_finish(struct nb_cb_apply_finish_args *args);
+void redistribute_ipv6_apply_finish(struct nb_cb_apply_finish_args *args);
+void isis_instance_segment_routing_srgb_apply_finish(
+ struct nb_cb_apply_finish_args *args);
+void isis_instance_segment_routing_srlb_apply_finish(
+ struct nb_cb_apply_finish_args *args);
+void isis_instance_segment_routing_prefix_sid_map_prefix_sid_apply_finish(
+ struct nb_cb_apply_finish_args *args);
+
+/* Optional 'cli_show' callbacks. */
+void cli_show_router_isis(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_router_isis_end(struct vty *vty, const struct lyd_node *dnode);
+void cli_show_ip_isis_ipv4(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_ipv6(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_bfd_monitoring(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_area_address(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_is_type(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_dynamic_hostname(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_attached_send(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_attached_receive(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_overload(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_overload_on_startup(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_advertise_high_metrics(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_metric_style(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_area_pwd(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_domain_pwd(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_lsp_timers(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_lsp_mtu(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_advertise_passive_only(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_spf_min_interval(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_spf_ietf_backoff(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_spf_prefix_priority(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_purge_origin(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mpls_te(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_admin_group_send_zero(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_asla_legacy_flag(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mpls_te_router_addr(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mpls_te_router_addr_ipv6(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mpls_te_export(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_def_origin_ipv4(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_def_origin_ipv6(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_redistribute_ipv4(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_redistribute_ipv6(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mt_ipv4_multicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_redistribute_ipv4_table(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_redistribute_ipv6_table(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mt_ipv4_mgmt(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mt_ipv6_unicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mt_ipv6_multicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mt_ipv6_mgmt(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mt_ipv6_dstsrc(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_sr_enabled(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_label_blocks(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_node_msd(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_prefix_sid(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_prefix_sid_algorithm(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_frr_lfa_priority_limit(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_frr_lfa_tiebreaker(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_frr_lfa_load_sharing(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_frr_remote_lfa_plist(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_passive(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_password(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_metric(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_hello_interval(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_hello_multi(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_threeway_shake(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_hello_padding(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_csnp_interval(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_psnp_interval(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_mt_standard(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_mt_ipv4_multicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_mt_ipv4_mgmt(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_mt_ipv6_unicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_mt_ipv6_multicast(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_mt_ipv6_mgmt(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_mt_ipv6_dstsrc(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_frr(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_frr_lfa_exclude_interface(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_frr_remote_lfa_max_metric(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_circ_type(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_network_type(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip_isis_priority(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_log_adjacency(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_log_pdu_drops(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mpls_ldp_sync(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mpls_ldp_sync_holddown(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mpls_if_ldp_sync(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_mpls_if_ldp_sync_holddown(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_flex_algo(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_isis_flex_algo_end(struct vty *vty, const struct lyd_node *dnode);
+
+/* Notifications. */
+void isis_notif_db_overload(const struct isis_area *area, bool overload);
+void isis_notif_lsp_too_large(const struct isis_circuit *circuit,
+ uint32_t pdu_size, const uint8_t *lsp_id);
+void isis_notif_if_state_change(const struct isis_circuit *circuit, bool down);
+void isis_notif_corrupted_lsp(const struct isis_area *area,
+ const uint8_t *lsp_id); /* currently unused */
+void isis_notif_lsp_exceed_max(const struct isis_area *area,
+ const uint8_t *lsp_id);
+void isis_notif_max_area_addr_mismatch(const struct isis_circuit *circuit,
+ uint8_t max_area_addrs,
+ const char *raw_pdu, size_t raw_pdu_len);
+void isis_notif_authentication_type_failure(const struct isis_circuit *circuit,
+ const char *raw_pdu,
+ size_t raw_pdu_len);
+void isis_notif_authentication_failure(const struct isis_circuit *circuit,
+ const char *raw_pdu, size_t raw_pdu_len);
+void isis_notif_adj_state_change(const struct isis_adjacency *adj,
+ int new_state, const char *reason);
+void isis_notif_reject_adjacency(const struct isis_circuit *circuit,
+ const char *reason, const char *raw_pdu,
+ size_t raw_pdu_len);
+void isis_notif_area_mismatch(const struct isis_circuit *circuit,
+ const char *raw_pdu, size_t raw_pdu_len);
+void isis_notif_lsp_received(const struct isis_circuit *circuit,
+ const uint8_t *lsp_id, uint32_t seqno,
+ uint32_t timestamp, const char *sys_id);
+void isis_notif_lsp_gen(const struct isis_area *area, const uint8_t *lsp_id,
+ uint32_t seqno, uint32_t timestamp);
+void isis_notif_id_len_mismatch(const struct isis_circuit *circuit,
+ uint8_t rcv_id_len, const char *raw_pdu,
+ size_t raw_pdu_len);
+void isis_notif_version_skew(const struct isis_circuit *circuit,
+ uint8_t version, const char *raw_pdu,
+ size_t raw_pdu_len);
+void isis_notif_lsp_error(const struct isis_circuit *circuit,
+ const uint8_t *lsp_id, const char *raw_pdu,
+ size_t raw_pdu_len, uint32_t offset,
+ uint8_t tlv_type);
+void isis_notif_seqno_skipped(const struct isis_circuit *circuit,
+ const uint8_t *lsp_id);
+void isis_notif_own_lsp_purge(const struct isis_circuit *circuit,
+ const uint8_t *lsp_id);
+/* cmp */
+int cli_cmp_isis_redistribute_table(const struct lyd_node *dnode1,
+ const struct lyd_node *dnode2);
+
+/* We also declare hook for every notification */
+
+DECLARE_HOOK(isis_hook_db_overload, (const struct isis_area *area), (area));
+DECLARE_HOOK(isis_hook_lsp_too_large,
+ (const struct isis_circuit *circuit, uint32_t pdu_size,
+ const uint8_t *lsp_id),
+ (circuit, pdu_size, lsp_id));
+/* Note: no isis_hook_corrupted_lsp - because this notificaiton is not used */
+DECLARE_HOOK(isis_hook_lsp_exceed_max,
+ (const struct isis_area *area, const uint8_t *lsp_id),
+ (area, lsp_id));
+DECLARE_HOOK(isis_hook_max_area_addr_mismatch,
+ (const struct isis_circuit *circuit, uint8_t max_addrs,
+ const char *raw_pdu, size_t raw_pdu_len),
+ (circuit, max_addrs, raw_pdu, raw_pdu_len));
+DECLARE_HOOK(isis_hook_authentication_type_failure,
+ (const struct isis_circuit *circuit, const char *raw_pdu,
+ size_t raw_pdu_len),
+ (circuit, raw_pdu, raw_pdu_len));
+DECLARE_HOOK(isis_hook_authentication_failure,
+ (const struct isis_circuit *circuit, const char *raw_pdu,
+ size_t raw_pdu_len),
+ (circuit, raw_pdu, raw_pdu_len));
+DECLARE_HOOK(isis_hook_adj_state_change, (const struct isis_adjacency *adj),
+ (adj));
+DECLARE_HOOK(isis_hook_reject_adjacency,
+ (const struct isis_circuit *circuit, const char *pdu,
+ size_t pdu_len),
+ (circuit, pdu, pdu_len));
+DECLARE_HOOK(isis_hook_area_mismatch,
+ (const struct isis_circuit *circuit, const char *raw_pdu,
+ size_t raw_pdu_len),
+ (circuit));
+DECLARE_HOOK(isis_hook_id_len_mismatch,
+ (const struct isis_circuit *circuit, uint8_t rcv_id_len,
+ const char *raw_pdu, size_t raw_pdu_len),
+ (circuit, rcv_id_len, raw_pdu, raw_pdu_len));
+DECLARE_HOOK(isis_hook_version_skew,
+ (const struct isis_circuit *circuit, uint8_t version,
+ const char *raw_pdu, size_t raw_pdu_len),
+ (circuit));
+DECLARE_HOOK(isis_hook_lsp_error,
+ (const struct isis_circuit *circuit, const uint8_t *lsp_id,
+ const char *raw_pdu, size_t raw_pdu_len),
+ (circuit));
+DECLARE_HOOK(isis_hook_seqno_skipped,
+ (const struct isis_circuit *circuit, const uint8_t *lsp_id),
+ (circuit, lsp_id));
+DECLARE_HOOK(isis_hook_own_lsp_purge,
+ (const struct isis_circuit *circuit, const uint8_t *lsp_id),
+ (circuit, lsp_id));
+
+#endif /* ISISD_ISIS_NB_H_ */
diff --git a/isisd/isis_nb_config.c b/isisd/isis_nb_config.c
new file mode 100644
index 0000000..b6ee073
--- /dev/null
+++ b/isisd/isis_nb_config.c
@@ -0,0 +1,4947 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ * Copyright (C) 2018 Volta Networks
+ * Emanuele Di Pascale
+ */
+
+#include <zebra.h>
+
+#include "printfrr.h"
+#include "northbound.h"
+#include "linklist.h"
+#include "log.h"
+#include "bfd.h"
+#include "filter.h"
+#include "plist.h"
+#include "spf_backoff.h"
+#include "lib_errors.h"
+#include "vrf.h"
+#include "ldp_sync.h"
+#include "link_state.h"
+#include "affinitymap.h"
+
+#include "isisd/isisd.h"
+#include "isisd/isis_nb.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_bfd.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_dynhn.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_spf.h"
+#include "isisd/isis_spf_private.h"
+#include "isisd/isis_srv6.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_redist.h"
+#include "isisd/isis_ldp_sync.h"
+#include "isisd/isis_dr.h"
+#include "isisd/isis_sr.h"
+#include "isisd/isis_flex_algo.h"
+#include "isisd/isis_zebra.h"
+
+#define AFFINITY_INCLUDE_ANY 0
+#define AFFINITY_INCLUDE_ALL 1
+#define AFFINITY_EXCLUDE_ANY 2
+
+/*
+ * XPath: /frr-isisd:isis/instance
+ */
+int isis_instance_create(struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+ const char *area_tag;
+ const char *vrf_name;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+ vrf_name = yang_dnode_get_string(args->dnode, "./vrf");
+ area_tag = yang_dnode_get_string(args->dnode, "./area-tag");
+
+ area = isis_area_lookup_by_vrf(area_tag, vrf_name);
+ if (area)
+ return NB_ERR_INCONSISTENCY;
+
+ area = isis_area_create(area_tag, vrf_name);
+
+ /* save area in dnode to avoid looking it up all the time */
+ nb_running_set_entry(args->dnode, area);
+
+ return NB_OK;
+}
+
+int isis_instance_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ struct isis *isis;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+ area = nb_running_unset_entry(args->dnode);
+ isis = area->isis;
+ isis_area_destroy(area);
+
+ if (listcount(isis->area_list) == 0)
+ isis_finish(isis);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/is-type
+ */
+int isis_instance_is_type_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ int type;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ type = yang_dnode_get_enum(args->dnode, NULL);
+ isis_area_is_type_set(area, type);
+
+ return NB_OK;
+}
+
+struct sysid_iter {
+ struct iso_address *addr;
+ bool same;
+};
+
+static int sysid_iter_cb(const struct lyd_node *dnode, void *arg)
+{
+ struct sysid_iter *iter = arg;
+ struct iso_address addr;
+ const char *net;
+
+ net = yang_dnode_get_string(dnode, NULL);
+ addr.addr_len = dotformat2buff(addr.area_addr, net);
+
+ if (memcmp(GETSYSID(iter->addr), GETSYSID((&addr)), ISIS_SYS_ID_LEN)) {
+ iter->same = false;
+ return YANG_ITER_STOP;
+ }
+
+ return YANG_ITER_CONTINUE;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/area-address
+ */
+int isis_instance_area_address_create(struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+ struct iso_address addr, *addrr = NULL, *addrp = NULL;
+ struct listnode *node;
+ struct sysid_iter iter;
+ uint8_t buff[255];
+ const char *net_title = yang_dnode_get_string(args->dnode, NULL);
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ addr.addr_len = dotformat2buff(buff, net_title);
+ memcpy(addr.area_addr, buff, addr.addr_len);
+ if (addr.area_addr[addr.addr_len - 1] != 0) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "nsel byte (last byte) in area address must be 0");
+ return NB_ERR_VALIDATION;
+ }
+
+ iter.addr = &addr;
+ iter.same = true;
+
+ yang_dnode_iterate(sysid_iter_cb, &iter, args->dnode,
+ "../area-address");
+
+ if (!iter.same) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "System ID must not change when defining additional area addresses");
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ addrr = XMALLOC(MTYPE_ISIS_AREA_ADDR,
+ sizeof(struct iso_address));
+ addrr->addr_len = dotformat2buff(buff, net_title);
+ memcpy(addrr->area_addr, buff, addrr->addr_len);
+ args->resource->ptr = addrr;
+ break;
+ case NB_EV_ABORT:
+ XFREE(MTYPE_ISIS_AREA_ADDR, args->resource->ptr);
+ break;
+ case NB_EV_APPLY:
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ addrr = args->resource->ptr;
+ assert(area);
+
+ if (area->isis->sysid_set == 0) {
+ /*
+ * First area address - get the SystemID for this router
+ */
+ memcpy(area->isis->sysid, GETSYSID(addrr),
+ ISIS_SYS_ID_LEN);
+ area->isis->sysid_set = 1;
+ } else {
+ /* check that we don't already have this address */
+ for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node,
+ addrp)) {
+ if ((addrp->addr_len + ISIS_SYS_ID_LEN
+ + ISIS_NSEL_LEN)
+ != (addrr->addr_len))
+ continue;
+ if (!memcmp(addrp->area_addr, addrr->area_addr,
+ addrr->addr_len)) {
+ XFREE(MTYPE_ISIS_AREA_ADDR, addrr);
+ return NB_OK; /* silent fail */
+ }
+ }
+ }
+
+ /*Forget the systemID part of the address */
+ addrr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN);
+ assert(area->area_addrs); /* to silence scan-build sillyness */
+ listnode_add(area->area_addrs, addrr);
+
+ /* only now we can safely generate our LSPs for this area */
+ if (listcount(area->area_addrs) > 0) {
+ if (area->is_type & IS_LEVEL_1)
+ lsp_generate(area, IS_LEVEL_1);
+ if (area->is_type & IS_LEVEL_2)
+ lsp_generate(area, IS_LEVEL_2);
+ }
+ break;
+ }
+
+ return NB_OK;
+}
+
+int isis_instance_area_address_destroy(struct nb_cb_destroy_args *args)
+{
+ struct iso_address addr, *addrp = NULL;
+ struct listnode *node;
+ uint8_t buff[255];
+ struct isis_area *area;
+ const char *net_title;
+ struct listnode *cnode;
+ struct isis_circuit *circuit;
+ int lvl;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ net_title = yang_dnode_get_string(args->dnode, NULL);
+ addr.addr_len = dotformat2buff(buff, net_title);
+ memcpy(addr.area_addr, buff, (int)addr.addr_len);
+ area = nb_running_get_entry(args->dnode, NULL, true);
+
+ for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) {
+ if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len
+ && !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len))
+ break;
+ }
+ if (!addrp)
+ return NB_ERR_INCONSISTENCY;
+
+ listnode_delete(area->area_addrs, addrp);
+ XFREE(MTYPE_ISIS_AREA_ADDR, addrp);
+ /*
+ * Last area address - reset the SystemID for this router
+ */
+ if (listcount(area->area_addrs) == 0) {
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit))
+ for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) {
+ if (circuit->u.bc.is_dr[lvl - 1])
+ isis_dr_resign(circuit, lvl);
+ }
+ memset(area->isis->sysid, 0, ISIS_SYS_ID_LEN);
+ area->isis->sysid_set = 0;
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("Router has no SystemID");
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/dynamic-hostname
+ */
+int isis_instance_dynamic_hostname_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_dynhostname_set(area, yang_dnode_get_bool(args->dnode, NULL));
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/attach-send
+ */
+int isis_instance_attached_send_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ bool attached;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ attached = yang_dnode_get_bool(args->dnode, NULL);
+ isis_area_attached_bit_send_set(area, attached);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/attach-receive-ignore
+ */
+int isis_instance_attached_receive_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ bool attached;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ attached = yang_dnode_get_bool(args->dnode, NULL);
+ isis_area_attached_bit_receive_set(area, attached);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/attached
+ */
+int isis_instance_attached_modify(struct nb_cb_modify_args *args)
+{
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/overload/enabled
+ */
+int isis_instance_overload_enabled_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ bool overload;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ overload = yang_dnode_get_bool(args->dnode, NULL);
+ area->overload_configured = overload;
+
+ isis_area_overload_bit_set(area, overload);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/overload/on-startup
+ */
+int isis_instance_overload_on_startup_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ uint32_t overload_time;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ overload_time = yang_dnode_get_uint32(args->dnode, NULL);
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_overload_on_startup_set(area, overload_time);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/advertise-high-metrics
+ */
+int isis_instance_advertise_high_metrics_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ bool advertise_high_metrics;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ advertise_high_metrics = yang_dnode_get_bool(args->dnode, NULL);
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_advertise_high_metrics_set(area, advertise_high_metrics);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/metric-style
+ */
+int isis_instance_metric_style_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ bool old_metric, new_metric;
+ enum isis_metric_style metric_style =
+ yang_dnode_get_enum(args->dnode, NULL);
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ old_metric = (metric_style == ISIS_WIDE_METRIC) ? false : true;
+ new_metric = (metric_style == ISIS_NARROW_METRIC) ? false : true;
+ isis_area_metricstyle_set(area, old_metric, new_metric);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/purge-originator
+ */
+int isis_instance_purge_originator_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->purge_originator = yang_dnode_get_bool(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+
+/*
+ * XPath: /frr-isisd:isis/instance/admin-group-send-zero
+ */
+int isis_instance_admin_group_send_zero_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ struct isis_area *area;
+ struct listnode *node;
+ struct flex_algo *fa;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->admin_group_send_zero = yang_dnode_get_bool(args->dnode, NULL);
+
+ if (area->admin_group_send_zero) {
+ for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node,
+ fa)) {
+ admin_group_allow_explicit_zero(
+ &fa->admin_group_exclude_any);
+ admin_group_allow_explicit_zero(
+ &fa->admin_group_include_any);
+ admin_group_allow_explicit_zero(
+ &fa->admin_group_include_all);
+ }
+ } else {
+ for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node,
+ fa)) {
+ admin_group_disallow_explicit_zero(
+ &fa->admin_group_exclude_any);
+ admin_group_disallow_explicit_zero(
+ &fa->admin_group_include_any);
+ admin_group_disallow_explicit_zero(
+ &fa->admin_group_include_all);
+ }
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit))
+ isis_link_params_update(circuit, circuit->interface);
+
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0);
+
+ return NB_OK;
+}
+
+
+/*
+ * XPath: /frr-isisd:isis/instance/asla-legacy-flag
+ */
+int isis_instance_asla_legacy_flag_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->asla_legacy_flag = yang_dnode_get_bool(args->dnode, NULL);
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/lsp/mtu
+ */
+int isis_instance_lsp_mtu_modify(struct nb_cb_modify_args *args)
+{
+ uint16_t lsp_mtu = yang_dnode_get_uint16(args->dnode, NULL);
+ struct isis_area *area;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_lsp_mtu_set(area, lsp_mtu);
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/advertise-passive-only
+ */
+int isis_instance_advertise_passive_only_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ bool advertise_passive_only;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ advertise_passive_only = yang_dnode_get_bool(args->dnode, NULL);
+ area->advertise_passive_only = advertise_passive_only;
+
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/refresh-interval
+ */
+int isis_instance_lsp_refresh_interval_level_1_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ uint16_t refr_int;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ refr_int = yang_dnode_get_uint16(args->dnode, NULL);
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_lsp_refresh_set(area, IS_LEVEL_1, refr_int);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-2/refresh-interval
+ */
+int isis_instance_lsp_refresh_interval_level_2_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ uint16_t refr_int;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ refr_int = yang_dnode_get_uint16(args->dnode, NULL);
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_lsp_refresh_set(area, IS_LEVEL_2, refr_int);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/maximum-lifetime
+ */
+int isis_instance_lsp_maximum_lifetime_level_1_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ uint16_t max_lt;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ max_lt = yang_dnode_get_uint16(args->dnode, NULL);
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_max_lsp_lifetime_set(area, IS_LEVEL_1, max_lt);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-2/maximum-lifetime
+ */
+int isis_instance_lsp_maximum_lifetime_level_2_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ uint16_t max_lt;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ max_lt = yang_dnode_get_uint16(args->dnode, NULL);
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_max_lsp_lifetime_set(area, IS_LEVEL_2, max_lt);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/generation-interval
+ */
+int isis_instance_lsp_generation_interval_level_1_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ uint16_t gen_int;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ gen_int = yang_dnode_get_uint16(args->dnode, NULL);
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->lsp_gen_interval[0] = gen_int;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/lsp/timers/level-2/generation-interval
+ */
+int isis_instance_lsp_generation_interval_level_2_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ uint16_t gen_int;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ gen_int = yang_dnode_get_uint16(args->dnode, NULL);
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->lsp_gen_interval[1] = gen_int;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay
+ */
+void ietf_backoff_delay_apply_finish(struct nb_cb_apply_finish_args *args)
+{
+ long init_delay = yang_dnode_get_uint16(args->dnode, "./init-delay");
+ long short_delay = yang_dnode_get_uint16(args->dnode, "./short-delay");
+ long long_delay = yang_dnode_get_uint16(args->dnode, "./long-delay");
+ long holddown = yang_dnode_get_uint16(args->dnode, "./hold-down");
+ long timetolearn =
+ yang_dnode_get_uint16(args->dnode, "./time-to-learn");
+ struct isis_area *area = nb_running_get_entry(args->dnode, NULL, true);
+ size_t bufsiz = strlen(area->area_tag) + sizeof("IS-IS Lx");
+ char *buf = XCALLOC(MTYPE_TMP, bufsiz);
+
+ snprintf(buf, bufsiz, "IS-IS %s L1", area->area_tag);
+ spf_backoff_free(area->spf_delay_ietf[0]);
+ area->spf_delay_ietf[0] =
+ spf_backoff_new(master, buf, init_delay, short_delay,
+ long_delay, holddown, timetolearn);
+
+ snprintf(buf, bufsiz, "IS-IS %s L2", area->area_tag);
+ spf_backoff_free(area->spf_delay_ietf[1]);
+ area->spf_delay_ietf[1] =
+ spf_backoff_new(master, buf, init_delay, short_delay,
+ long_delay, holddown, timetolearn);
+
+ XFREE(MTYPE_TMP, buf);
+}
+
+int isis_instance_spf_ietf_backoff_delay_create(struct nb_cb_create_args *args)
+{
+ /* All the work is done in the apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_spf_ietf_backoff_delay_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ spf_backoff_free(area->spf_delay_ietf[0]);
+ spf_backoff_free(area->spf_delay_ietf[1]);
+ area->spf_delay_ietf[0] = NULL;
+ area->spf_delay_ietf[1] = NULL;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/init-delay
+ */
+int isis_instance_spf_ietf_backoff_delay_init_delay_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* All the work is done in the apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/short-delay
+ */
+int isis_instance_spf_ietf_backoff_delay_short_delay_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* All the work is done in the apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/long-delay
+ */
+int isis_instance_spf_ietf_backoff_delay_long_delay_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* All the work is done in the apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/hold-down
+ */
+int isis_instance_spf_ietf_backoff_delay_hold_down_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* All the work is done in the apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/time-to-learn
+ */
+int isis_instance_spf_ietf_backoff_delay_time_to_learn_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* All the work is done in the apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/minimum-interval/level-1
+ */
+int isis_instance_spf_minimum_interval_level_1_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->min_spf_interval[0] = yang_dnode_get_uint16(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/minimum-interval/level-2
+ */
+int isis_instance_spf_minimum_interval_level_2_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->min_spf_interval[1] = yang_dnode_get_uint16(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/spf/prefix-priorities/critical/access-list-name
+ */
+int isis_instance_spf_prefix_priorities_critical_access_list_name_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ const char *acl_name;
+ struct spf_prefix_priority_acl *ppa;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ acl_name = yang_dnode_get_string(args->dnode, NULL);
+
+ ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_CRITICAL];
+ XFREE(MTYPE_ISIS_ACL_NAME, ppa->name);
+ ppa->name = XSTRDUP(MTYPE_ISIS_ACL_NAME, acl_name);
+ ppa->list_v4 = access_list_lookup(AFI_IP, acl_name);
+ ppa->list_v6 = access_list_lookup(AFI_IP6, acl_name);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_spf_prefix_priorities_critical_access_list_name_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ struct spf_prefix_priority_acl *ppa;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+
+ ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_CRITICAL];
+ XFREE(MTYPE_ISIS_ACL_NAME, ppa->name);
+ ppa->list_v4 = NULL;
+ ppa->list_v6 = NULL;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/prefix-priorities/high/access-list-name
+ */
+int isis_instance_spf_prefix_priorities_high_access_list_name_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ const char *acl_name;
+ struct spf_prefix_priority_acl *ppa;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ acl_name = yang_dnode_get_string(args->dnode, NULL);
+
+ ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_HIGH];
+ XFREE(MTYPE_ISIS_ACL_NAME, ppa->name);
+ ppa->name = XSTRDUP(MTYPE_ISIS_ACL_NAME, acl_name);
+ ppa->list_v4 = access_list_lookup(AFI_IP, acl_name);
+ ppa->list_v6 = access_list_lookup(AFI_IP6, acl_name);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_spf_prefix_priorities_high_access_list_name_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ struct spf_prefix_priority_acl *ppa;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+
+ ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_HIGH];
+ XFREE(MTYPE_ISIS_ACL_NAME, ppa->name);
+ ppa->list_v4 = NULL;
+ ppa->list_v6 = NULL;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/spf/prefix-priorities/medium/access-list-name
+ */
+int isis_instance_spf_prefix_priorities_medium_access_list_name_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ const char *acl_name;
+ struct spf_prefix_priority_acl *ppa;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ acl_name = yang_dnode_get_string(args->dnode, NULL);
+
+ ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_MEDIUM];
+ XFREE(MTYPE_ISIS_ACL_NAME, ppa->name);
+ ppa->name = XSTRDUP(MTYPE_ISIS_ACL_NAME, acl_name);
+ ppa->list_v4 = access_list_lookup(AFI_IP, acl_name);
+ ppa->list_v6 = access_list_lookup(AFI_IP6, acl_name);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_spf_prefix_priorities_medium_access_list_name_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ struct spf_prefix_priority_acl *ppa;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+
+ ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_MEDIUM];
+ XFREE(MTYPE_ISIS_ACL_NAME, ppa->name);
+ ppa->list_v4 = NULL;
+ ppa->list_v6 = NULL;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/area-password
+ */
+void area_password_apply_finish(struct nb_cb_apply_finish_args *args)
+{
+ const char *password = yang_dnode_get_string(args->dnode, "./password");
+ struct isis_area *area = nb_running_get_entry(args->dnode, NULL, true);
+ int pass_type = yang_dnode_get_enum(args->dnode, "./password-type");
+ uint8_t snp_auth =
+ yang_dnode_get_enum(args->dnode, "./authenticate-snp");
+
+ switch (pass_type) {
+ case ISIS_PASSWD_TYPE_CLEARTXT:
+ isis_area_passwd_cleartext_set(area, IS_LEVEL_1, password,
+ snp_auth);
+ break;
+ case ISIS_PASSWD_TYPE_HMAC_MD5:
+ isis_area_passwd_hmac_md5_set(area, IS_LEVEL_1, password,
+ snp_auth);
+ break;
+ }
+}
+
+int isis_instance_area_password_create(struct nb_cb_create_args *args)
+{
+ /* actual setting is done in apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_area_password_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_passwd_unset(area, IS_LEVEL_1);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/area-password/password
+ */
+int isis_instance_area_password_password_modify(struct nb_cb_modify_args *args)
+{
+ /* actual setting is done in apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/area-password/password-type
+ */
+int isis_instance_area_password_password_type_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* actual setting is done in apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/area-password/authenticate-snp
+ */
+int isis_instance_area_password_authenticate_snp_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* actual setting is done in apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/domain-password
+ */
+void domain_password_apply_finish(struct nb_cb_apply_finish_args *args)
+{
+ const char *password = yang_dnode_get_string(args->dnode, "./password");
+ struct isis_area *area = nb_running_get_entry(args->dnode, NULL, true);
+ int pass_type = yang_dnode_get_enum(args->dnode, "./password-type");
+ uint8_t snp_auth =
+ yang_dnode_get_enum(args->dnode, "./authenticate-snp");
+
+ switch (pass_type) {
+ case ISIS_PASSWD_TYPE_CLEARTXT:
+ isis_area_passwd_cleartext_set(area, IS_LEVEL_2, password,
+ snp_auth);
+ break;
+ case ISIS_PASSWD_TYPE_HMAC_MD5:
+ isis_area_passwd_hmac_md5_set(area, IS_LEVEL_2, password,
+ snp_auth);
+ break;
+ }
+}
+
+int isis_instance_domain_password_create(struct nb_cb_create_args *args)
+{
+ /* actual setting is done in apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_domain_password_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_passwd_unset(area, IS_LEVEL_2);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/domain-password/password
+ */
+int isis_instance_domain_password_password_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* actual setting is done in apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/domain-password/password-type
+ */
+int isis_instance_domain_password_password_type_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* actual setting is done in apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/domain-password/authenticate-snp
+ */
+int isis_instance_domain_password_authenticate_snp_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* actual setting is done in apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4
+ */
+void default_info_origin_apply_finish(const struct lyd_node *dnode, int family)
+{
+ int originate_type = DEFAULT_ORIGINATE;
+ unsigned long metric = 0;
+ const char *routemap = NULL;
+ struct isis_area *area = nb_running_get_entry(dnode, NULL, true);
+ int level = yang_dnode_get_enum(dnode, "./level");
+
+ if (yang_dnode_get_bool(dnode, "./always")) {
+ originate_type = DEFAULT_ORIGINATE_ALWAYS;
+ } else if (family == AF_INET6) {
+ zlog_warn(
+ "%s: Zebra doesn't implement default-originate for IPv6 yet, so use with care or use default-originate always.",
+ __func__);
+ }
+
+ if (yang_dnode_exists(dnode, "./metric"))
+ metric = yang_dnode_get_uint32(dnode, "./metric");
+ if (yang_dnode_exists(dnode, "./route-map"))
+ routemap = yang_dnode_get_string(dnode, "./route-map");
+
+ isis_redist_set(area, level, family, DEFAULT_ROUTE, metric, routemap,
+ originate_type, 0);
+}
+
+void default_info_origin_ipv4_apply_finish(struct nb_cb_apply_finish_args *args)
+{
+ default_info_origin_apply_finish(args->dnode, AF_INET);
+}
+
+void default_info_origin_ipv6_apply_finish(struct nb_cb_apply_finish_args *args)
+{
+ default_info_origin_apply_finish(args->dnode, AF_INET6);
+}
+
+int isis_instance_default_information_originate_ipv4_create(
+ struct nb_cb_create_args *args)
+{
+ /* It's all done by default_info_origin_apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_default_information_originate_ipv4_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ int level;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ level = yang_dnode_get_enum(args->dnode, "./level");
+ isis_redist_unset(area, level, AF_INET, DEFAULT_ROUTE, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4/always
+ */
+int isis_instance_default_information_originate_ipv4_always_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* It's all done by default_info_origin_apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4/route-map
+ */
+int isis_instance_default_information_originate_ipv4_route_map_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* It's all done by default_info_origin_apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_default_information_originate_ipv4_route_map_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ /* It's all done by default_info_origin_apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4/metric
+ */
+int isis_instance_default_information_originate_ipv4_metric_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* It's all done by default_info_origin_apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6
+ */
+int isis_instance_default_information_originate_ipv6_create(
+ struct nb_cb_create_args *args)
+{
+ /* It's all done by default_info_origin_apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_default_information_originate_ipv6_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ int level;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ level = yang_dnode_get_enum(args->dnode, "./level");
+ isis_redist_unset(area, level, AF_INET6, DEFAULT_ROUTE, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6/always
+ */
+int isis_instance_default_information_originate_ipv6_always_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* It's all done by default_info_origin_apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6/route-map
+ */
+int isis_instance_default_information_originate_ipv6_route_map_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* It's all done by default_info_origin_apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_default_information_originate_ipv6_route_map_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ /* It's all done by default_info_origin_apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6/metric
+ */
+int isis_instance_default_information_originate_ipv6_metric_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* It's all done by default_info_origin_apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/redistribute/ipv4
+ */
+void redistribute_apply_finish(const struct lyd_node *dnode, int family)
+{
+ assert(family == AF_INET || family == AF_INET6);
+ int type, level;
+ unsigned long metric = 0;
+ const char *routemap = NULL;
+ struct isis_area *area;
+
+ type = yang_dnode_get_enum(dnode, "./protocol");
+ level = yang_dnode_get_enum(dnode, "./level");
+ area = nb_running_get_entry(dnode, NULL, true);
+
+ if (yang_dnode_exists(dnode, "./metric"))
+ metric = yang_dnode_get_uint32(dnode, "./metric");
+ if (yang_dnode_exists(dnode, "./route-map"))
+ routemap = yang_dnode_get_string(dnode, "./route-map");
+
+ isis_redist_set(area, level, family, type, metric, routemap, 0, 0);
+}
+
+void redistribute_ipv4_apply_finish(struct nb_cb_apply_finish_args *args)
+{
+ redistribute_apply_finish(args->dnode, AF_INET);
+}
+
+void redistribute_ipv6_apply_finish(struct nb_cb_apply_finish_args *args)
+{
+ redistribute_apply_finish(args->dnode, AF_INET6);
+}
+
+int isis_instance_redistribute_ipv4_create(struct nb_cb_create_args *args)
+{
+ /* It's all done by redistribute_apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_redistribute_ipv4_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ int level, type;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ level = yang_dnode_get_enum(args->dnode, "./level");
+ type = yang_dnode_get_enum(args->dnode, "./protocol");
+ isis_redist_unset(area, level, AF_INET, type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/redistribute/ipv4/route-map
+ * XPath: /frr-isisd:isis/instance/redistribute/ipv4/table/route-map
+ */
+int isis_instance_redistribute_ipv4_route_map_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* It's all done by redistribute_apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_redistribute_ipv4_route_map_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ /* It's all done by redistribute_apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/redistribute/ipv4/metric
+ * XPath: /frr-isisd:isis/instance/redistribute/ipv4/table/metric
+ */
+int isis_instance_redistribute_ipv4_metric_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* It's all done by redistribute_apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_redistribute_ipv4_metric_destroy(struct nb_cb_destroy_args *args)
+{
+ /* It's all done by redistribute_apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/redistribute/ipv4/table
+ */
+int isis_instance_redistribute_ipv4_table_create(struct nb_cb_create_args *args)
+{
+ uint16_t table;
+ int type, level;
+ unsigned long metric = 0;
+ const char *routemap = NULL;
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ type = yang_dnode_get_enum(args->dnode, "../protocol");
+ level = yang_dnode_get_enum(args->dnode, "../level");
+ area = nb_running_get_entry(args->dnode, "../.", true);
+
+ if (yang_dnode_exists(args->dnode, "./metric"))
+ metric = yang_dnode_get_uint32(args->dnode, "./metric");
+ if (yang_dnode_exists(args->dnode, "./route-map"))
+ routemap = yang_dnode_get_string(args->dnode, "./route-map");
+
+ table = yang_dnode_get_uint16(args->dnode, "./table");
+ isis_redist_set(area, level, AF_INET, type, metric, routemap, 0, table);
+
+ return NB_OK;
+}
+int isis_instance_redistribute_ipv4_table_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ int level, type;
+ uint16_t table;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, "../.", true);
+ level = yang_dnode_get_enum(args->dnode, "../level");
+ type = yang_dnode_get_enum(args->dnode, "../protocol");
+ table = yang_dnode_get_uint16(args->dnode, "./table");
+ isis_redist_unset(area, level, AF_INET, type, table);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/redistribute/ipv6
+ */
+int isis_instance_redistribute_ipv6_create(struct nb_cb_create_args *args)
+{
+ /* It's all done by redistribute_apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_redistribute_ipv6_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ int level, type;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ level = yang_dnode_get_enum(args->dnode, "./level");
+ type = yang_dnode_get_enum(args->dnode, "./protocol");
+ isis_redist_unset(area, level, AF_INET6, type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/redistribute/ipv6/route-map
+ */
+int isis_instance_redistribute_ipv6_route_map_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* It's all done by redistribute_apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_redistribute_ipv6_route_map_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ /* It's all done by redistribute_apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/redistribute/ipv6/metric
+ */
+int isis_instance_redistribute_ipv6_metric_modify(
+ struct nb_cb_modify_args *args)
+{
+ /* It's all done by redistribute_apply_finish */
+ return NB_OK;
+}
+
+int isis_instance_redistribute_ipv6_metric_destroy(struct nb_cb_destroy_args *args)
+{
+ /* It's all done by redistribute_apply_finish */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/redistribute/ipv6/table
+ */
+int isis_instance_redistribute_ipv6_table_create(struct nb_cb_create_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /* TODO */
+ return NB_OK;
+}
+
+int isis_instance_redistribute_ipv6_table_destroy(struct nb_cb_destroy_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /* TODO */
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-multicast
+ */
+static int isis_multi_topology_common(enum nb_event event,
+ const struct lyd_node *dnode,
+ char *errmsg, size_t errmsg_len,
+ const char *topology, bool create)
+{
+ struct isis_area *area;
+ struct isis_area_mt_setting *setting;
+ uint16_t mtid = isis_str2mtid(topology);
+
+ switch (event) {
+ case NB_EV_VALIDATE:
+ if (mtid == (uint16_t)-1) {
+ snprintf(errmsg, errmsg_len, "Unknown topology %s",
+ topology);
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ area = nb_running_get_entry(dnode, NULL, true);
+ setting = area_get_mt_setting(area, mtid);
+ setting->enabled = create;
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0);
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int isis_multi_topology_overload_common(enum nb_event event,
+ const struct lyd_node *dnode,
+ const char *topology)
+{
+ struct isis_area *area;
+ struct isis_area_mt_setting *setting;
+ uint16_t mtid = isis_str2mtid(topology);
+
+ /* validation is done in isis_multi_topology_common */
+ if (event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(dnode, NULL, true);
+ setting = area_get_mt_setting(area, mtid);
+ setting->overload = yang_dnode_get_bool(dnode, NULL);
+ if (setting->enabled)
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_multi_topology_ipv4_multicast_create(
+ struct nb_cb_create_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv4-multicast", true);
+}
+
+int isis_instance_multi_topology_ipv4_multicast_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv4-multicast", false);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-multicast/overload
+ */
+int isis_instance_multi_topology_ipv4_multicast_overload_modify(
+ struct nb_cb_modify_args *args)
+{
+ return isis_multi_topology_overload_common(args->event, args->dnode,
+ "ipv4-multicast");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-management
+ */
+int isis_instance_multi_topology_ipv4_management_create(
+ struct nb_cb_create_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv4-mgmt", true);
+}
+
+int isis_instance_multi_topology_ipv4_management_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv4-mgmt", false);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-management/overload
+ */
+int isis_instance_multi_topology_ipv4_management_overload_modify(
+ struct nb_cb_modify_args *args)
+{
+ return isis_multi_topology_overload_common(args->event, args->dnode,
+ "ipv4-mgmt");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-unicast
+ */
+int isis_instance_multi_topology_ipv6_unicast_create(
+ struct nb_cb_create_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv6-unicast", true);
+}
+
+int isis_instance_multi_topology_ipv6_unicast_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv6-unicast", false);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-unicast/overload
+ */
+int isis_instance_multi_topology_ipv6_unicast_overload_modify(
+ struct nb_cb_modify_args *args)
+{
+ return isis_multi_topology_overload_common(args->event, args->dnode,
+ "ipv6-unicast");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-multicast
+ */
+int isis_instance_multi_topology_ipv6_multicast_create(
+ struct nb_cb_create_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv6-multicast", true);
+}
+
+int isis_instance_multi_topology_ipv6_multicast_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv6-multicast", false);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-multicast/overload
+ */
+int isis_instance_multi_topology_ipv6_multicast_overload_modify(
+ struct nb_cb_modify_args *args)
+{
+ return isis_multi_topology_overload_common(args->event, args->dnode,
+ "ipv6-multicast");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-management
+ */
+int isis_instance_multi_topology_ipv6_management_create(
+ struct nb_cb_create_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv6-mgmt", true);
+}
+
+int isis_instance_multi_topology_ipv6_management_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv6-mgmt", false);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-management/overload
+ */
+int isis_instance_multi_topology_ipv6_management_overload_modify(
+ struct nb_cb_modify_args *args)
+{
+ return isis_multi_topology_overload_common(args->event, args->dnode,
+ "ipv6-mgmt");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-dstsrc
+ */
+int isis_instance_multi_topology_ipv6_dstsrc_create(
+ struct nb_cb_create_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv6-dstsrc", true);
+}
+
+int isis_instance_multi_topology_ipv6_dstsrc_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return isis_multi_topology_common(args->event, args->dnode,
+ args->errmsg, args->errmsg_len,
+ "ipv6-dstsrc", false);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-dstsrc/overload
+ */
+int isis_instance_multi_topology_ipv6_dstsrc_overload_modify(
+ struct nb_cb_modify_args *args)
+{
+ return isis_multi_topology_overload_common(args->event, args->dnode,
+ "ipv6-dstsrc");
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-1/lfa/load-sharing
+ */
+int isis_instance_fast_reroute_level_1_lfa_load_sharing_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->lfa_load_sharing[0] = yang_dnode_get_bool(args->dnode, NULL);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-1/lfa/priority-limit
+ */
+int isis_instance_fast_reroute_level_1_lfa_priority_limit_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->lfa_priority_limit[0] = yang_dnode_get_enum(args->dnode, NULL);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_fast_reroute_level_1_lfa_priority_limit_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->lfa_priority_limit[0] = SPF_PREFIX_PRIO_LOW;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-1/lfa/tiebreaker
+ */
+int isis_instance_fast_reroute_level_1_lfa_tiebreaker_create(
+ struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+ uint8_t index;
+ enum lfa_tiebreaker_type type;
+ struct lfa_tiebreaker *tie_b;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ index = yang_dnode_get_uint8(args->dnode, "./index");
+ type = yang_dnode_get_enum(args->dnode, "./type");
+
+ tie_b = isis_lfa_tiebreaker_add(area, ISIS_LEVEL1, index, type);
+ nb_running_set_entry(args->dnode, tie_b);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_fast_reroute_level_1_lfa_tiebreaker_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct lfa_tiebreaker *tie_b;
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ tie_b = nb_running_unset_entry(args->dnode);
+ area = tie_b->area;
+ isis_lfa_tiebreaker_delete(area, ISIS_LEVEL1, tie_b);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-1/lfa/tiebreaker/type
+ */
+int isis_instance_fast_reroute_level_1_lfa_tiebreaker_type_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct lfa_tiebreaker *tie_b;
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ tie_b = nb_running_get_entry(args->dnode, NULL, true);
+ area = tie_b->area;
+ tie_b->type = yang_dnode_get_enum(args->dnode, NULL);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-1/remote-lfa/prefix-list
+ */
+int isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ const char *plist_name;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ plist_name = yang_dnode_get_string(args->dnode, NULL);
+
+ area->rlfa_plist_name[0] = XSTRDUP(MTYPE_ISIS_PLIST_NAME, plist_name);
+ area->rlfa_plist[0] = prefix_list_lookup(AFI_IP, plist_name);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+
+ XFREE(MTYPE_ISIS_PLIST_NAME, area->rlfa_plist_name[0]);
+ area->rlfa_plist[0] = NULL;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-2/lfa/load-sharing
+ */
+int isis_instance_fast_reroute_level_2_lfa_load_sharing_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->lfa_load_sharing[1] = yang_dnode_get_bool(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-2/lfa/priority-limit
+ */
+int isis_instance_fast_reroute_level_2_lfa_priority_limit_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->lfa_priority_limit[1] = yang_dnode_get_enum(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+int isis_instance_fast_reroute_level_2_lfa_priority_limit_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->lfa_priority_limit[1] = SPF_PREFIX_PRIO_LOW;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-2/lfa/tiebreaker
+ */
+int isis_instance_fast_reroute_level_2_lfa_tiebreaker_create(
+ struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+ uint8_t index;
+ enum lfa_tiebreaker_type type;
+ struct lfa_tiebreaker *tie_b;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ index = yang_dnode_get_uint8(args->dnode, "./index");
+ type = yang_dnode_get_enum(args->dnode, "./type");
+
+ tie_b = isis_lfa_tiebreaker_add(area, ISIS_LEVEL2, index, type);
+ nb_running_set_entry(args->dnode, tie_b);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_fast_reroute_level_2_lfa_tiebreaker_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct lfa_tiebreaker *tie_b;
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ tie_b = nb_running_unset_entry(args->dnode);
+ area = tie_b->area;
+ isis_lfa_tiebreaker_delete(area, ISIS_LEVEL2, tie_b);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-2/lfa/tiebreaker/type
+ */
+int isis_instance_fast_reroute_level_2_lfa_tiebreaker_type_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct lfa_tiebreaker *tie_b;
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ tie_b = nb_running_get_entry(args->dnode, NULL, true);
+ area = tie_b->area;
+ tie_b->type = yang_dnode_get_enum(args->dnode, NULL);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/fast-reroute/level-2/remote-lfa/prefix-list
+ */
+int isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ const char *plist_name;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ plist_name = yang_dnode_get_string(args->dnode, NULL);
+
+ area->rlfa_plist_name[1] = XSTRDUP(MTYPE_ISIS_PLIST_NAME, plist_name);
+ area->rlfa_plist[1] = prefix_list_lookup(AFI_IP, plist_name);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+
+ XFREE(MTYPE_ISIS_PLIST_NAME, area->rlfa_plist_name[1]);
+ area->rlfa_plist[1] = NULL;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/log-adjacency-changes
+ */
+int isis_instance_log_adjacency_changes_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ bool log = yang_dnode_get_bool(args->dnode, NULL);
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->log_adj_changes = log ? 1 : 0;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/log-pdu-drops
+ */
+int isis_instance_log_pdu_drops_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ bool log = yang_dnode_get_bool(args->dnode, NULL);
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->log_pdu_drops = log ? 1 : 0;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/mpls-te
+ */
+int isis_instance_mpls_te_create(struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_mpls_te_create(area);
+
+ /* Reoriginate STD_TE & GMPLS circuits */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_mpls_te_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ if (!IS_MPLS_TE(area->mta))
+ return NB_OK;
+
+ isis_mpls_te_disable(area);
+
+ /* Reoriginate STD_TE & GMPLS circuits */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ zlog_debug("ISIS-TE(%s): Disabled MPLS Traffic Engineering",
+ area->area_tag);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/mpls-te/router-address
+ */
+int isis_instance_mpls_te_router_address_modify(struct nb_cb_modify_args *args)
+{
+ struct in_addr value;
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ /* only proceed if MPLS-TE is enabled */
+ if (!IS_MPLS_TE(area->mta))
+ return NB_OK;
+
+ /* Update Area Router ID */
+ yang_dnode_get_ipv4(&value, args->dnode, NULL);
+ area->mta->router_id.s_addr = value.s_addr;
+
+ /* And re-schedule LSP update */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_mpls_te_router_address_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ /* only proceed if MPLS-TE is enabled */
+ if (!IS_MPLS_TE(area->mta))
+ return NB_OK;
+
+ /* Reset Area Router ID */
+ area->mta->router_id.s_addr = INADDR_ANY;
+
+ /* And re-schedule LSP update */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/mpls-te/router-address-v6
+ */
+int isis_instance_mpls_te_router_address_ipv6_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct in6_addr value;
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ /* only proceed if MPLS-TE is enabled */
+ if (!IS_MPLS_TE(area->mta))
+ return NB_OK;
+
+ yang_dnode_get_ipv6(&value, args->dnode, NULL);
+ /* Update Area IPv6 Router ID if different */
+ if (!IPV6_ADDR_SAME(&area->mta->router_id_ipv6, &value)) {
+ IPV6_ADDR_COPY(&area->mta->router_id_ipv6, &value);
+
+ /* And re-schedule LSP update */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ }
+
+ return NB_OK;
+}
+
+int isis_instance_mpls_te_router_address_ipv6_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ /* only proceed if MPLS-TE is enabled */
+ if (!IS_MPLS_TE(area->mta))
+ return NB_OK;
+
+ /* Reset Area Router ID */
+ IPV6_ADDR_COPY(&area->mta->router_id_ipv6, &in6addr_any);
+
+ /* And re-schedule LSP update */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/mpls-te/export
+ */
+int isis_instance_mpls_te_export_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ /* only proceed if MPLS-TE is enabled */
+ if (!IS_MPLS_TE(area->mta))
+ return NB_OK;
+
+ area->mta->export = yang_dnode_get_bool(args->dnode, NULL);
+ if (area->mta->export) {
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("MPLS-TE: Enabled Link State export");
+ if (isis_zebra_ls_register(true) != 0)
+ zlog_warn("Unable to register Link State");
+ } else {
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("MPLS-TE: Disable Link State export");
+ if (isis_zebra_ls_register(false) != 0)
+ zlog_warn("Unable to register Link State");
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/enabled
+ */
+int isis_instance_segment_routing_enabled_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->srdb.config.enabled = yang_dnode_get_bool(args->dnode, NULL);
+
+ if (area->srdb.config.enabled) {
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("SR: Segment Routing: OFF -> ON");
+
+ isis_sr_start(area);
+ } else {
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("SR: Segment Routing: ON -> OFF");
+
+ isis_sr_stop(area);
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks
+ */
+int isis_instance_segment_routing_label_blocks_pre_validate(
+ struct nb_cb_pre_validate_args *args)
+{
+ uint32_t srgb_lbound;
+ uint32_t srgb_ubound;
+ uint32_t srlb_lbound;
+ uint32_t srlb_ubound;
+
+ srgb_lbound = yang_dnode_get_uint32(args->dnode, "./srgb/lower-bound");
+ srgb_ubound = yang_dnode_get_uint32(args->dnode, "./srgb/upper-bound");
+ srlb_lbound = yang_dnode_get_uint32(args->dnode, "./srlb/lower-bound");
+ srlb_ubound = yang_dnode_get_uint32(args->dnode, "./srlb/upper-bound");
+
+ /* Check that the block size does not exceed 65535 */
+ if ((srgb_ubound - srgb_lbound + 1) > 65535) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "New SR Global Block (%u/%u) exceed the limit of 65535",
+ srgb_lbound, srgb_ubound);
+ return NB_ERR_VALIDATION;
+ }
+ if ((srlb_ubound - srlb_lbound + 1) > 65535) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "New SR Local Block (%u/%u) exceed the limit of 65535",
+ srlb_lbound, srlb_ubound);
+ return NB_ERR_VALIDATION;
+ }
+
+ /* Validate SRGB against SRLB */
+ if (!((srgb_ubound < srlb_lbound) || (srgb_lbound > srlb_ubound))) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "SR Global Block (%u/%u) conflicts with Local Block (%u/%u)",
+ srgb_lbound, srgb_ubound, srlb_lbound, srlb_ubound);
+ return NB_ERR_VALIDATION;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srgb
+ */
+
+void isis_instance_segment_routing_srgb_apply_finish(
+ struct nb_cb_apply_finish_args *args)
+{
+ struct isis_area *area;
+ uint32_t lower_bound, upper_bound;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ lower_bound = yang_dnode_get_uint32(args->dnode, "./lower-bound");
+ upper_bound = yang_dnode_get_uint32(args->dnode, "./upper-bound");
+
+ isis_sr_cfg_srgb_update(area, lower_bound, upper_bound);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srgb/lower-bound
+ */
+int isis_instance_segment_routing_srgb_lower_bound_modify(
+ struct nb_cb_modify_args *args)
+{
+ uint32_t lower_bound = yang_dnode_get_uint32(args->dnode, NULL);
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ if (!IS_MPLS_UNRESERVED_LABEL(lower_bound)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "Invalid SRGB lower bound: %u", lower_bound);
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ case NB_EV_APPLY:
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srgb/upper-bound
+ */
+int isis_instance_segment_routing_srgb_upper_bound_modify(
+ struct nb_cb_modify_args *args)
+{
+ uint32_t upper_bound = yang_dnode_get_uint32(args->dnode, NULL);
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ if (!IS_MPLS_UNRESERVED_LABEL(upper_bound)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "Invalid SRGB upper bound: %u", upper_bound);
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ case NB_EV_APPLY:
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srlb
+ */
+void isis_instance_segment_routing_srlb_apply_finish(
+ struct nb_cb_apply_finish_args *args)
+{
+ struct isis_area *area;
+ uint32_t lower_bound, upper_bound;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ lower_bound = yang_dnode_get_uint32(args->dnode, "./lower-bound");
+ upper_bound = yang_dnode_get_uint32(args->dnode, "./upper-bound");
+
+ isis_sr_cfg_srlb_update(area, lower_bound, upper_bound);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srlb/lower-bound
+ */
+int isis_instance_segment_routing_srlb_lower_bound_modify(
+ struct nb_cb_modify_args *args)
+{
+ uint32_t lower_bound = yang_dnode_get_uint32(args->dnode, NULL);
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ if (!IS_MPLS_UNRESERVED_LABEL(lower_bound)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "Invalid SRLB lower bound: %u", lower_bound);
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ case NB_EV_APPLY:
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srlb/upper-bound
+ */
+int isis_instance_segment_routing_srlb_upper_bound_modify(
+ struct nb_cb_modify_args *args)
+{
+ uint32_t upper_bound = yang_dnode_get_uint32(args->dnode, NULL);
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ if (!IS_MPLS_UNRESERVED_LABEL(upper_bound)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "Invalid SRLB upper bound: %u", upper_bound);
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ case NB_EV_APPLY:
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/msd/node-msd
+ */
+int isis_instance_segment_routing_msd_node_msd_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->srdb.config.msd = yang_dnode_get_uint8(args->dnode, NULL);
+
+ /* Update and regenerate LSP */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_segment_routing_msd_node_msd_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->srdb.config.msd = 0;
+
+ /* Update and regenerate LSP */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid
+ */
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_create(
+ struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+ struct prefix prefix;
+ struct sr_prefix_cfg *pcfg;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ yang_dnode_get_prefix(&prefix, args->dnode, "./prefix");
+
+ pcfg = isis_sr_cfg_prefix_add(area, &prefix, SR_ALGORITHM_SPF);
+ nb_running_set_entry(args->dnode, pcfg);
+
+ return NB_OK;
+}
+
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pcfg = nb_running_unset_entry(args->dnode);
+ area = pcfg->area;
+ isis_sr_cfg_prefix_del(pcfg);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_pre_validate(
+ struct nb_cb_pre_validate_args *args)
+{
+ const struct lyd_node *area_dnode;
+ struct isis_area *area;
+ struct prefix prefix;
+ uint32_t srgb_lbound;
+ uint32_t srgb_ubound;
+ uint32_t srgb_range;
+ uint32_t sid;
+ enum sr_sid_value_type sid_type;
+ struct isis_prefix_sid psid = {};
+
+ yang_dnode_get_prefix(&prefix, args->dnode, "./prefix");
+ srgb_lbound = yang_dnode_get_uint32(
+ args->dnode, "../../label-blocks/srgb/lower-bound");
+ srgb_ubound = yang_dnode_get_uint32(
+ args->dnode, "../../label-blocks/srgb/upper-bound");
+ sid = yang_dnode_get_uint32(args->dnode, "./sid-value");
+ sid_type = yang_dnode_get_enum(args->dnode, "./sid-value-type");
+
+ /* Check for invalid indexes/labels. */
+ srgb_range = srgb_ubound - srgb_lbound + 1;
+ psid.value = sid;
+ switch (sid_type) {
+ case SR_SID_VALUE_TYPE_INDEX:
+ if (sid >= srgb_range) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "SID index %u falls outside local SRGB range",
+ sid);
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case SR_SID_VALUE_TYPE_ABSOLUTE:
+ if (!IS_MPLS_UNRESERVED_LABEL(sid)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "Invalid absolute SID %u", sid);
+ return NB_ERR_VALIDATION;
+ }
+ SET_FLAG(psid.flags, ISIS_PREFIX_SID_VALUE);
+ SET_FLAG(psid.flags, ISIS_PREFIX_SID_LOCAL);
+ break;
+ }
+
+ /* Check for Prefix-SID collisions. */
+ area_dnode = yang_dnode_get_parent(args->dnode, "instance");
+ area = nb_running_get_entry(area_dnode, NULL, false);
+ if (area) {
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2;
+ level++) {
+ struct isis_spftree *spftree;
+ struct isis_vertex *vertex_psid;
+
+ if (!(area->is_type & level))
+ continue;
+ spftree = area->spftree[tree][level - 1];
+ if (!spftree)
+ continue;
+
+ vertex_psid = isis_spf_prefix_sid_lookup(
+ spftree, &psid);
+ if (vertex_psid
+ && !prefix_same(&vertex_psid->N.ip.p.dest,
+ &prefix)) {
+ snprintfrr(
+ args->errmsg, args->errmsg_len,
+ "Prefix-SID collision detected, SID %s %u is already in use by prefix %pFX (L%u)",
+ CHECK_FLAG(
+ psid.flags,
+ ISIS_PREFIX_SID_VALUE)
+ ? "label"
+ : "index",
+ psid.value,
+ &vertex_psid->N.ip.p.dest,
+ level);
+ return NB_ERR_VALIDATION;
+ }
+ }
+ }
+ }
+
+ return NB_OK;
+}
+
+void isis_instance_segment_routing_prefix_sid_map_prefix_sid_apply_finish(
+ struct nb_cb_apply_finish_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+ struct isis_area *area;
+
+ pcfg = nb_running_get_entry(args->dnode, NULL, true);
+ area = pcfg->area;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/sid-value-type
+ */
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_type_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pcfg = nb_running_get_entry(args->dnode, NULL, true);
+ pcfg->sid_type = yang_dnode_get_enum(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/sid-value
+ */
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pcfg = nb_running_get_entry(args->dnode, NULL, true);
+ pcfg->sid = yang_dnode_get_uint32(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/last-hop-behavior
+ */
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_last_hop_behavior_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pcfg = nb_running_get_entry(args->dnode, NULL, true);
+ pcfg->last_hop_behavior = yang_dnode_get_enum(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/n-flag-clear
+ */
+int isis_instance_segment_routing_prefix_sid_map_prefix_sid_n_flag_clear_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pcfg = nb_running_get_entry(args->dnode, NULL, true);
+ pcfg->n_flag_clear = yang_dnode_get_bool(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid
+ */
+int isis_instance_segment_routing_algorithm_prefix_sid_create(
+ struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+ struct prefix prefix;
+ struct sr_prefix_cfg *pcfg;
+ uint32_t algorithm;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ yang_dnode_get_prefix(&prefix, args->dnode, "./prefix");
+ algorithm = yang_dnode_get_uint32(args->dnode, "./algo");
+
+ pcfg = isis_sr_cfg_prefix_add(area, &prefix, algorithm);
+ pcfg->algorithm = algorithm;
+ nb_running_set_entry(args->dnode, pcfg);
+
+ return NB_OK;
+}
+
+int isis_instance_segment_routing_algorithm_prefix_sid_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pcfg = nb_running_unset_entry(args->dnode);
+ area = pcfg->area;
+ isis_sr_cfg_prefix_del(pcfg);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int isis_instance_segment_routing_algorithm_prefix_sid_pre_validate(
+ struct nb_cb_pre_validate_args *args)
+{
+ const struct lyd_node *area_dnode;
+ struct isis_area *area;
+ struct prefix prefix;
+ uint32_t srgb_lbound;
+ uint32_t srgb_ubound;
+ uint32_t srgb_range;
+ uint32_t sid;
+ enum sr_sid_value_type sid_type;
+ struct isis_prefix_sid psid = {};
+
+ yang_dnode_get_prefix(&prefix, args->dnode, "./prefix");
+ srgb_lbound = yang_dnode_get_uint32(
+ args->dnode, "../../label-blocks/srgb/lower-bound");
+ srgb_ubound = yang_dnode_get_uint32(
+ args->dnode, "../../label-blocks/srgb/upper-bound");
+ sid = yang_dnode_get_uint32(args->dnode, "./sid-value");
+ sid_type = yang_dnode_get_enum(args->dnode, "./sid-value-type");
+
+ /* Check for invalid indexes/labels. */
+ srgb_range = srgb_ubound - srgb_lbound + 1;
+ psid.value = sid;
+ switch (sid_type) {
+ case SR_SID_VALUE_TYPE_INDEX:
+ if (sid >= srgb_range) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "SID index %u falls outside local SRGB range",
+ sid);
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case SR_SID_VALUE_TYPE_ABSOLUTE:
+ if (!IS_MPLS_UNRESERVED_LABEL(sid)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "Invalid absolute SID %u", sid);
+ return NB_ERR_VALIDATION;
+ }
+ SET_FLAG(psid.flags, ISIS_PREFIX_SID_VALUE);
+ SET_FLAG(psid.flags, ISIS_PREFIX_SID_LOCAL);
+ break;
+ }
+
+ /* Check for Prefix-SID collisions. */
+ area_dnode = yang_dnode_get_parent(args->dnode, "instance");
+ area = nb_running_get_entry(area_dnode, NULL, false);
+ if (!area)
+ return NB_OK;
+
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+ struct isis_spftree *spftree;
+ struct isis_vertex *vertex_psid;
+
+ if (!(area->is_type & level))
+ continue;
+ spftree = area->spftree[tree][level - 1];
+ if (!spftree)
+ continue;
+
+ vertex_psid =
+ isis_spf_prefix_sid_lookup(spftree, &psid);
+ if (vertex_psid &&
+ !prefix_same(&vertex_psid->N.ip.p.dest, &prefix)) {
+ snprintfrr(
+ args->errmsg, args->errmsg_len,
+ "Prefix-SID collision detected, SID %s %u is already in use by prefix %pFX (L%u)",
+ CHECK_FLAG(psid.flags,
+ ISIS_PREFIX_SID_VALUE)
+ ? "label"
+ : "index",
+ psid.value, &vertex_psid->N.ip.p.dest,
+ level);
+ return NB_ERR_VALIDATION;
+ }
+ }
+ }
+
+ return NB_OK;
+}
+
+void isis_instance_segment_routing_algorithm_prefix_sid_apply_finish(
+ struct nb_cb_apply_finish_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+ struct isis_area *area;
+
+ pcfg = nb_running_get_entry(args->dnode, NULL, true);
+ area = pcfg->area;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/sid-value-type
+ */
+int isis_instance_segment_routing_algorithm_prefix_sid_sid_value_type_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pcfg = nb_running_get_entry(args->dnode, NULL, true);
+ pcfg->sid_type = yang_dnode_get_enum(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/sid-value
+ */
+int isis_instance_segment_routing_algorithm_prefix_sid_sid_value_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pcfg = nb_running_get_entry(args->dnode, NULL, true);
+ pcfg->sid = yang_dnode_get_uint32(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sid-map/algorithm-prefix-sid/last-hop-behavior
+ */
+int isis_instance_segment_routing_algorithm_prefix_sid_last_hop_behavior_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pcfg = nb_running_get_entry(args->dnode, NULL, true);
+ pcfg->last_hop_behavior = yang_dnode_get_enum(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/n-flag-clear
+ */
+int isis_instance_segment_routing_algorithm_prefix_sid_n_flag_clear_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct sr_prefix_cfg *pcfg;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pcfg = nb_running_get_entry(args->dnode, NULL, true);
+ pcfg->n_flag_clear = yang_dnode_get_bool(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo
+ */
+int isis_instance_flex_algo_create(struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+ struct flex_algo *fa;
+ bool advertise;
+ uint32_t algorithm;
+ uint32_t priority = FLEX_ALGO_PRIO_DEFAULT;
+ struct isis_flex_algo_alloc_arg arg;
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "./flex-algo");
+ advertise = yang_dnode_exists(args->dnode, "./advertise-definition");
+
+ switch (args->event) {
+ case NB_EV_APPLY:
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ arg.algorithm = algorithm;
+ arg.area = area;
+ fa = flex_algo_alloc(area->flex_algos, algorithm, &arg);
+ fa->priority = priority;
+ fa->advertise_definition = advertise;
+ if (area->admin_group_send_zero) {
+ admin_group_allow_explicit_zero(
+ &fa->admin_group_exclude_any);
+ admin_group_allow_explicit_zero(
+ &fa->admin_group_include_any);
+ admin_group_allow_explicit_zero(
+ &fa->admin_group_include_all);
+ }
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ }
+
+ return NB_OK;
+}
+
+int isis_instance_flex_algo_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ uint32_t algorithm;
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "./flex-algo");
+ area = nb_running_get_entry(args->dnode, NULL, true);
+
+ switch (args->event) {
+ case NB_EV_APPLY:
+ flex_algo_delete(area->flex_algos, algorithm);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/advertise-definition
+ */
+int isis_instance_flex_algo_advertise_definition_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct flex_algo *fa;
+ bool advertise;
+ uint32_t algorithm;
+
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "./../flex-algo");
+ advertise = yang_dnode_exists(args->dnode, "./../advertise-definition");
+
+ switch (args->event) {
+ case NB_EV_APPLY:
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ fa->advertise_definition = advertise;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ }
+
+ return NB_OK;
+}
+
+int isis_instance_flex_algo_advertise_definition_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ struct flex_algo *fa;
+ uint32_t algorithm;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "./../flex-algo");
+
+ switch (args->event) {
+ case NB_EV_APPLY:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ fa->advertise_definition = false;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int isis_instance_flex_algo_affinity_set(struct nb_cb_create_args *args,
+ int type)
+{
+ struct affinity_map *map;
+ struct isis_area *area;
+ struct admin_group *ag;
+ struct flex_algo *fa;
+ uint32_t algorithm;
+ const char *val;
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "../../flex-algo");
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ val = yang_dnode_get_string(args->dnode, ".");
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ map = affinity_map_get(val);
+ if (!map) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "affinity map %s isn't found", val);
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ map = affinity_map_get(val);
+ if (!map) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "affinity map %s isn't found", val);
+ return NB_ERR_RESOURCE;
+ }
+ if (type == AFFINITY_INCLUDE_ANY)
+ ag = &fa->admin_group_include_any;
+ else if (type == AFFINITY_INCLUDE_ALL)
+ ag = &fa->admin_group_include_all;
+ else if (type == AFFINITY_EXCLUDE_ANY)
+ ag = &fa->admin_group_exclude_any;
+ else
+ break;
+
+ admin_group_set(ag, map->bit_position);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int
+isis_instance_flex_algo_affinity_unset(struct nb_cb_destroy_args *args,
+ int type)
+{
+ struct affinity_map *map;
+ struct isis_area *area;
+ struct admin_group *ag;
+ struct flex_algo *fa;
+ uint32_t algorithm;
+ const char *val;
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "../../flex-algo");
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ val = yang_dnode_get_string(args->dnode, ".");
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ map = affinity_map_get(val);
+ if (!map) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "affinity map %s isn't found", val);
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ map = affinity_map_get(val);
+ if (!map) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "affinity map %s isn't found", val);
+ return NB_ERR_RESOURCE;
+ }
+ if (type == AFFINITY_INCLUDE_ANY)
+ ag = &fa->admin_group_include_any;
+ else if (type == AFFINITY_INCLUDE_ALL)
+ ag = &fa->admin_group_include_all;
+ else if (type == AFFINITY_EXCLUDE_ANY)
+ ag = &fa->admin_group_exclude_any;
+ else
+ break;
+
+ admin_group_unset(ag, map->bit_position);
+ if (area->admin_group_send_zero)
+ admin_group_allow_explicit_zero(ag);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/flex-algos/flex-algo/affinity-include-anies/affinity-include-any
+ */
+int isis_instance_flex_algo_affinity_include_any_create(
+ struct nb_cb_create_args *args)
+{
+ return isis_instance_flex_algo_affinity_set(args, AFFINITY_INCLUDE_ANY);
+}
+
+int isis_instance_flex_algo_affinity_include_any_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return isis_instance_flex_algo_affinity_unset(args,
+ AFFINITY_INCLUDE_ANY);
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/flex-algos/flex-algo/affinity-include-alls/affinity-include-all
+ */
+int isis_instance_flex_algo_affinity_include_all_create(
+ struct nb_cb_create_args *args)
+{
+ return isis_instance_flex_algo_affinity_set(args, AFFINITY_INCLUDE_ALL);
+}
+
+int isis_instance_flex_algo_affinity_include_all_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return isis_instance_flex_algo_affinity_unset(args,
+ AFFINITY_INCLUDE_ALL);
+}
+
+/*
+ * XPath:
+ * /frr-isisd:isis/instance/flex-algos/flex-algo/affinity-exclude-anies/affinity-exclude-any
+ */
+int isis_instance_flex_algo_affinity_exclude_any_create(
+ struct nb_cb_create_args *args)
+{
+ return isis_instance_flex_algo_affinity_set(args, AFFINITY_EXCLUDE_ANY);
+}
+
+int isis_instance_flex_algo_affinity_exclude_any_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return isis_instance_flex_algo_affinity_unset(args,
+ AFFINITY_EXCLUDE_ANY);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/prefix-metric
+ */
+
+int isis_instance_flex_algo_prefix_metric_create(struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+ const char *area_tag;
+ struct flex_algo *fa;
+ uint32_t algorithm;
+
+ area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag");
+ area = isis_area_lookup(area_tag, VRF_DEFAULT);
+ if (!area)
+ return NB_ERR_RESOURCE;
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "./../flex-algo");
+
+ switch (args->event) {
+ case NB_EV_APPLY:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ SET_FLAG(fa->flags, FAD_FLAG_M);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ }
+
+ return NB_OK;
+}
+
+int isis_instance_flex_algo_prefix_metric_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ const char *area_tag;
+ struct flex_algo *fa;
+ uint32_t algorithm;
+
+ area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag");
+ area = isis_area_lookup(area_tag, VRF_DEFAULT);
+ if (!area)
+ return NB_ERR_RESOURCE;
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "./../flex-algo");
+
+ switch (args->event) {
+ case NB_EV_APPLY:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ UNSET_FLAG(fa->flags, FAD_FLAG_M);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int isis_instance_flex_algo_dplane_set(struct nb_cb_create_args *args,
+ int type)
+{
+ struct isis_area *area;
+ const char *area_tag;
+ struct flex_algo *fa;
+ uint32_t algorithm;
+
+ area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag");
+ area = isis_area_lookup(area_tag, VRF_DEFAULT);
+ if (!area)
+ return NB_ERR_RESOURCE;
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "./../flex-algo");
+
+ switch (args->event) {
+ case NB_EV_APPLY:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ SET_FLAG(fa->dataplanes, type);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ case NB_EV_VALIDATE:
+ if (type == FLEX_ALGO_SRV6 || type == FLEX_ALGO_IP) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "%s Flex-algo dataplane is not yet supported.",
+ type == FLEX_ALGO_SRV6 ? "SRv6" : "IP");
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int isis_instance_flex_algo_dplane_unset(struct nb_cb_destroy_args *args,
+ int type)
+{
+ struct isis_area *area;
+ const char *area_tag;
+ struct flex_algo *fa;
+ uint32_t algorithm;
+
+ area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag");
+ area = isis_area_lookup(area_tag, VRF_DEFAULT);
+ if (!area)
+ return NB_ERR_RESOURCE;
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "./../flex-algo");
+
+ switch (args->event) {
+ case NB_EV_APPLY:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ UNSET_FLAG(fa->dataplanes, type);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/dplane-sr-mpls
+ */
+
+int isis_instance_flex_algo_dplane_sr_mpls_create(
+ struct nb_cb_create_args *args)
+{
+ return isis_instance_flex_algo_dplane_set(args, FLEX_ALGO_SR_MPLS);
+}
+
+int isis_instance_flex_algo_dplane_sr_mpls_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return isis_instance_flex_algo_dplane_unset(args, FLEX_ALGO_SR_MPLS);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/dplane-srv6
+ */
+
+int isis_instance_flex_algo_dplane_srv6_create(struct nb_cb_create_args *args)
+{
+ return isis_instance_flex_algo_dplane_set(args, FLEX_ALGO_SRV6);
+}
+
+int isis_instance_flex_algo_dplane_srv6_destroy(struct nb_cb_destroy_args *args)
+{
+ return isis_instance_flex_algo_dplane_unset(args, FLEX_ALGO_SRV6);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/dplane-ip
+ */
+
+int isis_instance_flex_algo_dplane_ip_create(struct nb_cb_create_args *args)
+{
+ return isis_instance_flex_algo_dplane_set(args, FLEX_ALGO_IP);
+}
+
+int isis_instance_flex_algo_dplane_ip_destroy(struct nb_cb_destroy_args *args)
+{
+ return isis_instance_flex_algo_dplane_unset(args, FLEX_ALGO_IP);
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/metric-type
+ */
+
+int isis_instance_flex_algo_metric_type_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ const char *area_tag;
+ struct flex_algo *fa;
+ uint32_t algorithm;
+ enum flex_algo_metric_type metric_type;
+
+ area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag");
+ area = isis_area_lookup(area_tag, VRF_DEFAULT);
+ if (!area)
+ return NB_ERR_RESOURCE;
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "./../flex-algo");
+ metric_type = yang_dnode_get_enum(args->dnode, NULL);
+
+ switch (args->event) {
+ case NB_EV_APPLY:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ fa->metric_type = metric_type;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/priority
+ */
+
+int isis_instance_flex_algo_priority_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ const char *area_tag;
+ struct flex_algo *fa;
+ uint32_t algorithm;
+ uint32_t priority;
+
+ area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag");
+ area = isis_area_lookup(area_tag, VRF_DEFAULT);
+ if (!area)
+ return NB_ERR_RESOURCE;
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "./../flex-algo");
+ priority = yang_dnode_get_uint32(args->dnode, NULL);
+
+ switch (args->event) {
+ case NB_EV_APPLY:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ fa->priority = priority;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ }
+
+ return NB_OK;
+}
+
+int isis_instance_flex_algo_priority_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ const char *area_tag;
+ struct flex_algo *fa;
+ uint32_t algorithm;
+ uint32_t priority = FLEX_ALGO_PRIO_DEFAULT;
+
+ area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag");
+ area = isis_area_lookup(area_tag, VRF_DEFAULT);
+ if (!area)
+ return NB_ERR_RESOURCE;
+
+ algorithm = yang_dnode_get_uint32(args->dnode, "./../flex-algo");
+ priority = yang_dnode_get_uint32(args->dnode, NULL);
+
+ switch (args->event) {
+ case NB_EV_APPLY:
+ fa = flex_algo_lookup(area->flex_algos, algorithm);
+ if (!fa) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "flex-algo object not found");
+ return NB_ERR_RESOURCE;
+ }
+ fa->priority = priority;
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ break;
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/enabled
+ */
+int isis_instance_segment_routing_srv6_enabled_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->srv6db.config.enabled = yang_dnode_get_bool(args->dnode, NULL);
+
+ if (area->srv6db.config.enabled) {
+ if (IS_DEBUG_EVENTS)
+ zlog_debug(
+ "Segment Routing over IPv6 (SRv6): OFF -> ON");
+ } else {
+ if (IS_DEBUG_EVENTS)
+ zlog_debug(
+ "Segment Routing over IPv6 (SRv6): ON -> OFF");
+ }
+
+ /* Regenerate LSPs to advertise SRv6 capabilities or signal that the
+ * node is no longer SRv6-capable. */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/locator
+ */
+int isis_instance_segment_routing_srv6_locator_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ const char *loc_name;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(lyd_parent(lyd_parent(args->dnode)), NULL,
+ true);
+
+ loc_name = yang_dnode_get_string(args->dnode, NULL);
+
+ if (strncmp(loc_name, area->srv6db.config.srv6_locator_name,
+ sizeof(area->srv6db.config.srv6_locator_name)) == 0) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "SRv6 locator %s is already configured", loc_name);
+ return NB_ERR_NO_CHANGES;
+ }
+
+ /* Remove previously configured locator */
+ if (strncmp(area->srv6db.config.srv6_locator_name, "",
+ sizeof(area->srv6db.config.srv6_locator_name)) != 0) {
+ sr_debug("Unsetting previously configured SRv6 locator");
+ if (!isis_srv6_locator_unset(area)) {
+ zlog_warn("Failed to unset SRv6 locator");
+ return NB_ERR;
+ }
+ }
+
+ strlcpy(area->srv6db.config.srv6_locator_name, loc_name,
+ sizeof(area->srv6db.config.srv6_locator_name));
+
+ sr_debug("Configured SRv6 locator %s for IS-IS area %s", loc_name,
+ area->area_tag);
+
+ sr_debug("Trying to get a chunk from locator %s for IS-IS area %s",
+ loc_name, area->area_tag);
+
+ if (isis_zebra_srv6_manager_get_locator_chunk(loc_name) < 0)
+ return NB_ERR;
+
+ return NB_OK;
+}
+
+int isis_instance_segment_routing_srv6_locator_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ const char *loc_name;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(lyd_parent(lyd_parent(args->dnode)), NULL,
+ true);
+
+ loc_name = yang_dnode_get_string(args->dnode, NULL);
+
+ sr_debug("Trying to unset SRv6 locator %s", loc_name);
+
+ if (strncmp(loc_name, area->srv6db.config.srv6_locator_name,
+ sizeof(area->srv6db.config.srv6_locator_name)) != 0) {
+ sr_debug("SRv6 locator %s is not configured", loc_name);
+ snprintf(args->errmsg, args->errmsg_len,
+ "SRv6 locator %s is not configured", loc_name);
+ return NB_ERR_NO_CHANGES;
+ }
+
+ if (!isis_srv6_locator_unset(area)) {
+ zlog_warn("Failed to unset SRv6 locator");
+ return NB_ERR;
+ }
+
+ sr_debug("Deleted SRv6 locator %s for IS-IS area %s", loc_name,
+ area->area_tag);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-segs-left
+ */
+int isis_instance_segment_routing_srv6_msd_node_msd_max_segs_left_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->srv6db.config.max_seg_left_msd = yang_dnode_get_uint8(args->dnode,
+ NULL);
+
+ /* Update and regenerate LSP */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-pop
+ */
+int isis_instance_segment_routing_srv6_msd_node_msd_max_end_pop_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->srv6db.config.max_end_pop_msd = yang_dnode_get_uint8(args->dnode,
+ NULL);
+
+ /* Update and regenerate LSP */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-h-encaps
+ */
+int isis_instance_segment_routing_srv6_msd_node_msd_max_h_encaps_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->srv6db.config.max_h_encaps_msd = yang_dnode_get_uint8(args->dnode,
+ NULL);
+
+ /* Update and regenerate LSP */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-d
+ */
+int isis_instance_segment_routing_srv6_msd_node_msd_max_end_d_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ area->srv6db.config.max_end_d_msd = yang_dnode_get_uint8(args->dnode,
+ NULL);
+
+ /* Update and regenerate LSP */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/segment-routing-srv6/interface
+ */
+int isis_instance_segment_routing_srv6_interface_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ const char *ifname;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(lyd_parent(lyd_parent(args->dnode)), NULL,
+ true);
+
+ ifname = yang_dnode_get_string(args->dnode, NULL);
+
+ sr_debug("Changing SRv6 interface for IS-IS area %s to %s",
+ area->area_tag, ifname);
+
+ isis_srv6_interface_set(area, ifname);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/mpls/ldp-sync
+ */
+int isis_instance_mpls_ldp_sync_create(struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+ const char *vrfname;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ vrfname = yang_dnode_get_string(
+ lyd_parent(lyd_parent(args->dnode)), "./vrf");
+
+ if (strcmp(vrfname, VRF_DEFAULT_NAME)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "LDP-Sync only runs on Default VRF");
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_ldp_sync_enable(area);
+ break;
+ }
+ return NB_OK;
+}
+
+int isis_instance_mpls_ldp_sync_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ isis_area_ldp_sync_disable(area);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-isisd:isis/instance/mpls/ldp-sync/holddown
+ */
+int isis_instance_mpls_ldp_sync_holddown_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ uint16_t holddown;
+ const char *vrfname;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ vrfname = yang_dnode_get_string(
+ lyd_parent(lyd_parent(lyd_parent(args->dnode))),
+ "./vrf");
+
+ if (strcmp(vrfname, VRF_DEFAULT_NAME)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "LDP-Sync only runs on Default VRF");
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ area = nb_running_get_entry(args->dnode, NULL, true);
+ holddown = yang_dnode_get_uint16(args->dnode, NULL);
+ isis_area_ldp_sync_set_holddown(area, holddown);
+ break;
+ }
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis
+ */
+int lib_interface_isis_create(struct nb_cb_create_args *args)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit = NULL;
+ const char *area_tag = yang_dnode_get_string(args->dnode, "./area-tag");
+
+ switch (args->event) {
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ case NB_EV_VALIDATE:
+ break;
+ case NB_EV_APPLY:
+ ifp = nb_running_get_entry(args->dnode, NULL, true);
+ circuit = isis_circuit_new(ifp, area_tag);
+ nb_running_set_entry(args->dnode, circuit);
+ break;
+ }
+
+ return NB_OK;
+}
+
+int lib_interface_isis_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_unset_entry(args->dnode);
+
+ isis_circuit_del(circuit);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/area-tag
+ */
+int lib_interface_isis_area_tag_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event == NB_EV_VALIDATE) {
+ circuit = nb_running_get_entry_non_rec(lyd_parent(args->dnode),
+ NULL, false);
+ if (circuit) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "Changing area tag is not allowed");
+ return NB_ERR_VALIDATION;
+ }
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/circuit-type
+ */
+int lib_interface_isis_circuit_type_modify(struct nb_cb_modify_args *args)
+{
+ int circ_type = yang_dnode_get_enum(args->dnode, NULL);
+ struct isis_circuit *circuit;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->is_type_config = circ_type;
+ isis_circuit_is_type_set(circuit, circ_type);
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv4-routing
+ */
+int lib_interface_isis_ipv4_routing_modify(struct nb_cb_modify_args *args)
+{
+ bool ipv4, ipv6;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ ipv4 = yang_dnode_get_bool(args->dnode, NULL);
+ ipv6 = yang_dnode_get_bool(args->dnode, "../ipv6-routing");
+ isis_circuit_af_set(circuit, ipv4, ipv6);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv6-routing
+ */
+int lib_interface_isis_ipv6_routing_modify(struct nb_cb_modify_args *args)
+{
+ bool ipv4, ipv6;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ ipv4 = yang_dnode_get_bool(args->dnode, "../ipv4-routing");
+ ipv6 = yang_dnode_get_bool(args->dnode, NULL);
+ isis_circuit_af_set(circuit, ipv4, ipv6);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring
+ */
+void lib_interface_isis_bfd_monitoring_apply_finish(
+ struct nb_cb_apply_finish_args *args)
+{
+ struct isis_circuit *circuit;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ isis_bfd_circuit_cmd(circuit);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring/enabled
+ */
+int lib_interface_isis_bfd_monitoring_enabled_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->bfd_config.enabled = yang_dnode_get_bool(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring/profile
+ */
+int lib_interface_isis_bfd_monitoring_profile_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ XFREE(MTYPE_TMP, circuit->bfd_config.profile);
+ circuit->bfd_config.profile =
+ XSTRDUP(MTYPE_TMP, yang_dnode_get_string(args->dnode, NULL));
+
+ return NB_OK;
+}
+
+int lib_interface_isis_bfd_monitoring_profile_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ XFREE(MTYPE_TMP, circuit->bfd_config.profile);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-1
+ */
+int lib_interface_isis_csnp_interval_level_1_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->csnp_interval[0] = yang_dnode_get_uint16(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-2
+ */
+int lib_interface_isis_csnp_interval_level_2_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->csnp_interval[1] = yang_dnode_get_uint16(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-1
+ */
+int lib_interface_isis_psnp_interval_level_1_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->psnp_interval[0] = yang_dnode_get_uint16(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-2
+ */
+int lib_interface_isis_psnp_interval_level_2_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->psnp_interval[1] = yang_dnode_get_uint16(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/padding
+ */
+int lib_interface_isis_hello_padding_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->pad_hellos = yang_dnode_get_enum(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-1
+ */
+int lib_interface_isis_hello_interval_level_1_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ uint32_t interval;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ interval = yang_dnode_get_uint32(args->dnode, NULL);
+ circuit->hello_interval[0] = interval;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-2
+ */
+int lib_interface_isis_hello_interval_level_2_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ uint32_t interval;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ interval = yang_dnode_get_uint32(args->dnode, NULL);
+ circuit->hello_interval[1] = interval;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-1
+ */
+int lib_interface_isis_hello_multiplier_level_1_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ uint16_t multi;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ multi = yang_dnode_get_uint16(args->dnode, NULL);
+ circuit->hello_multiplier[0] = multi;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-2
+ */
+int lib_interface_isis_hello_multiplier_level_2_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ uint16_t multi;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ multi = yang_dnode_get_uint16(args->dnode, NULL);
+ circuit->hello_multiplier[1] = multi;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/metric/level-1
+ */
+int lib_interface_isis_metric_level_1_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ unsigned int met;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ met = yang_dnode_get_uint32(args->dnode, NULL);
+ isis_circuit_metric_set(circuit, IS_LEVEL_1, met);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/metric/level-2
+ */
+int lib_interface_isis_metric_level_2_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ unsigned int met;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ met = yang_dnode_get_uint32(args->dnode, NULL);
+ isis_circuit_metric_set(circuit, IS_LEVEL_2, met);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/priority/level-1
+ */
+int lib_interface_isis_priority_level_1_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->priority[0] = yang_dnode_get_uint8(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/priority/level-2
+ */
+int lib_interface_isis_priority_level_2_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->priority[1] = yang_dnode_get_uint8(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/network-type
+ */
+int lib_interface_isis_network_type_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ int net_type = yang_dnode_get_enum(args->dnode, NULL);
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ circuit = nb_running_get_entry(args->dnode, NULL, false);
+ if (!circuit)
+ break;
+ if (circuit->circ_type == CIRCUIT_T_LOOPBACK) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "Cannot change network type on loopback interface");
+ return NB_ERR_VALIDATION;
+ }
+ if (net_type == CIRCUIT_T_BROADCAST
+ && circuit->state == C_STATE_UP
+ && !if_is_broadcast(circuit->interface)) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "Cannot configure non-broadcast interface for broadcast operation");
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ isis_circuit_circ_type_set(circuit, net_type);
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/passive
+ */
+int lib_interface_isis_passive_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ bool passive = yang_dnode_get_bool(args->dnode, NULL);
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ isis_circuit_passive_set(circuit, passive);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/password
+ */
+int lib_interface_isis_password_create(struct nb_cb_create_args *args)
+{
+ return NB_OK;
+}
+
+int lib_interface_isis_password_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ isis_circuit_passwd_unset(circuit);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/password/password
+ */
+int lib_interface_isis_password_password_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ const char *password;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ password = yang_dnode_get_string(args->dnode, NULL);
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+
+ isis_circuit_passwd_set(circuit, circuit->passwd.type, password);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/password/password-type
+ */
+int lib_interface_isis_password_password_type_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ uint8_t pass_type;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pass_type = yang_dnode_get_enum(args->dnode, NULL);
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->passwd.type = pass_type;
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/disable-three-way-handshake
+ */
+int lib_interface_isis_disable_three_way_handshake_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->disable_threeway_adj = yang_dnode_get_bool(args->dnode, NULL);
+
+ return NB_OK;
+}
+
+static int lib_interface_isis_multi_topology_common(
+ enum nb_event event, const struct lyd_node *dnode, char *errmsg,
+ size_t errmsg_len, uint16_t mtid)
+{
+ struct isis_circuit *circuit;
+ bool value;
+
+ switch (event) {
+ case NB_EV_VALIDATE:
+ circuit = nb_running_get_entry(dnode, NULL, false);
+ if (circuit && circuit->area && circuit->area->oldmetric) {
+ snprintf(
+ errmsg, errmsg_len,
+ "Multi topology IS-IS can only be used with wide metrics");
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ circuit = nb_running_get_entry(dnode, NULL, true);
+ value = yang_dnode_get_bool(dnode, NULL);
+ isis_circuit_mt_enabled_set(circuit, mtid, value);
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/standard
+ */
+int lib_interface_isis_multi_topology_standard_modify(
+ struct nb_cb_modify_args *args)
+{
+ return lib_interface_isis_multi_topology_common(
+ args->event, args->dnode, args->errmsg, args->errmsg_len,
+ ISIS_MT_STANDARD);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-multicast
+ */
+int lib_interface_isis_multi_topology_ipv4_multicast_modify(
+ struct nb_cb_modify_args *args)
+{
+ return lib_interface_isis_multi_topology_common(
+ args->event, args->dnode, args->errmsg, args->errmsg_len,
+ ISIS_MT_IPV4_MULTICAST);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-management
+ */
+int lib_interface_isis_multi_topology_ipv4_management_modify(
+ struct nb_cb_modify_args *args)
+{
+ return lib_interface_isis_multi_topology_common(
+ args->event, args->dnode, args->errmsg, args->errmsg_len,
+ ISIS_MT_IPV4_MGMT);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-unicast
+ */
+int lib_interface_isis_multi_topology_ipv6_unicast_modify(
+ struct nb_cb_modify_args *args)
+{
+ return lib_interface_isis_multi_topology_common(
+ args->event, args->dnode, args->errmsg, args->errmsg_len,
+ ISIS_MT_IPV6_UNICAST);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-multicast
+ */
+int lib_interface_isis_multi_topology_ipv6_multicast_modify(
+ struct nb_cb_modify_args *args)
+{
+ return lib_interface_isis_multi_topology_common(
+ args->event, args->dnode, args->errmsg, args->errmsg_len,
+ ISIS_MT_IPV6_MULTICAST);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-management
+ */
+int lib_interface_isis_multi_topology_ipv6_management_modify(
+ struct nb_cb_modify_args *args)
+{
+ return lib_interface_isis_multi_topology_common(
+ args->event, args->dnode, args->errmsg, args->errmsg_len,
+ ISIS_MT_IPV6_MGMT);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-dstsrc
+ */
+int lib_interface_isis_multi_topology_ipv6_dstsrc_modify(
+ struct nb_cb_modify_args *args)
+{
+ return lib_interface_isis_multi_topology_common(
+ args->event, args->dnode, args->errmsg, args->errmsg_len,
+ ISIS_MT_IPV6_DSTSRC);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/mpls/ldp-sync
+ */
+int lib_interface_isis_mpls_ldp_sync_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ struct ldp_sync_info *ldp_sync_info;
+ bool ldp_sync_enable;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ ldp_sync_enable = yang_dnode_get_bool(args->dnode, NULL);
+
+ ldp_sync_info = circuit->ldp_sync_info;
+
+ SET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_IF_CONFIG);
+ ldp_sync_info->enabled = ldp_sync_enable;
+
+ if (circuit->area) {
+ if (ldp_sync_enable)
+ isis_if_ldp_sync_enable(circuit);
+ else
+ isis_if_ldp_sync_disable(circuit);
+ }
+ break;
+ }
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-isisd:isis/mpls/holddown
+ */
+int lib_interface_isis_mpls_holddown_modify(struct nb_cb_modify_args *args)
+{
+ struct isis_circuit *circuit;
+ struct ldp_sync_info *ldp_sync_info;
+ uint16_t holddown;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ holddown = yang_dnode_get_uint16(args->dnode, NULL);
+
+ ldp_sync_info = circuit->ldp_sync_info;
+
+ SET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN);
+ ldp_sync_info->holddown = holddown;
+ break;
+ }
+ return NB_OK;
+}
+
+int lib_interface_isis_mpls_holddown_destroy(struct nb_cb_destroy_args *args)
+{
+ struct isis_circuit *circuit;
+ struct ldp_sync_info *ldp_sync_info;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ ldp_sync_info = circuit->ldp_sync_info;
+
+ UNSET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN);
+
+ if (circuit->area)
+ isis_if_set_ldp_sync_holddown(circuit);
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/lfa/enable
+ */
+int lib_interface_isis_fast_reroute_level_1_lfa_enable_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->lfa_protection[0] = yang_dnode_get_bool(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area) {
+ if (circuit->lfa_protection[0])
+ area->lfa_protected_links[0]++;
+ else {
+ assert(area->lfa_protected_links[0] > 0);
+ area->lfa_protected_links[0]--;
+ }
+
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/lfa/exclude-interface
+ */
+int lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_create(
+ struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ const char *exclude_ifname;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ exclude_ifname = yang_dnode_get_string(args->dnode, NULL);
+
+ isis_lfa_excluded_iface_add(circuit, ISIS_LEVEL1, exclude_ifname);
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ const char *exclude_ifname;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ exclude_ifname = yang_dnode_get_string(args->dnode, NULL);
+
+ isis_lfa_excluded_iface_delete(circuit, ISIS_LEVEL1, exclude_ifname);
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/remote-lfa/enable
+ */
+int lib_interface_isis_fast_reroute_level_1_remote_lfa_enable_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->rlfa_protection[0] = yang_dnode_get_bool(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area) {
+ if (circuit->rlfa_protection[0])
+ area->rlfa_protected_links[0]++;
+ else {
+ assert(area->rlfa_protected_links[0] > 0);
+ area->rlfa_protected_links[0]--;
+ }
+
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/remote-lfa/maximum-metric
+ */
+int lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->rlfa_max_metric[0] = yang_dnode_get_uint32(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->rlfa_max_metric[0] = 0;
+
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/enable
+ */
+int lib_interface_isis_fast_reroute_level_1_ti_lfa_enable_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->tilfa_protection[0] = yang_dnode_get_bool(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area) {
+ if (circuit->tilfa_protection[0])
+ area->tilfa_protected_links[0]++;
+ else {
+ assert(area->tilfa_protected_links[0] > 0);
+ area->tilfa_protected_links[0]--;
+ }
+
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/node-protection
+ */
+int lib_interface_isis_fast_reroute_level_1_ti_lfa_node_protection_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->tilfa_node_protection[0] =
+ yang_dnode_get_bool(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/link-fallback
+ */
+int lib_interface_isis_fast_reroute_level_1_ti_lfa_link_fallback_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->tilfa_link_fallback[0] =
+ yang_dnode_get_bool(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/lfa/enable
+ */
+int lib_interface_isis_fast_reroute_level_2_lfa_enable_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->lfa_protection[1] = yang_dnode_get_bool(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area) {
+ if (circuit->lfa_protection[1])
+ area->lfa_protected_links[1]++;
+ else {
+ assert(area->lfa_protected_links[1] > 0);
+ area->lfa_protected_links[1]--;
+ }
+
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/lfa/exclude-interface
+ */
+int lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_create(
+ struct nb_cb_create_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ const char *exclude_ifname;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ exclude_ifname = yang_dnode_get_string(args->dnode, NULL);
+
+ isis_lfa_excluded_iface_add(circuit, ISIS_LEVEL2, exclude_ifname);
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ const char *exclude_ifname;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ exclude_ifname = yang_dnode_get_string(args->dnode, NULL);
+
+ isis_lfa_excluded_iface_delete(circuit, ISIS_LEVEL2, exclude_ifname);
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/remote-lfa/enable
+ */
+int lib_interface_isis_fast_reroute_level_2_remote_lfa_enable_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->rlfa_protection[1] = yang_dnode_get_bool(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area) {
+ if (circuit->rlfa_protection[1])
+ area->rlfa_protected_links[1]++;
+ else {
+ assert(area->rlfa_protected_links[1] > 0);
+ area->rlfa_protected_links[1]--;
+ }
+
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/remote-lfa/maximum-metric
+ */
+int lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->rlfa_max_metric[1] = yang_dnode_get_uint32(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+int lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->rlfa_max_metric[1] = 0;
+
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/enable
+ */
+int lib_interface_isis_fast_reroute_level_2_ti_lfa_enable_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->tilfa_protection[1] = yang_dnode_get_bool(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area) {
+ if (circuit->tilfa_protection[1])
+ area->tilfa_protected_links[1]++;
+ else {
+ assert(area->tilfa_protected_links[1] > 0);
+ area->tilfa_protected_links[1]--;
+ }
+
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/node-protection
+ */
+int lib_interface_isis_fast_reroute_level_2_ti_lfa_node_protection_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->tilfa_node_protection[1] =
+ yang_dnode_get_bool(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/link-fallback
+ */
+int lib_interface_isis_fast_reroute_level_2_ti_lfa_link_fallback_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ circuit = nb_running_get_entry(args->dnode, NULL, true);
+ circuit->tilfa_link_fallback[1] =
+ yang_dnode_get_bool(args->dnode, NULL);
+
+ area = circuit->area;
+ if (area)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return NB_OK;
+}
diff --git a/isisd/isis_nb_notifications.c b/isisd/isis_nb_notifications.c
new file mode 100644
index 0000000..5a1e312
--- /dev/null
+++ b/isisd/isis_nb_notifications.c
@@ -0,0 +1,586 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 Volta Networks
+ * Emanuele Di Pascale
+ */
+
+#include <zebra.h>
+
+#include "northbound.h"
+#include "log.h"
+
+#include "isisd/isisd.h"
+#include "isisd/isis_nb.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_dynhn.h"
+#include "isisd/isis_misc.h"
+
+DEFINE_HOOK(isis_hook_lsp_too_large,
+ (const struct isis_circuit *circuit, uint32_t pdu_size,
+ const uint8_t *lsp_id),
+ (circuit, pdu_size, lsp_id));
+DEFINE_HOOK(isis_hook_corrupted_lsp, (const struct isis_area *area), (area));
+DEFINE_HOOK(isis_hook_lsp_exceed_max,
+ (const struct isis_area *area, const uint8_t *lsp_id),
+ (area, lsp_id));
+DEFINE_HOOK(isis_hook_max_area_addr_mismatch,
+ (const struct isis_circuit *circuit, uint8_t max_addrs,
+ const char *raw_pdu, size_t raw_pdu_len),
+ (circuit, max_addrs, raw_pdu, raw_pdu_len));
+DEFINE_HOOK(isis_hook_authentication_type_failure,
+ (const struct isis_circuit *circuit, const char *raw_pdu,
+ size_t raw_pdu_len),
+ (circuit, raw_pdu, raw_pdu_len));
+DEFINE_HOOK(isis_hook_authentication_failure,
+ (const struct isis_circuit *circuit, const char *raw_pdu,
+ size_t raw_pdu_len),
+ (circuit, raw_pdu, raw_pdu_len));
+DEFINE_HOOK(isis_hook_adj_state_change, (const struct isis_adjacency *adj),
+ (adj));
+DEFINE_HOOK(isis_hook_reject_adjacency,
+ (const struct isis_circuit *circuit, const char *raw_pdu,
+ size_t raw_pdu_len),
+ (circuit, raw_pdu, raw_pdu_len));
+DEFINE_HOOK(isis_hook_area_mismatch,
+ (const struct isis_circuit *circuit, const char *raw_pdu,
+ size_t raw_pdu_len),
+ (circuit, raw_pdu, raw_pdu_len));
+DEFINE_HOOK(isis_hook_id_len_mismatch,
+ (const struct isis_circuit *circuit, uint8_t rcv_id_len,
+ const char *raw_pdu, size_t raw_pdu_len),
+ (circuit, rcv_id_len, raw_pdu, raw_pdu_len));
+DEFINE_HOOK(isis_hook_version_skew,
+ (const struct isis_circuit *circuit, uint8_t version,
+ const char *raw_pdu, size_t raw_pdu_len),
+ (circuit, version, raw_pdu, raw_pdu_len));
+DEFINE_HOOK(isis_hook_lsp_error,
+ (const struct isis_circuit *circuit, const uint8_t *lsp_id,
+ const char *raw_pdu, size_t raw_pdu_len),
+ (circuit, lsp_id, raw_pdu, raw_pdu_len));
+DEFINE_HOOK(isis_hook_seqno_skipped,
+ (const struct isis_circuit *circuit, const uint8_t *lsp_id),
+ (circuit, lsp_id));
+DEFINE_HOOK(isis_hook_own_lsp_purge,
+ (const struct isis_circuit *circuit, const uint8_t *lsp_id),
+ (circuit, lsp_id));
+
+
+/*
+ * Helper functions.
+ */
+static void notif_prep_instance_hdr(const char *xpath,
+ const struct isis_area *area,
+ const char *routing_instance,
+ struct list *args)
+{
+ char xpath_arg[XPATH_MAXLEN];
+ struct yang_data *data;
+
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/routing-instance", xpath);
+ data = yang_data_new_string(xpath_arg, routing_instance);
+ listnode_add(args, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/routing-protocol-name",
+ xpath);
+ data = yang_data_new_string(xpath_arg, area->area_tag);
+ listnode_add(args, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/isis-level", xpath);
+ data = yang_data_new_enum(xpath_arg, area->is_type);
+ listnode_add(args, data);
+}
+
+static void notif_prepr_iface_hdr(const char *xpath,
+ const struct isis_circuit *circuit,
+ struct list *args)
+{
+ char xpath_arg[XPATH_MAXLEN];
+ struct yang_data *data;
+
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-name", xpath);
+ data = yang_data_new_string(xpath_arg, circuit->interface->name);
+ listnode_add(args, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-level", xpath);
+ data = yang_data_new_enum(xpath_arg, circuit->is_type);
+ listnode_add(args, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/extended-circuit-id", xpath);
+ /* we do not seem to have the extended version of the circuit_id */
+ data = yang_data_new_uint32(xpath_arg, (uint32_t)circuit->circuit_id);
+ listnode_add(args, data);
+}
+
+/*
+ * XPath: /frr-isisd:database-overload
+ */
+void isis_notif_db_overload(const struct isis_area *area, bool overload)
+{
+ const char *xpath = "/frr-isisd:database-overload";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ struct yang_data *data;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/overload", xpath);
+ data = yang_data_new_enum(xpath_arg, !!overload);
+ listnode_add(arguments, data);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:lsp-too-large
+ */
+void isis_notif_lsp_too_large(const struct isis_circuit *circuit,
+ uint32_t pdu_size, const uint8_t *lsp_id)
+{
+ const char *xpath = "/frr-isisd:lsp-too-large";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ char xpath_value[ISO_SYSID_STRLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/pdu-size", xpath);
+ data = yang_data_new_uint32(xpath_arg, pdu_size);
+ listnode_add(arguments, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath);
+ snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id);
+ data = yang_data_new_string(xpath_arg, xpath_value);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_lsp_too_large, circuit, pdu_size, lsp_id);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:if-state-change
+ */
+void isis_notif_if_state_change(const struct isis_circuit *circuit, bool down)
+{
+ const char *xpath = "/frr-isisd:if-state-change";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/state", xpath);
+ data = yang_data_new_enum(xpath_arg, !!down);
+ listnode_add(arguments, data);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:corrupted-lsp-detected
+ */
+void isis_notif_corrupted_lsp(const struct isis_area *area,
+ const uint8_t *lsp_id)
+{
+ const char *xpath = "/frr-isisd:corrupted-lsp-detected";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ char xpath_value[ISO_SYSID_STRLEN];
+ struct yang_data *data;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath);
+ snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id);
+ data = yang_data_new_string(xpath_arg, xpath_value);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_corrupted_lsp, area);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:attempt-to-exceed-max-sequence
+ */
+void isis_notif_lsp_exceed_max(const struct isis_area *area,
+ const uint8_t *lsp_id)
+{
+ const char *xpath = "/frr-isisd:attempt-to-exceed-max-sequence";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ char xpath_value[ISO_SYSID_STRLEN];
+ struct yang_data *data;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath);
+ snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id);
+ data = yang_data_new_string(xpath_arg, xpath_value);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_lsp_exceed_max, area, lsp_id);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:max-area-addresses-mismatch
+ */
+void isis_notif_max_area_addr_mismatch(const struct isis_circuit *circuit,
+ uint8_t max_area_addrs,
+ const char *raw_pdu, size_t raw_pdu_len)
+{
+ const char *xpath = "/frr-isisd:max-area-addresses-mismatch";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/max-area-addresses", xpath);
+ data = yang_data_new_uint8(xpath_arg, max_area_addrs);
+ listnode_add(arguments, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath);
+ data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_max_area_addr_mismatch, circuit, max_area_addrs,
+ raw_pdu, raw_pdu_len);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:authentication-type-failure
+ */
+void isis_notif_authentication_type_failure(const struct isis_circuit *circuit,
+ const char *raw_pdu,
+ size_t raw_pdu_len)
+{
+ const char *xpath = "/frr-isisd:authentication-type-failure";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath);
+ data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_authentication_type_failure, circuit, raw_pdu,
+ raw_pdu_len);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:authentication-failure
+ */
+void isis_notif_authentication_failure(const struct isis_circuit *circuit,
+ const char *raw_pdu, size_t raw_pdu_len)
+{
+ const char *xpath = "/frr-isisd:authentication-failure";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath);
+ data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_authentication_failure, circuit, raw_pdu,
+ raw_pdu_len);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:adjacency-state-change
+ */
+void isis_notif_adj_state_change(const struct isis_adjacency *adj,
+ int new_state, const char *reason)
+{
+ const char *xpath = "/frr-isisd:adjacency-state-change";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ char xpath_value[ISO_SYSID_STRLEN];
+ struct yang_data *data;
+ struct isis_circuit *circuit = adj->circuit;
+ struct isis_area *area = circuit->area;
+ struct isis_dynhn *dyn = dynhn_find_by_id(circuit->isis, adj->sysid);
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ if (dyn) {
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/neighbor", xpath);
+ data = yang_data_new_string(xpath_arg, dyn->hostname);
+ listnode_add(arguments, data);
+ }
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/neighbor-system-id", xpath);
+ snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pSY", adj->sysid);
+ data = yang_data_new_string(xpath_arg, xpath_value);
+ listnode_add(arguments, data);
+
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/state", xpath);
+ data = yang_data_new_string(xpath_arg, isis_adj_yang_state(new_state));
+ listnode_add(arguments, data);
+ if (new_state == ISIS_ADJ_DOWN) {
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/reason", xpath);
+ data = yang_data_new_string(xpath_arg, reason);
+ listnode_add(arguments, data);
+ }
+
+ hook_call(isis_hook_adj_state_change, adj);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:rejected-adjacency
+ */
+void isis_notif_reject_adjacency(const struct isis_circuit *circuit,
+ const char *reason, const char *raw_pdu,
+ size_t raw_pdu_len)
+{
+ const char *xpath = "/frr-isisd:rejected-adjacency";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/reason", xpath);
+ data = yang_data_new_string(xpath_arg, reason);
+ listnode_add(arguments, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath);
+ data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_reject_adjacency, circuit, raw_pdu, raw_pdu_len);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:area-mismatch
+ */
+void isis_notif_area_mismatch(const struct isis_circuit *circuit,
+ const char *raw_pdu, size_t raw_pdu_len)
+{
+ const char *xpath = "/frr-isisd:area-mismatch";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath);
+ data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_area_mismatch, circuit, raw_pdu, raw_pdu_len);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:lsp-received
+ */
+void isis_notif_lsp_received(const struct isis_circuit *circuit,
+ const uint8_t *lsp_id, uint32_t seqno,
+ uint32_t timestamp, const char *sys_id)
+{
+ const char *xpath = "/frr-isisd:lsp-received";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ char xpath_value[ISO_SYSID_STRLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath);
+ snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id);
+ data = yang_data_new_string(xpath_arg, xpath_value);
+ listnode_add(arguments, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/sequence", xpath);
+ data = yang_data_new_uint32(xpath_arg, seqno);
+ listnode_add(arguments, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/received-timestamp", xpath);
+ data = yang_data_new_uint32(xpath_arg, timestamp);
+ listnode_add(arguments, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/neighbor-system-id", xpath);
+ data = yang_data_new_string(xpath_arg, sys_id);
+ listnode_add(arguments, data);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:lsp-generation
+ */
+void isis_notif_lsp_gen(const struct isis_area *area, const uint8_t *lsp_id,
+ uint32_t seqno, uint32_t timestamp)
+{
+ const char *xpath = "/frr-isisd:lsp-generation";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ char xpath_value[ISO_SYSID_STRLEN];
+ struct yang_data *data;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath);
+ snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id);
+ data = yang_data_new_string(xpath_arg, xpath_value);
+ listnode_add(arguments, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/sequence", xpath);
+ data = yang_data_new_uint32(xpath_arg, seqno);
+ listnode_add(arguments, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/send-timestamp", xpath);
+ data = yang_data_new_uint32(xpath_arg, timestamp);
+ listnode_add(arguments, data);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:id-len-mismatch
+ */
+void isis_notif_id_len_mismatch(const struct isis_circuit *circuit,
+ uint8_t rcv_id_len, const char *raw_pdu,
+ size_t raw_pdu_len)
+{
+ const char *xpath = "/frr-isisd:id-len-mismatch";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/pdu-field-len", xpath);
+ data = yang_data_new_uint8(xpath_arg, rcv_id_len);
+ listnode_add(arguments, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath);
+ data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_id_len_mismatch, circuit, rcv_id_len, raw_pdu,
+ raw_pdu_len);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:version-skew
+ */
+void isis_notif_version_skew(const struct isis_circuit *circuit,
+ uint8_t version, const char *raw_pdu,
+ size_t raw_pdu_len)
+{
+ const char *xpath = "/frr-isisd:version-skew";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/protocol-version", xpath);
+ data = yang_data_new_uint8(xpath_arg, version);
+ listnode_add(arguments, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath);
+ data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_version_skew, circuit, version, raw_pdu,
+ raw_pdu_len);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:lsp-error-detected
+ */
+void isis_notif_lsp_error(const struct isis_circuit *circuit,
+ const uint8_t *lsp_id, const char *raw_pdu,
+ size_t raw_pdu_len,
+ __attribute__((unused)) uint32_t offset,
+ __attribute__((unused)) uint8_t tlv_type)
+{
+ const char *xpath = "/frr-isisd:lsp-error-detected";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ char xpath_value[ISO_SYSID_STRLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath);
+ snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id);
+ data = yang_data_new_string(xpath_arg, xpath_value);
+ listnode_add(arguments, data);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath);
+ data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len);
+ listnode_add(arguments, data);
+ /* ignore offset and tlv_type which cannot be set properly */
+
+ hook_call(isis_hook_lsp_error, circuit, lsp_id, raw_pdu, raw_pdu_len);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:sequence-number-skipped
+ */
+void isis_notif_seqno_skipped(const struct isis_circuit *circuit,
+ const uint8_t *lsp_id)
+{
+ const char *xpath = "/frr-isisd:sequence-number-skipped";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ char xpath_value[ISO_SYSID_STRLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath);
+ snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id);
+ data = yang_data_new_string(xpath_arg, xpath_value);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_seqno_skipped, circuit, lsp_id);
+
+ nb_notification_send(xpath, arguments);
+}
+
+/*
+ * XPath: /frr-isisd:own-lsp-purge
+ */
+void isis_notif_own_lsp_purge(const struct isis_circuit *circuit,
+ const uint8_t *lsp_id)
+{
+ const char *xpath = "/frr-isisd:own-lsp-purge";
+ struct list *arguments = yang_data_list_new();
+ char xpath_arg[XPATH_MAXLEN];
+ char xpath_value[ISO_SYSID_STRLEN];
+ struct yang_data *data;
+ struct isis_area *area = circuit->area;
+
+ notif_prep_instance_hdr(xpath, area, "default", arguments);
+ notif_prepr_iface_hdr(xpath, circuit, arguments);
+ snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath);
+ snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id);
+ data = yang_data_new_string(xpath_arg, xpath_value);
+ listnode_add(arguments, data);
+
+ hook_call(isis_hook_own_lsp_purge, circuit, lsp_id);
+
+ nb_notification_send(xpath, arguments);
+}
diff --git a/isisd/isis_nb_state.c b/isisd/isis_nb_state.c
new file mode 100644
index 0000000..b7c33ed
--- /dev/null
+++ b/isisd/isis_nb_state.c
@@ -0,0 +1,630 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 Volta Networks
+ * Emanuele Di Pascale
+ */
+
+#include <zebra.h>
+
+#include "northbound.h"
+#include "linklist.h"
+
+#include "isisd/isisd.h"
+#include "isisd/isis_nb.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_misc.h"
+
+/*
+ * XPath: /frr-interface:lib/interface/state/frr-isisd:isis
+ */
+struct yang_data *
+lib_interface_state_isis_get_elem(struct nb_cb_get_elem_args *args)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+
+ ifp = (struct interface *)args->list_entry;
+ if (!ifp)
+ return NULL;
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit || !circuit->area)
+ return NULL;
+
+ return yang_data_new(args->xpath, NULL);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency
+ */
+const void *lib_interface_state_isis_adjacencies_adjacency_get_next(
+ struct nb_cb_get_next_args *args)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+ struct isis_adjacency *adj = NULL, *adj_next = NULL;
+ struct list *list;
+ struct listnode *node, *node_next;
+
+ /* Get first adjacency. */
+ if (args->list_entry == NULL) {
+ ifp = (struct interface *)args->parent_list_entry;
+ if (!ifp)
+ return NULL;
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit)
+ return NULL;
+
+ switch (circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS;
+ level++) {
+ struct list *adjdb;
+
+ adjdb = circuit->u.bc.adjdb[level - 1];
+ if (adjdb) {
+ adj = listnode_head(adjdb);
+ if (adj)
+ break;
+ }
+ }
+ break;
+ case CIRCUIT_T_P2P:
+ adj = circuit->u.p2p.neighbor;
+ break;
+ default:
+ break;
+ }
+
+ return adj;
+ }
+
+ /* Get next adjacency. */
+ adj = (struct isis_adjacency *)args->list_entry;
+ circuit = adj->circuit;
+ switch (circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ list = circuit->u.bc.adjdb[adj->level - 1];
+ node = listnode_lookup(list, adj);
+ node_next = listnextnode(node);
+ if (node_next)
+ adj_next = listgetdata(node_next);
+ else if (adj->level == ISIS_LEVEL1) {
+ /*
+ * Once we finish the L1 adjacencies, move to the L2
+ * adjacencies list.
+ */
+ list = circuit->u.bc.adjdb[ISIS_LEVEL2 - 1];
+ adj_next = listnode_head(list);
+ }
+ break;
+ case CIRCUIT_T_P2P:
+ /* P2P circuits have at most one adjacency. */
+ default:
+ break;
+ }
+
+ return adj_next;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-sys-type
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_neighbor_sys_type_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct isis_adjacency *adj = args->list_entry;
+
+ return yang_data_new_enum(args->xpath, adj->level);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-sysid
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_neighbor_sysid_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct isis_adjacency *adj = args->list_entry;
+ char xpath_value[ISO_SYSID_STRLEN];
+
+ snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pSY", adj->sysid);
+
+ return yang_data_new_string(args->xpath, xpath_value);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-extended-circuit-id
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_neighbor_extended_circuit_id_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct isis_adjacency *adj = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, adj->circuit->circuit_id);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-snpa
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_neighbor_snpa_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct isis_adjacency *adj = args->list_entry;
+ char xpath_value[ISO_SYSID_STRLEN];
+
+ snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pSY", adj->snpa);
+
+ return yang_data_new_string(args->xpath, xpath_value);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/hold-timer
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_hold_timer_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct isis_adjacency *adj = args->list_entry;
+
+ return yang_data_new_uint16(args->xpath, adj->hold_time);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-priority
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_neighbor_priority_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct isis_adjacency *adj = args->list_entry;
+
+ return yang_data_new_uint8(args->xpath, adj->prio[adj->level - 1]);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/state
+ */
+struct yang_data *lib_interface_state_isis_adjacencies_adjacency_state_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct isis_adjacency *adj = args->list_entry;
+
+ return yang_data_new_string(args->xpath,
+ isis_adj_yang_state(adj->adj_state));
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid
+ */
+const void *
+lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_get_next(
+ struct nb_cb_get_next_args *args)
+{
+ const struct isis_adjacency *adj = args->parent_list_entry;
+ const struct sr_adjacency *sra = args->list_entry, *sra_next = NULL;
+ struct listnode *node, *node_next;
+
+ if (args->list_entry == NULL)
+ sra_next = listnode_head(adj->adj_sids);
+ else {
+ node = listnode_lookup(adj->adj_sids, sra);
+ node_next = listnextnode(node);
+ if (node_next)
+ sra_next = listgetdata(node_next);
+ }
+
+ return sra_next;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/af
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_af_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct sr_adjacency *sra = args->list_entry;
+
+ switch (sra->adj->circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ /* Adjacency SID is not published with circuit type Broadcast */
+ return NULL;
+ case CIRCUIT_T_P2P:
+ return yang_data_new_uint8(args->xpath, sra->u.adj_sid->family);
+ }
+
+ return NULL;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/value
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_value_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct sr_adjacency *sra = args->list_entry;
+
+ switch (sra->adj->circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ /* Adjacency SID is not published with circuit type Broadcast */
+ return NULL;
+ case CIRCUIT_T_P2P:
+ return yang_data_new_uint32(args->xpath, sra->u.adj_sid->sid);
+ }
+
+ return NULL;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/weight
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_weight_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct sr_adjacency *sra = args->list_entry;
+
+ switch (sra->adj->circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ /* Adjacency SID is not published with circuit type Broadcast */
+ return NULL;
+ case CIRCUIT_T_P2P:
+ return yang_data_new_uint8(args->xpath, sra->u.adj_sid->weight);
+ }
+
+ return NULL;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/protection-requested
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_protection_requested_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct sr_adjacency *sra = args->list_entry;
+
+ switch (sra->adj->circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ /* Adjacency SID is not published with circuit type Broadcast */
+ return NULL;
+ case CIRCUIT_T_P2P:
+ return yang_data_new_bool(args->xpath,
+ sra->u.adj_sid->flags &
+ EXT_SUBTLV_LINK_ADJ_SID_BFLG);
+ }
+
+ return NULL;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid
+ */
+const void *
+lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_get_next(
+ struct nb_cb_get_next_args *args)
+{
+ const struct isis_adjacency *adj = args->parent_list_entry;
+ const struct sr_adjacency *sra = args->list_entry, *sra_next = NULL;
+ struct listnode *node, *node_next;
+
+ if (args->list_entry == NULL)
+ sra_next = listnode_head(adj->adj_sids);
+ else {
+ node = listnode_lookup(adj->adj_sids, sra);
+ node_next = listnextnode(node);
+ if (node_next)
+ sra_next = listgetdata(node_next);
+ }
+
+ return sra_next;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/af
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_af_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct sr_adjacency *sra = args->list_entry;
+
+ switch (sra->adj->circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ return yang_data_new_uint8(args->xpath,
+ sra->u.ladj_sid->family);
+ case CIRCUIT_T_P2P:
+ /* LAN adjacency SID is not published with circuit type P2P */
+ return NULL;
+ }
+
+ return NULL;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/value
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_value_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct sr_adjacency *sra = args->list_entry;
+
+ switch (sra->adj->circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ return yang_data_new_uint32(args->xpath, sra->u.ladj_sid->sid);
+ case CIRCUIT_T_P2P:
+ /* LAN adjacency SID is not published with circuit type P2P */
+ return NULL;
+ }
+
+ return NULL;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/weight
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_weight_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct sr_adjacency *sra = args->list_entry;
+
+ switch (sra->adj->circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ return yang_data_new_uint8(args->xpath,
+ sra->u.ladj_sid->weight);
+ case CIRCUIT_T_P2P:
+ /* LAN adjacency SID is not published with circuit type P2P */
+ return NULL;
+ }
+
+ return NULL;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/protection-requested
+ */
+struct yang_data *
+lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_protection_requested_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct sr_adjacency *sra = args->list_entry;
+
+ switch (sra->adj->circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ return yang_data_new_bool(args->xpath,
+ sra->u.ladj_sid->flags &
+ EXT_SUBTLV_LINK_ADJ_SID_BFLG);
+ case CIRCUIT_T_P2P:
+ /* LAN adjacency SID is not published with circuit type P2P */
+ return NULL;
+ }
+
+ return NULL;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-changes
+ */
+struct yang_data *
+lib_interface_state_isis_event_counters_adjacency_changes_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+
+ ifp = (struct interface *)args->list_entry;
+ if (!ifp)
+ return NULL;
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit)
+ return NULL;
+
+ return yang_data_new_uint32(args->xpath, circuit->adj_state_changes);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-number
+ */
+struct yang_data *
+lib_interface_state_isis_event_counters_adjacency_number_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+ struct isis_adjacency *adj;
+ struct listnode *node;
+ uint32_t total = 0;
+
+ ifp = (struct interface *)args->list_entry;
+ if (!ifp)
+ return NULL;
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit)
+ return NULL;
+
+ /*
+ * TODO: keep track of the number of adjacencies instead of calculating
+ * it on demand.
+ */
+ switch (circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) {
+ for (ALL_LIST_ELEMENTS_RO(
+ circuit->u.bc.adjdb[level - 1], node, adj))
+ total++;
+ }
+ break;
+ case CIRCUIT_T_P2P:
+ adj = circuit->u.p2p.neighbor;
+ if (adj)
+ total = 1;
+ break;
+ default:
+ break;
+ }
+
+ return yang_data_new_uint32(args->xpath, total);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/init-fails
+ */
+struct yang_data *lib_interface_state_isis_event_counters_init_fails_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+
+ ifp = (struct interface *)args->list_entry;
+ if (!ifp)
+ return NULL;
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit)
+ return NULL;
+
+ return yang_data_new_uint32(args->xpath, circuit->init_failures);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-rejects
+ */
+struct yang_data *
+lib_interface_state_isis_event_counters_adjacency_rejects_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+
+ ifp = (struct interface *)args->list_entry;
+ if (!ifp)
+ return NULL;
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit)
+ return NULL;
+
+ return yang_data_new_uint32(args->xpath, circuit->rej_adjacencies);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/id-len-mismatch
+ */
+struct yang_data *
+lib_interface_state_isis_event_counters_id_len_mismatch_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+
+ ifp = (struct interface *)args->list_entry;
+ if (!ifp)
+ return NULL;
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit)
+ return NULL;
+
+ return yang_data_new_uint32(args->xpath, circuit->id_len_mismatches);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/max-area-addresses-mismatch
+ */
+struct yang_data *
+lib_interface_state_isis_event_counters_max_area_addresses_mismatch_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+
+ ifp = (struct interface *)args->list_entry;
+ if (!ifp)
+ return NULL;
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit)
+ return NULL;
+
+ return yang_data_new_uint32(args->xpath,
+ circuit->max_area_addr_mismatches);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/authentication-type-fails
+ */
+struct yang_data *
+lib_interface_state_isis_event_counters_authentication_type_fails_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+
+ ifp = (struct interface *)args->list_entry;
+ if (!ifp)
+ return NULL;
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit)
+ return NULL;
+
+ return yang_data_new_uint32(args->xpath, circuit->auth_type_failures);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/authentication-fails
+ */
+struct yang_data *
+lib_interface_state_isis_event_counters_authentication_fails_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+
+ ifp = (struct interface *)args->list_entry;
+ if (!ifp)
+ return NULL;
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit)
+ return NULL;
+
+ return yang_data_new_uint32(args->xpath, circuit->auth_failures);
+}
diff --git a/isisd/isis_network.h b/isisd/isis_network.h
new file mode 100644
index 0000000..31cad32
--- /dev/null
+++ b/isisd/isis_network.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_network.h
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+
+#ifndef _ZEBRA_ISIS_NETWORK_H
+#define _ZEBRA_ISIS_NETWORK_H
+
+extern uint8_t ALL_L1_ISYSTEMS[];
+extern uint8_t ALL_L2_ISYSTEMS[];
+
+int isis_sock_init(struct isis_circuit *circuit);
+
+int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa);
+int isis_recv_pdu_p2p(struct isis_circuit *circuit, uint8_t *ssnpa);
+int isis_send_pdu_bcast(struct isis_circuit *circuit, int level);
+int isis_send_pdu_p2p(struct isis_circuit *circuit, int level);
+
+#endif /* _ZEBRA_ISIS_NETWORK_H */
diff --git a/isisd/isis_pdu.c b/isisd/isis_pdu.c
new file mode 100644
index 0000000..0cd43a7
--- /dev/null
+++ b/isisd/isis_pdu.c
@@ -0,0 +1,2585 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_pdu.c
+ * PDU processing
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+
+#include "memory.h"
+#include "frrevent.h"
+#include "linklist.h"
+#include "log.h"
+#include "stream.h"
+#include "vty.h"
+#include "hash.h"
+#include "prefix.h"
+#include "if.h"
+#include "checksum.h"
+#include "md5.h"
+#include "lib_errors.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_network.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_dr.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_dynhn.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/iso_checksum.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isis_events.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_tlvs.h"
+#include "isisd/isis_errors.h"
+#include "isisd/fabricd.h"
+#include "isisd/isis_tx_queue.h"
+#include "isisd/isis_pdu_counter.h"
+#include "isisd/isis_nb.h"
+
+static int ack_lsp(struct isis_lsp_hdr *hdr, struct isis_circuit *circuit,
+ int level)
+{
+ unsigned long lenp;
+ int retval;
+ uint16_t length;
+ uint8_t pdu_type =
+ (level == IS_LEVEL_1) ? L1_PARTIAL_SEQ_NUM : L2_PARTIAL_SEQ_NUM;
+
+ isis_circuit_stream(circuit, &circuit->snd_stream);
+
+ fill_fixed_hdr(pdu_type, circuit->snd_stream);
+
+ lenp = stream_get_endp(circuit->snd_stream);
+
+ stream_putw(circuit->snd_stream, 0); /* PDU length */
+ stream_put(circuit->snd_stream, circuit->isis->sysid, ISIS_SYS_ID_LEN);
+ stream_putc(circuit->snd_stream, circuit->idx);
+ stream_putc(circuit->snd_stream, 9); /* code */
+ stream_putc(circuit->snd_stream, 16); /* len */
+
+ stream_putw(circuit->snd_stream, hdr->rem_lifetime);
+ stream_put(circuit->snd_stream, hdr->lsp_id, ISIS_SYS_ID_LEN + 2);
+ stream_putl(circuit->snd_stream, hdr->seqno);
+ stream_putw(circuit->snd_stream, hdr->checksum);
+
+ length = (uint16_t)stream_get_endp(circuit->snd_stream);
+ /* Update PDU length */
+ stream_putw_at(circuit->snd_stream, lenp, length);
+
+ pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type);
+ retval = circuit->tx(circuit, level);
+ if (retval != ISIS_OK)
+ flog_err(EC_ISIS_PACKET,
+ "ISIS-Upd (%s): Send L%d LSP PSNP on %s failed",
+ circuit->area->area_tag, level,
+ circuit->interface->name);
+
+ return retval;
+}
+
+/*
+ * RECEIVE SIDE
+ */
+
+struct iih_info {
+ struct isis_circuit *circuit;
+ uint8_t *ssnpa;
+ int level;
+
+ uint8_t circ_type;
+ uint8_t sys_id[ISIS_SYS_ID_LEN];
+ uint16_t holdtime;
+ uint16_t pdu_len;
+
+ uint8_t circuit_id;
+
+ uint8_t priority;
+ uint8_t dis[ISIS_SYS_ID_LEN + 1];
+
+ bool v4_usable;
+ bool v6_usable;
+
+ struct isis_tlvs *tlvs;
+};
+
+static int process_p2p_hello(struct iih_info *iih)
+{
+ struct isis_threeway_adj *tw_adj = iih->tlvs->threeway_adj;
+
+ if (tw_adj) {
+ if (tw_adj->state > ISIS_THREEWAY_DOWN) {
+ if (IS_DEBUG_ADJ_PACKETS) {
+ zlog_debug("ISIS-Adj (%s): Rcvd P2P IIH from (%s) with invalid three-way state: %d",
+ iih->circuit->area->area_tag,
+ iih->circuit->interface->name,
+ tw_adj->state);
+ }
+ return ISIS_WARNING;
+ }
+
+ if (tw_adj->neighbor_set
+ && (memcmp(tw_adj->neighbor_id, iih->circuit->isis->sysid,
+ ISIS_SYS_ID_LEN)
+ || tw_adj->neighbor_circuit_id
+ != (uint32_t)iih->circuit->idx)) {
+
+ if (IS_DEBUG_ADJ_PACKETS) {
+ zlog_debug("ISIS-Adj (%s): Rcvd P2P IIH from (%s) which lists IS/Circuit different from us as neighbor.",
+ iih->circuit->area->area_tag,
+ iih->circuit->interface->name);
+ }
+
+ return ISIS_WARNING;
+ }
+ }
+
+ /*
+ * My interpertation of the ISO, if no adj exists we will create one for
+ * the circuit
+ */
+ struct isis_adjacency *adj = iih->circuit->u.p2p.neighbor;
+ /* If an adjacency exists, check it is with the source of the hello
+ * packets */
+ if (adj) {
+ if (memcmp(iih->sys_id, adj->sysid, ISIS_SYS_ID_LEN)) {
+ zlog_debug(
+ "hello source and adjacency do not match, set adj down");
+ isis_adj_state_change(&adj, ISIS_ADJ_DOWN,
+ "adj do not exist");
+ return ISIS_OK;
+ }
+ }
+ if (!adj || adj->level != iih->circ_type) {
+ if (!adj) {
+ adj = isis_new_adj(iih->sys_id, NULL, iih->circ_type,
+ iih->circuit);
+ } else {
+ adj->level = iih->circ_type;
+ }
+ iih->circuit->u.p2p.neighbor = adj;
+ /* Build lsp with the new neighbor entry when a new
+ * adjacency is formed. Set adjacency circuit type to
+ * IIH PDU header circuit type before lsp is regenerated
+ * when an adjacency is up. This will result in the new
+ * adjacency entry getting added to the lsp tlv neighbor list.
+ */
+ adj->circuit_t = iih->circ_type;
+ isis_adj_state_change(&adj, ISIS_ADJ_INITIALIZING, NULL);
+ adj->sys_type = ISIS_SYSTYPE_UNKNOWN;
+ }
+
+ if (tw_adj)
+ adj->ext_circuit_id = tw_adj->local_circuit_id;
+
+ /* 8.2.6 Monitoring point-to-point adjacencies */
+ adj->hold_time = iih->holdtime;
+ adj->last_upd = time(NULL);
+
+ bool changed;
+ isis_tlvs_to_adj(iih->tlvs, adj, &changed);
+ changed |= tlvs_to_adj_mt_set(iih->tlvs, iih->v4_usable, iih->v6_usable,
+ adj);
+
+ /* lets take care of the expiry */
+ EVENT_OFF(adj->t_expire);
+ event_add_timer(master, isis_adj_expire, adj, (long)adj->hold_time,
+ &adj->t_expire);
+
+ /* While fabricds initial sync is in progress, ignore hellos from other
+ * interfaces than the one we are performing the initial sync on. */
+ if (fabricd_initial_sync_is_in_progress(iih->circuit->area)
+ && fabricd_initial_sync_circuit(iih->circuit->area) != iih->circuit)
+ return ISIS_OK;
+
+ /* 8.2.5.2 a) a match was detected */
+ if (isis_tlvs_area_addresses_match(iih->tlvs,
+ iih->circuit->area->area_addrs)) {
+ /* 8.2.5.2 a) 2) If the system is L1 - table 5 */
+ if (iih->circuit->area->is_type == IS_LEVEL_1) {
+ switch (iih->circ_type) {
+ case IS_LEVEL_1:
+ case IS_LEVEL_1_AND_2:
+ if (adj->adj_state != ISIS_ADJ_UP
+ || adj->adj_usage == ISIS_ADJ_LEVEL1) {
+ isis_adj_process_threeway(adj, tw_adj,
+ ISIS_ADJ_LEVEL1);
+ }
+ break;
+ case IS_LEVEL_2:
+ if (adj->adj_state != ISIS_ADJ_UP) {
+ /* (7) reject - wrong system type event
+ */
+ zlog_warn("wrongSystemType");
+ return ISIS_WARNING;
+ } else if (adj->adj_usage == ISIS_ADJ_LEVEL1) {
+ /* (6) down - wrong system */
+ isis_adj_state_change(&adj,
+ ISIS_ADJ_DOWN,
+ "Wrong System");
+ }
+ break;
+ }
+ }
+
+ /* 8.2.5.2 a) 3) If the system is L1L2 - table 6 */
+ if (iih->circuit->area->is_type == IS_LEVEL_1_AND_2) {
+ switch (iih->circ_type) {
+ case IS_LEVEL_1:
+ if (adj->adj_state != ISIS_ADJ_UP
+ || adj->adj_usage == ISIS_ADJ_LEVEL1) {
+ isis_adj_process_threeway(adj, tw_adj,
+ ISIS_ADJ_LEVEL1);
+ } else if ((adj->adj_usage
+ == ISIS_ADJ_LEVEL1AND2)
+ || (adj->adj_usage
+ == ISIS_ADJ_LEVEL2)) {
+ /* (8) down - wrong system */
+ isis_adj_state_change(&adj,
+ ISIS_ADJ_DOWN,
+ "Wrong System");
+ }
+ break;
+ case IS_LEVEL_2:
+ if (adj->adj_state != ISIS_ADJ_UP
+ || adj->adj_usage == ISIS_ADJ_LEVEL2) {
+ isis_adj_process_threeway(adj, tw_adj,
+ ISIS_ADJ_LEVEL2);
+ } else if ((adj->adj_usage == ISIS_ADJ_LEVEL1)
+ || (adj->adj_usage
+ == ISIS_ADJ_LEVEL1AND2)) {
+ /* (8) down - wrong system */
+ isis_adj_state_change(&adj,
+ ISIS_ADJ_DOWN,
+ "Wrong System");
+ }
+ break;
+ case IS_LEVEL_1_AND_2:
+ if (adj->adj_state != ISIS_ADJ_UP
+ || adj->adj_usage == ISIS_ADJ_LEVEL1AND2) {
+ isis_adj_process_threeway(adj, tw_adj,
+ ISIS_ADJ_LEVEL1AND2);
+ } else if ((adj->adj_usage == ISIS_ADJ_LEVEL1)
+ || (adj->adj_usage
+ == ISIS_ADJ_LEVEL2)) {
+ /* (8) down - wrong system */
+ isis_adj_state_change(&adj,
+ ISIS_ADJ_DOWN,
+ "Wrong System");
+ }
+ break;
+ }
+ }
+
+ /* 8.2.5.2 a) 4) If the system is L2 - table 7 */
+ if (iih->circuit->area->is_type == IS_LEVEL_2) {
+ switch (iih->circ_type) {
+ case IS_LEVEL_1:
+ if (adj->adj_state != ISIS_ADJ_UP) {
+ /* (5) reject - wrong system type event
+ */
+ zlog_warn("wrongSystemType");
+ return ISIS_WARNING;
+ } else if ((adj->adj_usage
+ == ISIS_ADJ_LEVEL1AND2)
+ || (adj->adj_usage
+ == ISIS_ADJ_LEVEL2)) {
+ /* (6) down - wrong system */
+ isis_adj_state_change(&adj,
+ ISIS_ADJ_DOWN,
+ "Wrong System");
+ }
+ break;
+ case IS_LEVEL_1_AND_2:
+ case IS_LEVEL_2:
+ if (adj->adj_state != ISIS_ADJ_UP
+ || adj->adj_usage == ISIS_ADJ_LEVEL2) {
+ isis_adj_process_threeway(adj, tw_adj,
+ ISIS_ADJ_LEVEL2);
+ } else if (adj->adj_usage
+ == ISIS_ADJ_LEVEL1AND2) {
+ /* (6) down - wrong system */
+ isis_adj_state_change(&adj,
+ ISIS_ADJ_DOWN,
+ "Wrong System");
+ }
+ break;
+ }
+ }
+ }
+ /* 8.2.5.2 b) if no match was detected */
+ else if (listcount(iih->circuit->area->area_addrs) > 0) {
+ if (iih->circuit->area->is_type == IS_LEVEL_1) {
+ /* 8.2.5.2 b) 1) is_type L1 and adj is not up */
+ if (adj->adj_state != ISIS_ADJ_UP) {
+ isis_adj_state_change(&adj, ISIS_ADJ_DOWN,
+ "Area Mismatch");
+ /* 8.2.5.2 b) 2)is_type L1 and adj is up */
+ } else {
+ isis_adj_state_change(&adj, ISIS_ADJ_DOWN,
+ "Down - Area Mismatch");
+ }
+ }
+ /* 8.2.5.2 b 3 If the system is L2 or L1L2 - table 8 */
+ else {
+ switch (iih->circ_type) {
+ case IS_LEVEL_1:
+ if (adj->adj_state != ISIS_ADJ_UP) {
+ /* (6) reject - Area Mismatch event */
+ zlog_warn("AreaMismatch");
+ return ISIS_WARNING;
+ } else if (adj->adj_usage == ISIS_ADJ_LEVEL1) {
+ /* (7) down - area mismatch */
+ isis_adj_state_change(&adj,
+ ISIS_ADJ_DOWN,
+ "Area Mismatch");
+
+ } else if ((adj->adj_usage
+ == ISIS_ADJ_LEVEL1AND2)
+ || (adj->adj_usage
+ == ISIS_ADJ_LEVEL2)) {
+ /* (7) down - wrong system */
+ isis_adj_state_change(&adj,
+ ISIS_ADJ_DOWN,
+ "Wrong System");
+ }
+ break;
+ case IS_LEVEL_1_AND_2:
+ case IS_LEVEL_2:
+ if (adj->adj_state != ISIS_ADJ_UP
+ || adj->adj_usage == ISIS_ADJ_LEVEL2) {
+ isis_adj_process_threeway(adj, tw_adj,
+ ISIS_ADJ_LEVEL2);
+ } else if (adj->adj_usage == ISIS_ADJ_LEVEL1) {
+ /* (7) down - wrong system */
+ isis_adj_state_change(&adj,
+ ISIS_ADJ_DOWN,
+ "Wrong System");
+ } else if (adj->adj_usage
+ == ISIS_ADJ_LEVEL1AND2) {
+ if (iih->circ_type == IS_LEVEL_2) {
+ /* (7) down - wrong system */
+ isis_adj_state_change(
+ &adj, ISIS_ADJ_DOWN,
+ "Wrong System");
+ } else {
+ /* (7) down - area mismatch */
+ isis_adj_state_change(
+ &adj, ISIS_ADJ_DOWN,
+ "Area Mismatch");
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ /* down - area mismatch */
+ isis_adj_state_change(&adj, ISIS_ADJ_DOWN, "Area Mismatch");
+ }
+
+ if (adj) {
+ if (adj->adj_state == ISIS_ADJ_UP && changed) {
+ lsp_regenerate_schedule(
+ adj->circuit->area,
+ isis_adj_usage2levels(adj->adj_usage), 0);
+ }
+
+ /* 8.2.5.2 c) if the action was up - comparing circuit IDs */
+ /* FIXME - Missing parts */
+
+ /* some of my own understanding of the ISO, why the heck does
+ * it not say what should I change the system_type to...
+ */
+ switch (adj->adj_usage) {
+ case ISIS_ADJ_LEVEL1:
+ adj->sys_type = ISIS_SYSTYPE_L1_IS;
+ break;
+ case ISIS_ADJ_LEVEL2:
+ adj->sys_type = ISIS_SYSTYPE_L2_IS;
+ break;
+ case ISIS_ADJ_LEVEL1AND2:
+ adj->sys_type = ISIS_SYSTYPE_L2_IS;
+ break;
+ case ISIS_ADJ_NONE:
+ adj->sys_type = ISIS_SYSTYPE_UNKNOWN;
+ break;
+ }
+ }
+
+ if (IS_DEBUG_ADJ_PACKETS) {
+ zlog_debug(
+ "ISIS-Adj (%s): Rcvd P2P IIH from (%s), cir type %s, cir id %hhu, length %hu",
+ iih->circuit->area->area_tag,
+ iih->circuit->interface->name,
+ circuit_t2string(iih->circuit->is_type),
+ iih->circuit->circuit_id, iih->pdu_len);
+ }
+
+ return ISIS_OK;
+}
+
+static int process_lan_hello(struct iih_info *iih)
+{
+ struct isis_adjacency *adj;
+ adj = isis_adj_lookup(iih->sys_id,
+ iih->circuit->u.bc.adjdb[iih->level - 1]);
+ if ((adj == NULL) || (memcmp(adj->snpa, iih->ssnpa, ETH_ALEN))
+ || (adj->level != iih->level)) {
+ if (!adj) {
+ /* Do as in 8.4.2.5 */
+ adj = isis_new_adj(iih->sys_id, iih->ssnpa, iih->level,
+ iih->circuit);
+ } else {
+ if (iih->ssnpa) {
+ memcpy(adj->snpa, iih->ssnpa, 6);
+ } else {
+ memset(adj->snpa, ' ', 6);
+ }
+ adj->level = iih->level;
+ }
+ isis_adj_state_change(&adj, ISIS_ADJ_INITIALIZING, NULL);
+
+ if (iih->level == IS_LEVEL_1)
+ adj->sys_type = ISIS_SYSTYPE_L1_IS;
+ else
+ adj->sys_type = ISIS_SYSTYPE_L2_IS;
+ list_delete_all_node(
+ iih->circuit->u.bc.lan_neighs[iih->level - 1]);
+ isis_adj_build_neigh_list(
+ iih->circuit->u.bc.adjdb[iih->level - 1],
+ iih->circuit->u.bc.lan_neighs[iih->level - 1]);
+ }
+
+ if (adj->dis_record[iih->level - 1].dis == ISIS_IS_DIS) {
+ uint8_t *dis = (iih->level == 1)
+ ? iih->circuit->u.bc.l1_desig_is
+ : iih->circuit->u.bc.l2_desig_is;
+
+ if (memcmp(dis, iih->dis, ISIS_SYS_ID_LEN + 1)) {
+ event_add_event(master, isis_event_dis_status_change,
+ iih->circuit, 0, NULL);
+ memcpy(dis, iih->dis, ISIS_SYS_ID_LEN + 1);
+ }
+ }
+
+ adj->circuit_t = iih->circ_type;
+ adj->hold_time = iih->holdtime;
+ adj->last_upd = time(NULL);
+ adj->prio[iih->level - 1] = iih->priority;
+ memcpy(adj->lanid, iih->dis, ISIS_SYS_ID_LEN + 1);
+
+ bool changed;
+ isis_tlvs_to_adj(iih->tlvs, adj, &changed);
+ changed |= tlvs_to_adj_mt_set(iih->tlvs, iih->v4_usable, iih->v6_usable,
+ adj);
+
+ /* lets take care of the expiry */
+ EVENT_OFF(adj->t_expire);
+ event_add_timer(master, isis_adj_expire, adj, (long)adj->hold_time,
+ &adj->t_expire);
+
+ /*
+ * If the snpa for this circuit is found from LAN Neighbours TLV
+ * we have two-way communication -> adjacency can be put to state "up"
+ */
+ bool own_snpa_found =
+ isis_tlvs_own_snpa_found(iih->tlvs, iih->circuit->u.bc.snpa);
+
+ if (adj->adj_state != ISIS_ADJ_UP) {
+ if (own_snpa_found) {
+ isis_adj_state_change(
+ &adj, ISIS_ADJ_UP,
+ "own SNPA found in LAN Neighbours TLV");
+ }
+ } else {
+ if (!own_snpa_found) {
+ isis_adj_state_change(
+ &adj, ISIS_ADJ_INITIALIZING,
+ "own SNPA not found in LAN Neighbours TLV");
+ }
+ }
+
+ if (adj->adj_state == ISIS_ADJ_UP && changed)
+ lsp_regenerate_schedule(adj->circuit->area, iih->level, 0);
+
+ if (IS_DEBUG_ADJ_PACKETS) {
+ zlog_debug(
+ "ISIS-Adj (%s): Rcvd L%d LAN IIH from %pSY on %s, cirType %s, cirID %u, length %zd",
+ iih->circuit->area->area_tag, iih->level, iih->ssnpa,
+ iih->circuit->interface->name,
+ circuit_t2string(iih->circuit->is_type),
+ iih->circuit->circuit_id,
+ stream_get_endp(iih->circuit->rcv_stream));
+ }
+ return ISIS_OK;
+}
+
+static int pdu_len_validate(uint16_t pdu_len, struct isis_circuit *circuit)
+{
+ if (pdu_len < stream_get_getp(circuit->rcv_stream)
+ || pdu_len > ISO_MTU(circuit)
+ || pdu_len > stream_get_endp(circuit->rcv_stream))
+ return 1;
+
+ if (pdu_len < stream_get_endp(circuit->rcv_stream))
+ stream_set_endp(circuit->rcv_stream, pdu_len);
+ return 0;
+}
+
+static void update_rej_adj_count(struct isis_circuit *circuit)
+{
+ circuit->rej_adjacencies++;
+ if (circuit->is_type == IS_LEVEL_1)
+ circuit->area->rej_adjacencies[0]++;
+ else if (circuit->is_type == IS_LEVEL_2)
+ circuit->area->rej_adjacencies[1]++;
+ else {
+ circuit->area->rej_adjacencies[0]++;
+ circuit->area->rej_adjacencies[1]++;
+ }
+}
+
+static int process_hello(uint8_t pdu_type, struct isis_circuit *circuit,
+ uint8_t *ssnpa)
+{
+ /* keep a copy of the raw pdu for NB notifications */
+ size_t pdu_start = stream_get_getp(circuit->rcv_stream);
+ size_t pdu_end = stream_get_endp(circuit->rcv_stream);
+ char raw_pdu[pdu_end - pdu_start];
+ bool p2p_hello = (pdu_type == P2P_HELLO);
+ int level = p2p_hello ? 0
+ : (pdu_type == L1_LAN_HELLO) ? ISIS_LEVEL1
+ : ISIS_LEVEL2;
+ const char *pdu_name =
+ p2p_hello
+ ? "P2P IIH"
+ : (level == ISIS_LEVEL1) ? "L1 LAN IIH" : "L2 LAN IIH";
+
+ stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start,
+ pdu_end - pdu_start);
+ if (IS_DEBUG_ADJ_PACKETS) {
+ zlog_debug("ISIS-Adj (%s): Rcvd %s on %s, cirType %s, cirID %u",
+ circuit->area->area_tag, pdu_name,
+ circuit->interface->name,
+ circuit_t2string(circuit->is_type),
+ circuit->circuit_id);
+ if (IS_DEBUG_PACKET_DUMP)
+ zlog_dump_data(STREAM_DATA(circuit->rcv_stream),
+ stream_get_endp(circuit->rcv_stream));
+ }
+
+ if (p2p_hello) {
+ if (circuit->circ_type != CIRCUIT_T_P2P) {
+ zlog_warn("p2p hello on non p2p circuit");
+ update_rej_adj_count(circuit);
+#ifndef FABRICD
+ isis_notif_reject_adjacency(
+ circuit, "p2p hello on non p2p circuit",
+ raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ return ISIS_WARNING;
+ }
+ } else {
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST) {
+ zlog_warn("lan hello on non broadcast circuit");
+ update_rej_adj_count(circuit);
+#ifndef FABRICD
+ isis_notif_reject_adjacency(
+ circuit, "lan hello on non broadcast circuit",
+ raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ return ISIS_WARNING;
+ }
+
+ if (circuit->ext_domain) {
+ zlog_debug(
+ "level %d LAN Hello received over circuit with externalDomain = true",
+ level);
+ update_rej_adj_count(circuit);
+#ifndef FABRICD
+ isis_notif_reject_adjacency(
+ circuit,
+ "LAN Hello received over circuit with externalDomain = true",
+ raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ return ISIS_WARNING;
+ }
+
+ if (!(circuit->is_type & level)) {
+ if (IS_DEBUG_ADJ_PACKETS) {
+ zlog_debug(
+ "ISIS-Adj (%s): Interface level mismatch, %s",
+ circuit->area->area_tag,
+ circuit->interface->name);
+ }
+ update_rej_adj_count(circuit);
+#ifndef FABRICD
+ isis_notif_reject_adjacency(circuit,
+ "Interface level mismatch",
+ raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ return ISIS_WARNING;
+ }
+ }
+
+ struct iih_info iih = {
+ .circuit = circuit, .ssnpa = ssnpa, .level = level};
+
+ /* Generic IIH Header */
+ iih.circ_type = stream_getc(circuit->rcv_stream) & 0x03;
+ stream_get(iih.sys_id, circuit->rcv_stream, ISIS_SYS_ID_LEN);
+ iih.holdtime = stream_getw(circuit->rcv_stream);
+ iih.pdu_len = stream_getw(circuit->rcv_stream);
+
+ if (p2p_hello) {
+ iih.circuit_id = stream_getc(circuit->rcv_stream);
+ } else {
+ iih.priority = stream_getc(circuit->rcv_stream);
+ stream_get(iih.dis, circuit->rcv_stream, ISIS_SYS_ID_LEN + 1);
+ }
+
+ if (pdu_len_validate(iih.pdu_len, circuit)) {
+ zlog_warn(
+ "ISIS-Adj (%s): Rcvd %s from (%s) with invalid pdu length %hu",
+ circuit->area->area_tag, pdu_name,
+ circuit->interface->name, iih.pdu_len);
+ update_rej_adj_count(circuit);
+#ifndef FABRICD
+ isis_notif_reject_adjacency(circuit, "Invalid PDU length",
+ raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ return ISIS_WARNING;
+ }
+
+ if (!p2p_hello && !(level & iih.circ_type)) {
+ flog_err(EC_ISIS_PACKET,
+ "Level %d LAN Hello with Circuit Type %d", level,
+ iih.circ_type);
+ update_rej_adj_count(circuit);
+#ifndef FABRICD
+ isis_notif_reject_adjacency(circuit,
+ "LAN Hello with wrong IS-level",
+ raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ return ISIS_ERROR;
+ }
+
+ const char *error_log;
+ int retval = ISIS_WARNING;
+
+ if (isis_unpack_tlvs(STREAM_READABLE(circuit->rcv_stream),
+ circuit->rcv_stream, &iih.tlvs, &error_log)) {
+ zlog_warn("isis_unpack_tlvs() failed: %s", error_log);
+ update_rej_adj_count(circuit);
+#ifndef FABRICD
+ isis_notif_reject_adjacency(circuit, "Failed to unpack TLVs",
+ raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ goto out;
+ }
+
+ if (!iih.tlvs->area_addresses.count) {
+ zlog_warn("No Area addresses TLV in %s", pdu_name);
+#ifndef FABRICD
+ /* send northbound notification */
+ isis_notif_area_mismatch(circuit, raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ goto out;
+ }
+
+ if (!iih.tlvs->protocols_supported.count) {
+ zlog_warn("No supported protocols TLV in %s", pdu_name);
+ update_rej_adj_count(circuit);
+#ifndef FABRICD
+ isis_notif_reject_adjacency(circuit,
+ "No supported protocols TLV",
+ raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ goto out;
+ }
+
+ int auth_code = isis_tlvs_auth_is_valid(iih.tlvs, &circuit->passwd,
+ circuit->rcv_stream, false);
+ if (auth_code != ISIS_AUTH_OK) {
+ isis_event_auth_failure(circuit->area->area_tag,
+ "IIH authentication failure",
+ iih.sys_id);
+#ifndef FABRICD
+ /* send northbound notification */
+ stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start,
+ pdu_end - pdu_start);
+ if (auth_code == ISIS_AUTH_FAILURE) {
+ update_rej_adj_count(circuit);
+ isis_notif_authentication_failure(circuit, raw_pdu,
+ sizeof(raw_pdu));
+ } else { /* AUTH_TYPE_FAILURE or NO_VALIDATOR */
+ update_rej_adj_count(circuit);
+ isis_notif_authentication_type_failure(circuit, raw_pdu,
+ sizeof(raw_pdu));
+ }
+#endif /* ifndef FABRICD */
+ goto out;
+ }
+
+ if (!memcmp(iih.sys_id, circuit->isis->sysid, ISIS_SYS_ID_LEN)) {
+ zlog_warn(
+ "ISIS-Adj (%s): Received IIH with own sysid on %s - discard",
+ circuit->area->area_tag, circuit->interface->name);
+ update_rej_adj_count(circuit);
+#ifndef FABRICD
+ isis_notif_reject_adjacency(circuit,
+ "Received IIH with our own sysid",
+ raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ goto out;
+ }
+
+ if (!p2p_hello
+ && (listcount(circuit->area->area_addrs) == 0
+ || (level == ISIS_LEVEL1
+ && !isis_tlvs_area_addresses_match(
+ iih.tlvs, circuit->area->area_addrs)))) {
+ if (IS_DEBUG_ADJ_PACKETS) {
+ zlog_debug(
+ "ISIS-Adj (%s): Area mismatch, level %d IIH on %s",
+ circuit->area->area_tag, level,
+ circuit->interface->name);
+ }
+#ifndef FABRICD
+ /* send northbound notification */
+ isis_notif_area_mismatch(circuit, raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ goto out;
+ }
+
+ iih.v4_usable = (fabricd_ip_addrs(circuit)
+ && iih.tlvs->ipv4_address.count);
+
+ iih.v6_usable =
+ (listcount(circuit->ipv6_link) && iih.tlvs->ipv6_address.count);
+
+ if (!iih.v4_usable && !iih.v6_usable) {
+ if (IS_DEBUG_ADJ_PACKETS) {
+ zlog_warn(
+ "ISIS-Adj (%s): Neither IPv4 nor IPv6 considered usable. Ignoring IIH",
+ circuit->area->area_tag);
+ }
+ update_rej_adj_count(circuit);
+#ifndef FABRICD
+ isis_notif_reject_adjacency(
+ circuit, "Neither IPv4 not IPv6 considered usable",
+ raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ goto out;
+ }
+
+ retval = p2p_hello ? process_p2p_hello(&iih) : process_lan_hello(&iih);
+out:
+ isis_free_tlvs(iih.tlvs);
+
+ return retval;
+}
+
+static void lsp_flood_or_update(struct isis_lsp *lsp,
+ struct isis_circuit *circuit,
+ bool circuit_scoped)
+{
+ if (!circuit_scoped)
+ lsp_flood(lsp, circuit);
+ else
+ fabricd_update_lsp_no_flood(lsp, circuit);
+}
+
+/*
+ * Process Level 1/2 Link State
+ * ISO - 10589
+ * Section 7.3.15.1 - Action on receipt of a link state PDU
+ */
+static int process_lsp(uint8_t pdu_type, struct isis_circuit *circuit,
+ const uint8_t *ssnpa, uint8_t max_area_addrs)
+{
+ int level;
+ bool circuit_scoped;
+ size_t pdu_start = stream_get_getp(circuit->rcv_stream);
+ size_t pdu_end = stream_get_endp(circuit->rcv_stream);
+ char raw_pdu[pdu_end - pdu_start];
+
+ stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start,
+ pdu_end - pdu_start);
+
+ if (pdu_type == FS_LINK_STATE) {
+ if (!fabricd)
+ return ISIS_ERROR;
+ if (max_area_addrs != L2_CIRCUIT_FLOODING_SCOPE)
+ return ISIS_ERROR;
+ level = ISIS_LEVEL2;
+ circuit_scoped = true;
+
+ /* The stream is used verbatim for sending out new LSPDUs.
+ * So make sure we store it as an L2 LSPDU internally.
+ * (compare for the reverse in `send_lsp`) */
+ stream_putc_at(circuit->rcv_stream, 4, L2_LINK_STATE);
+ stream_putc_at(circuit->rcv_stream, 7, 0);
+ } else {
+ if (pdu_type == L1_LINK_STATE)
+ level = ISIS_LEVEL1;
+ else
+ level = ISIS_LEVEL2;
+ circuit_scoped = false;
+ }
+
+ if (IS_DEBUG_UPDATE_PACKETS) {
+ zlog_debug(
+ "ISIS-Upd (%s): Rcvd %sL%d LSP on %s, cirType %s, cirID %u",
+ circuit->area->area_tag,
+ circuit_scoped ? "Circuit scoped " : "", level,
+ circuit->interface->name,
+ circuit_t2string(circuit->is_type),
+ circuit->circuit_id);
+ if (IS_DEBUG_PACKET_DUMP)
+ zlog_dump_data(STREAM_DATA(circuit->rcv_stream),
+ stream_get_endp(circuit->rcv_stream));
+ }
+
+ struct isis_lsp_hdr hdr = {};
+
+ hdr.pdu_len = stream_getw(circuit->rcv_stream);
+ hdr.rem_lifetime = stream_getw(circuit->rcv_stream);
+ stream_get(hdr.lsp_id, circuit->rcv_stream, sizeof(hdr.lsp_id));
+ hdr.seqno = stream_getl(circuit->rcv_stream);
+ hdr.checksum = stream_getw(circuit->rcv_stream);
+ hdr.lsp_bits = stream_getc(circuit->rcv_stream);
+
+#ifndef FABRICD
+ /* send northbound notification */
+ char buf[ISO_SYSID_STRLEN];
+
+ snprintfrr(buf, ISO_SYSID_STRLEN, "%pSY", hdr.lsp_id);
+ isis_notif_lsp_received(circuit, hdr.lsp_id, hdr.seqno, time(NULL),
+ buf);
+#endif /* ifndef FABRICD */
+
+ if (pdu_len_validate(hdr.pdu_len, circuit)) {
+ zlog_debug("ISIS-Upd (%s): LSP %pLS invalid LSP length %hu",
+ circuit->area->area_tag, hdr.lsp_id, hdr.pdu_len);
+ return ISIS_WARNING;
+ }
+
+ if (IS_DEBUG_UPDATE_PACKETS) {
+ zlog_debug(
+ "ISIS-Upd (%s): Rcvd L%d LSP %pLS, seq 0x%08x, cksum 0x%04hx, lifetime %hus, len %hu, on %s",
+ circuit->area->area_tag, level, hdr.lsp_id, hdr.seqno,
+ hdr.checksum, hdr.rem_lifetime, hdr.pdu_len,
+ circuit->interface->name);
+ }
+
+ /* lsp is_type check */
+ if ((hdr.lsp_bits & IS_LEVEL_1) != IS_LEVEL_1) {
+ zlog_debug("ISIS-Upd (%s): LSP %pLS invalid LSP is type 0x%x",
+ circuit->area->area_tag, hdr.lsp_id,
+ hdr.lsp_bits & IS_LEVEL_1_AND_2);
+ /* continue as per RFC1122 Be liberal in what you accept, and
+ * conservative in what you send */
+ }
+
+ /* Checksum sanity check - FIXME: move to correct place */
+ /* 12 = sysid+pdu+remtime */
+ if (iso_csum_verify(STREAM_DATA(circuit->rcv_stream) + 12,
+ hdr.pdu_len - 12, hdr.checksum, 12)) {
+ zlog_debug(
+ "ISIS-Upd (%s): LSP %pLS invalid LSP checksum 0x%04hx",
+ circuit->area->area_tag, hdr.lsp_id, hdr.checksum);
+ return ISIS_WARNING;
+ }
+
+ /* 7.3.15.1 a) 1 - external domain circuit will discard lsps */
+ if (circuit->ext_domain) {
+ zlog_debug(
+ "ISIS-Upd (%s): LSP %pLS received at level %d over circuit with externalDomain = true",
+ circuit->area->area_tag, hdr.lsp_id, level);
+ return ISIS_WARNING;
+ }
+
+ /* 7.3.15.1 a) 2,3 - manualL2OnlyMode not implemented */
+ if (!(circuit->is_type & level)) {
+ zlog_debug(
+ "ISIS-Upd (%s): LSP %pLS received at level %d over circuit of type %s",
+ circuit->area->area_tag, hdr.lsp_id, level,
+ circuit_t2string(circuit->is_type));
+ return ISIS_WARNING;
+ }
+
+ struct isis_tlvs *tlvs = NULL;
+ int retval = ISIS_WARNING;
+ const char *error_log;
+
+ if (isis_unpack_tlvs(STREAM_READABLE(circuit->rcv_stream),
+ circuit->rcv_stream, &tlvs, &error_log)) {
+ zlog_warn("Something went wrong unpacking the LSP: %s",
+ error_log);
+#ifndef FABRICD
+ /* send northbound notification. Note that the tlv-type and
+ * offset cannot correctly be set here as they are not returned
+ * by isis_unpack_tlvs, but in there I cannot fire a
+ * notification because I have no circuit information. So until
+ * we change the code above to return those extra fields, we
+ * will send dummy values which are ignored in the callback
+ */
+ circuit->lsp_error_counter++;
+ if (circuit->is_type == IS_LEVEL_1) {
+ circuit->area->lsp_error_counter[0]++;
+ } else if (circuit->is_type == IS_LEVEL_2) {
+ circuit->area->lsp_error_counter[1]++;
+ } else {
+ circuit->area->lsp_error_counter[0]++;
+ circuit->area->lsp_error_counter[1]++;
+ }
+
+ isis_notif_lsp_error(circuit, hdr.lsp_id, raw_pdu,
+ sizeof(raw_pdu), 0, 0);
+#endif /* ifndef FABRICD */
+ goto out;
+ }
+
+ /* 7.3.15.1 a) 4 - need to make sure IDLength matches */
+
+ /* 7.3.15.1 a) 5 - maximum area match, can be ommited since we only use
+ * 3 */
+
+ /* 7.3.15.1 a) 7 - password check */
+ struct isis_passwd *passwd = (level == ISIS_LEVEL1)
+ ? &circuit->area->area_passwd
+ : &circuit->area->domain_passwd;
+ int auth_code = isis_tlvs_auth_is_valid(tlvs, passwd,
+ circuit->rcv_stream, true);
+ if (auth_code != ISIS_AUTH_OK) {
+ isis_event_auth_failure(circuit->area->area_tag,
+ "LSP authentication failure",
+ hdr.lsp_id);
+#ifndef FABRICD
+ /* send northbound notification */
+ if (auth_code == ISIS_AUTH_FAILURE) {
+ circuit->auth_failures++;
+ if (circuit->is_type == IS_LEVEL_1) {
+ circuit->area->auth_failures[0]++;
+ } else if (circuit->is_type == IS_LEVEL_2) {
+ circuit->area->auth_failures[1]++;
+ } else {
+ circuit->area->auth_failures[0]++;
+ circuit->area->auth_failures[1]++;
+ }
+ isis_notif_authentication_failure(circuit, raw_pdu,
+ sizeof(raw_pdu));
+ } else { /* AUTH_TYPE_FAILURE or NO_VALIDATOR */
+ circuit->auth_type_failures++;
+ if (circuit->is_type == IS_LEVEL_1) {
+ circuit->area->auth_type_failures[0]++;
+ } else if (circuit->is_type == IS_LEVEL_2) {
+ circuit->area->auth_type_failures[1]++;
+ } else {
+ circuit->area->auth_type_failures[0]++;
+ circuit->area->auth_type_failures[1]++;
+ }
+ isis_notif_authentication_type_failure(circuit, raw_pdu,
+ sizeof(raw_pdu));
+ }
+#endif /* ifndef FABRICD */
+ goto out;
+ }
+
+ /* Find the LSP in our database and compare it to this Link State header
+ */
+ struct isis_lsp *lsp =
+ lsp_search(&circuit->area->lspdb[level - 1], hdr.lsp_id);
+ int comp = 0;
+ if (lsp)
+ comp = lsp_compare(circuit->area->area_tag, lsp, hdr.seqno,
+ hdr.checksum, hdr.rem_lifetime);
+ if (lsp && (lsp->own_lsp))
+ goto dontcheckadj;
+
+ /* 7.3.15.1 a) 6 - Must check that we have an adjacency of the same
+ * level */
+ /* for broadcast circuits, snpa should be compared */
+
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ if (!isis_adj_lookup_snpa(ssnpa,
+ circuit->u.bc.adjdb[level - 1])) {
+ zlog_debug(
+ "(%s): DS ======= LSP %pLS, seq 0x%08x, cksum 0x%04hx, lifetime %hus on %s",
+ circuit->area->area_tag, hdr.lsp_id, hdr.seqno,
+ hdr.checksum, hdr.rem_lifetime,
+ circuit->interface->name);
+ goto out; /* Silently discard */
+ }
+ }
+ /* for non broadcast, we just need to find same level adj */
+ else {
+ /* If no adj, or no sharing of level */
+ if (!circuit->u.p2p.neighbor) {
+ retval = ISIS_OK;
+ goto out;
+ } else {
+ if (((level == IS_LEVEL_1)
+ && (circuit->u.p2p.neighbor->adj_usage
+ == ISIS_ADJ_LEVEL2))
+ || ((level == IS_LEVEL_2)
+ && (circuit->u.p2p.neighbor->adj_usage
+ == ISIS_ADJ_LEVEL1)))
+ goto out;
+ }
+ }
+
+ bool lsp_confusion;
+
+dontcheckadj:
+ /* 7.3.15.1 a) 7 - Passwords for level 1 - not implemented */
+
+ /* 7.3.15.1 a) 8 - Passwords for level 2 - not implemented */
+
+ /* 7.3.15.1 a) 9 - OriginatingLSPBufferSize - not implemented FIXME: do
+ * it */
+
+ /* 7.3.16.2 - If this is an LSP from another IS with identical seq_num
+ * but
+ * wrong checksum, initiate a purge. */
+ if (lsp && (lsp->hdr.seqno == hdr.seqno)
+ && (lsp->hdr.checksum != hdr.checksum)
+ && hdr.rem_lifetime) {
+ zlog_warn(
+ "ISIS-Upd (%s): LSP %pLS seq 0x%08x with confused checksum received.",
+ circuit->area->area_tag, hdr.lsp_id, hdr.seqno);
+ hdr.rem_lifetime = 0;
+ lsp_confusion = true;
+ } else
+ lsp_confusion = false;
+
+ /* 7.3.15.1 b) - If the remaining life time is 0, we perform 7.3.16.4 */
+ if (hdr.rem_lifetime == 0) {
+ if (!lsp) {
+ /* 7.3.16.4 a) 1) No LSP in db -> send an ack, but don't
+ * save */
+ /* only needed on explicit update, eg - p2p */
+ if (circuit->circ_type == CIRCUIT_T_P2P)
+ ack_lsp(&hdr, circuit, level);
+ goto out; /* FIXME: do we need a purge? */
+ } else {
+ if (memcmp(hdr.lsp_id, circuit->isis->sysid,
+ ISIS_SYS_ID_LEN)) {
+ /* LSP by some other system -> do 7.3.16.4 b) */
+ /* 7.3.16.4 b) 1) */
+ if (comp == LSP_NEWER) {
+ lsp_update(lsp, &hdr, tlvs,
+ circuit->rcv_stream,
+ circuit->area, level,
+ lsp_confusion);
+ if (lsp_confusion)
+ isis_free_tlvs(tlvs);
+ tlvs = NULL;
+ /* ii */
+ lsp_flood_or_update(lsp, NULL,
+ circuit_scoped);
+ /* v */
+ ISIS_FLAGS_CLEAR_ALL(
+ lsp->SSNflags); /* FIXME:
+ OTHER
+ than c
+ */
+
+ /* For the case of lsp confusion, flood
+ * the purge back to its
+ * originator so that it can react.
+ * Otherwise, don't reflood
+ * through incoming circuit as usual */
+ if (!lsp_confusion) {
+ isis_tx_queue_del(
+ circuit->tx_queue,
+ lsp);
+
+ /* iv */
+ if (circuit->circ_type
+ != CIRCUIT_T_BROADCAST)
+ ISIS_SET_FLAG(
+ lsp->SSNflags,
+ circuit);
+ }
+ } /* 7.3.16.4 b) 2) */
+ else if (comp == LSP_EQUAL) {
+ /* i */
+ isis_tx_queue_del(circuit->tx_queue,
+ lsp);
+ /* ii */
+ if (circuit->circ_type
+ != CIRCUIT_T_BROADCAST)
+ ISIS_SET_FLAG(lsp->SSNflags,
+ circuit);
+ } /* 7.3.16.4 b) 3) */
+ else {
+ isis_tx_queue_add(circuit->tx_queue,
+ lsp, TX_LSP_NORMAL);
+ ISIS_CLEAR_FLAG(lsp->SSNflags, circuit);
+ }
+ } else if (lsp->hdr.rem_lifetime != 0) {
+ /* our own LSP -> 7.3.16.4 c) */
+ if (comp == LSP_NEWER) {
+#ifndef FABRICD
+ if (lsp->hdr.seqno < hdr.seqno) {
+ /* send northbound
+ * notification */
+ circuit->area
+ ->lsp_seqno_skipped_counter++;
+ isis_notif_seqno_skipped(
+ circuit, hdr.lsp_id);
+ }
+#endif /* ifndef FABRICD */
+ lsp_inc_seqno(lsp, hdr.seqno);
+ lsp_flood_or_update(lsp, NULL,
+ circuit_scoped);
+ } else {
+ isis_tx_queue_add(circuit->tx_queue,
+ lsp, TX_LSP_NORMAL);
+ ISIS_CLEAR_FLAG(lsp->SSNflags, circuit);
+ }
+ if (IS_DEBUG_UPDATE_PACKETS)
+ zlog_debug(
+ "ISIS-Upd (%s): (1) re-originating LSP %pLS new seq 0x%08x",
+ circuit->area->area_tag,
+ hdr.lsp_id, lsp->hdr.seqno);
+ } else {
+ /* our own LSP with 0 remaining life time */
+#ifndef FABRICD
+ /* send northbound notification */
+ isis_notif_own_lsp_purge(circuit, hdr.lsp_id);
+#endif /* ifndef FABRICD */
+ }
+ }
+ goto out;
+ }
+ /* 7.3.15.1 c) - If this is our own lsp and we don't have it initiate a
+ * purge */
+ if (memcmp(hdr.lsp_id, circuit->isis->sysid, ISIS_SYS_ID_LEN) == 0) {
+ if (!lsp) {
+ /* 7.3.16.4: initiate a purge */
+ lsp_purge_non_exist(level, &hdr, circuit->area);
+ retval = ISIS_OK;
+ goto out;
+ }
+ /* 7.3.15.1 d) - If this is our own lsp and we have it */
+
+ /* In 7.3.16.1, If an Intermediate system R somewhere in the
+ * domain
+ * has information that the current sequence number for source S
+ * is
+ * "greater" than that held by S, ... */
+
+ if (comp == LSP_NEWER) {
+ /* 7.3.16.1 */
+ lsp_inc_seqno(lsp, hdr.seqno);
+#ifndef FABRICD
+ /* send northbound notification */
+ circuit->area->lsp_seqno_skipped_counter++;
+ isis_notif_seqno_skipped(circuit, hdr.lsp_id);
+#endif /* ifndef FABRICD */
+ if (IS_DEBUG_UPDATE_PACKETS) {
+ zlog_debug(
+ "ISIS-Upd (%s): (2) re-originating LSP %pLS new seq 0x%08x",
+ circuit->area->area_tag, hdr.lsp_id,
+ lsp->hdr.seqno);
+ }
+ lsp_flood(lsp, NULL);
+ } else if (comp == LSP_EQUAL) {
+ isis_tx_queue_del(circuit->tx_queue, lsp);
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST)
+ ISIS_SET_FLAG(lsp->SSNflags, circuit);
+ } else {
+ isis_tx_queue_add(circuit->tx_queue, lsp,
+ TX_LSP_NORMAL);
+ ISIS_CLEAR_FLAG(lsp->SSNflags, circuit);
+ }
+ } else {
+ /* 7.3.15.1 e) - This lsp originated on another system */
+
+ /* 7.3.15.1 e) 1) LSP newer than the one in db or no LSP in db
+ */
+ if ((!lsp || comp == LSP_NEWER)) {
+ /*
+ * If this lsp is a frag, need to see if we have zero
+ * lsp present
+ */
+ struct isis_lsp *lsp0 = NULL;
+ if (LSP_FRAGMENT(hdr.lsp_id) != 0) {
+ uint8_t lspid[ISIS_SYS_ID_LEN + 2];
+ memcpy(lspid, hdr.lsp_id, ISIS_SYS_ID_LEN + 1);
+ LSP_FRAGMENT(lspid) = 0;
+ lsp0 = lsp_search(
+ &circuit->area->lspdb[level - 1], lspid);
+ if (!lsp0) {
+ zlog_debug(
+ "Got lsp frag, while zero lsp not in database");
+ goto out;
+ }
+ }
+ /* i */
+ if (!lsp) {
+ lsp = lsp_new_from_recv(
+ &hdr, tlvs, circuit->rcv_stream, lsp0,
+ circuit->area, level);
+ tlvs = NULL;
+ lsp_insert(&circuit->area->lspdb[level - 1],
+ lsp);
+ } else /* exists, so we overwrite */
+ {
+ lsp_update(lsp, &hdr, tlvs, circuit->rcv_stream,
+ circuit->area, level, false);
+ tlvs = NULL;
+ }
+ lsp_flood_or_update(lsp, circuit, circuit_scoped);
+
+ /* iv */
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST)
+ ISIS_SET_FLAG(lsp->SSNflags, circuit);
+ /* FIXME: v) */
+ }
+ /* 7.3.15.1 e) 2) LSP equal to the one in db */
+ else if (comp == LSP_EQUAL) {
+ isis_tx_queue_del(circuit->tx_queue, lsp);
+ lsp_update(lsp, &hdr, tlvs, circuit->rcv_stream,
+ circuit->area, level, false);
+ tlvs = NULL;
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST)
+ ISIS_SET_FLAG(lsp->SSNflags, circuit);
+ }
+ /* 7.3.15.1 e) 3) LSP older than the one in db */
+ else {
+ isis_tx_queue_add(circuit->tx_queue, lsp,
+ TX_LSP_NORMAL);
+ ISIS_CLEAR_FLAG(lsp->SSNflags, circuit);
+ }
+ }
+
+ retval = ISIS_OK;
+
+out:
+ fabricd_trigger_csnp(circuit->area, circuit_scoped);
+
+ isis_free_tlvs(tlvs);
+ return retval;
+}
+
+/*
+ * Process Sequence Numbers
+ * ISO - 10589
+ * Section 7.3.15.2 - Action on receipt of a sequence numbers PDU
+ */
+
+static int process_snp(uint8_t pdu_type, struct isis_circuit *circuit,
+ const uint8_t *ssnpa)
+{
+#ifndef FABRICD
+ size_t pdu_start = stream_get_getp(circuit->rcv_stream);
+ size_t pdu_end = stream_get_endp(circuit->rcv_stream);
+ char raw_pdu[pdu_end - pdu_start];
+#endif /* ifndef FABRICD */
+
+ bool is_csnp = (pdu_type == L1_COMPLETE_SEQ_NUM
+ || pdu_type == L2_COMPLETE_SEQ_NUM);
+ char typechar = is_csnp ? 'C' : 'P';
+ int level = (pdu_type == L1_COMPLETE_SEQ_NUM
+ || pdu_type == L1_PARTIAL_SEQ_NUM)
+ ? ISIS_LEVEL1
+ : ISIS_LEVEL2;
+
+ uint16_t pdu_len = stream_getw(circuit->rcv_stream);
+ uint8_t rem_sys_id[ISIS_SYS_ID_LEN];
+
+ stream_get(rem_sys_id, circuit->rcv_stream, ISIS_SYS_ID_LEN);
+ stream_forward_getp(circuit->rcv_stream, 1); /* Circuit ID - unused */
+
+ uint8_t start_lsp_id[ISIS_SYS_ID_LEN + 2] = {};
+ uint8_t stop_lsp_id[ISIS_SYS_ID_LEN + 2] = {};
+
+ if (is_csnp) {
+ stream_get(start_lsp_id, circuit->rcv_stream,
+ ISIS_SYS_ID_LEN + 2);
+ stream_get(stop_lsp_id, circuit->rcv_stream,
+ ISIS_SYS_ID_LEN + 2);
+ }
+
+ if (pdu_len_validate(pdu_len, circuit)) {
+ zlog_warn("Received a CSNP with bogus length %d", pdu_len);
+ return ISIS_WARNING;
+ }
+
+ if (IS_DEBUG_SNP_PACKETS) {
+ zlog_debug(
+ "ISIS-Snp (%s): Rcvd L%d %cSNP on %s, cirType %s, cirID %u",
+ circuit->area->area_tag, level, typechar,
+ circuit->interface->name,
+ circuit_t2string(circuit->is_type),
+ circuit->circuit_id);
+ if (IS_DEBUG_PACKET_DUMP)
+ zlog_dump_data(STREAM_DATA(circuit->rcv_stream),
+ stream_get_endp(circuit->rcv_stream));
+ }
+
+ /* 7.3.15.2 a) 1 - external domain circuit will discard snp pdu */
+ if (circuit->ext_domain) {
+
+ zlog_debug(
+ "ISIS-Snp (%s): Rcvd L%d %cSNP on %s, skipping: circuit externalDomain = true",
+ circuit->area->area_tag, level, typechar,
+ circuit->interface->name);
+
+ return ISIS_OK;
+ }
+
+ /* 7.3.15.2 a) 2,3 - manualL2OnlyMode not implemented */
+ if (!(circuit->is_type & level)) {
+ zlog_debug(
+ "ISIS-Snp (%s): Rcvd L%d %cSNP on %s, skipping: circuit type %s does not match level %d",
+ circuit->area->area_tag, level, typechar,
+ circuit->interface->name,
+ circuit_t2string(circuit->is_type), level);
+
+ return ISIS_OK;
+ }
+
+ /* 7.3.15.2 a) 4 - not applicable for CSNP only PSNPs on broadcast */
+ if (!is_csnp && (circuit->circ_type == CIRCUIT_T_BROADCAST)
+ && !circuit->u.bc.is_dr[level - 1]) {
+ zlog_debug(
+ "ISIS-Snp (%s): Rcvd L%d %cSNP from %pSY on %s, skipping: we are not the DIS",
+ circuit->area->area_tag, level, typechar, ssnpa,
+ circuit->interface->name);
+
+ return ISIS_OK;
+ }
+
+ /* 7.3.15.2 a) 5 - need to make sure IDLength matches - already checked
+ */
+
+ /* 7.3.15.2 a) 6 - maximum area match, can be ommited since we only use
+ * 3
+ * - already checked */
+
+ /* 7.3.15.2 a) 7 - Must check that we have an adjacency of the same
+ * level */
+ /* for broadcast circuits, snpa should be compared */
+ /* FIXME : Do we need to check SNPA? */
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ if (!isis_adj_lookup(rem_sys_id,
+ circuit->u.bc.adjdb[level - 1]))
+ return ISIS_OK; /* Silently discard */
+ } else {
+ if (!fabricd && !circuit->u.p2p.neighbor) {
+ zlog_warn("no p2p neighbor on circuit %s",
+ circuit->interface->name);
+ return ISIS_OK; /* Silently discard */
+ }
+ }
+
+ struct isis_tlvs *tlvs;
+ int retval = ISIS_WARNING;
+ const char *error_log;
+
+ if (isis_unpack_tlvs(STREAM_READABLE(circuit->rcv_stream),
+ circuit->rcv_stream, &tlvs, &error_log)) {
+ zlog_warn("Something went wrong unpacking the SNP: %s",
+ error_log);
+ goto out;
+ }
+
+ struct isis_passwd *passwd = (level == IS_LEVEL_1)
+ ? &circuit->area->area_passwd
+ : &circuit->area->domain_passwd;
+
+ if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_RECV)) {
+ int auth_code = isis_tlvs_auth_is_valid(
+ tlvs, passwd, circuit->rcv_stream, false);
+ if (auth_code != ISIS_AUTH_OK) {
+ isis_event_auth_failure(circuit->area->area_tag,
+ "SNP authentication failure",
+ rem_sys_id);
+#ifndef FABRICD
+ /* send northbound notification */
+ stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start,
+ pdu_end - pdu_start);
+ if (auth_code == ISIS_AUTH_FAILURE) {
+ circuit->auth_failures++;
+ if (circuit->is_type == IS_LEVEL_1) {
+ circuit->area->auth_failures[0]++;
+ } else if (circuit->is_type == IS_LEVEL_2) {
+ circuit->area->auth_failures[1]++;
+ } else {
+ circuit->area->auth_failures[0]++;
+ circuit->area->auth_failures[1]++;
+ }
+ isis_notif_authentication_failure(
+ circuit, raw_pdu, sizeof(raw_pdu));
+ } else { /* AUTH_TYPE_FAILURE or NO_VALIDATOR */
+ circuit->auth_type_failures++;
+ if (circuit->is_type == IS_LEVEL_1) {
+ circuit->area->auth_type_failures[0]++;
+ } else if (circuit->is_type == IS_LEVEL_2) {
+ circuit->area->auth_type_failures[1]++;
+ } else {
+ circuit->area->auth_type_failures[0]++;
+ circuit->area->auth_type_failures[1]++;
+ }
+ isis_notif_authentication_type_failure(
+ circuit, raw_pdu, sizeof(raw_pdu));
+ }
+#endif /* ifndef FABRICD */
+ goto out;
+ }
+ }
+
+ struct isis_lsp_entry *entry_head =
+ (struct isis_lsp_entry *)tlvs->lsp_entries.head;
+
+ /* debug isis snp-packets */
+ if (IS_DEBUG_SNP_PACKETS) {
+ zlog_debug("ISIS-Snp (%s): Rcvd L%d %cSNP from %pSY on %s",
+ circuit->area->area_tag, level, typechar, ssnpa,
+ circuit->interface->name);
+ for (struct isis_lsp_entry *entry = entry_head; entry;
+ entry = entry->next) {
+ zlog_debug(
+ "ISIS-Snp (%s): %cSNP entry %pLS, seq 0x%08x, cksum 0x%04hx, lifetime %hus",
+ circuit->area->area_tag, typechar, entry->id,
+ entry->seqno, entry->checksum,
+ entry->rem_lifetime);
+ }
+ }
+
+ bool resync_needed = false;
+
+ /* 7.3.15.2 b) Actions on LSP_ENTRIES reported */
+ for (struct isis_lsp_entry *entry = entry_head; entry;
+ entry = entry->next) {
+ struct isis_lsp *lsp =
+ lsp_search(&circuit->area->lspdb[level - 1], entry->id);
+ bool own_lsp = !memcmp(entry->id, circuit->isis->sysid,
+ ISIS_SYS_ID_LEN);
+ if (lsp) {
+ /* 7.3.15.2 b) 1) is this LSP newer */
+ int cmp = lsp_compare(circuit->area->area_tag, lsp,
+ entry->seqno, entry->checksum,
+ entry->rem_lifetime);
+ /* 7.3.15.2 b) 2) if it equals, clear SRM on p2p */
+ if (cmp == LSP_EQUAL) {
+ /* if (circuit->circ_type !=
+ * CIRCUIT_T_BROADCAST) */
+ isis_tx_queue_del(circuit->tx_queue, lsp);
+ }
+ /* 7.3.15.2 b) 3) if it is older, clear SSN and set SRM
+ */
+ else if (cmp == LSP_OLDER) {
+ ISIS_CLEAR_FLAG(lsp->SSNflags, circuit);
+ isis_tx_queue_add(circuit->tx_queue, lsp,
+ TX_LSP_NORMAL);
+ }
+ /* 7.3.15.2 b) 4) if it is newer, set SSN and clear SRM
+ on p2p */
+ else {
+ if (own_lsp) {
+ lsp_inc_seqno(lsp, entry->seqno);
+ isis_tx_queue_add(circuit->tx_queue, lsp,
+ TX_LSP_NORMAL);
+ } else {
+ ISIS_SET_FLAG(lsp->SSNflags, circuit);
+ /* if (circuit->circ_type !=
+ * CIRCUIT_T_BROADCAST) */
+ isis_tx_queue_del(circuit->tx_queue, lsp);
+ resync_needed = true;
+ }
+ }
+ } else {
+ /* 7.3.15.2 b) 5) if it was not found, and all of those
+ * are not 0,
+ * insert it and set SSN on it */
+ if (entry->rem_lifetime && entry->checksum
+ && entry->seqno
+ && memcmp(entry->id, circuit->isis->sysid,
+ ISIS_SYS_ID_LEN)) {
+ struct isis_lsp *lsp0 = NULL;
+
+ if (LSP_FRAGMENT(entry->id)) {
+ uint8_t lspid[ISIS_SYS_ID_LEN + 2];
+
+ memcpy(lspid, entry->id,
+ ISIS_SYS_ID_LEN + 1);
+ LSP_FRAGMENT(lspid) = 0;
+ lsp0 = lsp_search(
+ &circuit->area->lspdb[level - 1],
+ lspid);
+ if (!lsp0) {
+ zlog_debug("Got lsp frag in snp, while zero not in database");
+ continue;
+ }
+ }
+ lsp = lsp_new(circuit->area, entry->id,
+ entry->rem_lifetime, 0, 0,
+ entry->checksum, lsp0, level);
+ lsp_insert(&circuit->area->lspdb[level - 1],
+ lsp);
+
+ lsp_set_all_srmflags(lsp, false);
+ ISIS_SET_FLAG(lsp->SSNflags, circuit);
+ resync_needed = true;
+ }
+ }
+ }
+
+ /* 7.3.15.2 c) on CSNP set SRM for all in range which were not reported
+ */
+ if (is_csnp) {
+ /*
+ * Build a list from our own LSP db bounded with
+ * start_lsp_id and stop_lsp_id
+ */
+ struct list *lsp_list = list_new();
+ lsp_build_list_nonzero_ht(&circuit->area->lspdb[level - 1],
+ start_lsp_id, stop_lsp_id, lsp_list);
+
+ /* Fixme: Find a better solution */
+ struct listnode *node, *nnode;
+ struct isis_lsp *lsp;
+ for (struct isis_lsp_entry *entry = entry_head; entry;
+ entry = entry->next) {
+ for (ALL_LIST_ELEMENTS(lsp_list, node, nnode, lsp)) {
+ if (lsp_id_cmp(lsp->hdr.lsp_id, entry->id)
+ == 0) {
+ list_delete_node(lsp_list, node);
+ break;
+ }
+ }
+ }
+
+ /* on remaining LSPs we set SRM (neighbor knew not of) */
+ for (ALL_LIST_ELEMENTS_RO(lsp_list, node, lsp)) {
+ isis_tx_queue_add(circuit->tx_queue, lsp, TX_LSP_NORMAL);
+ resync_needed = true;
+ }
+
+ /* lets free it */
+ list_delete(&lsp_list);
+ }
+
+ if (fabricd_initial_sync_is_complete(circuit->area) && resync_needed)
+ zlog_warn("OpenFabric: Needed to resync LSPDB using CSNP!");
+
+ retval = ISIS_OK;
+out:
+ isis_free_tlvs(tlvs);
+ return retval;
+}
+
+static int pdu_size(uint8_t pdu_type, uint8_t *size)
+{
+ switch (pdu_type) {
+ case L1_LAN_HELLO:
+ case L2_LAN_HELLO:
+ *size = ISIS_LANHELLO_HDRLEN;
+ break;
+ case P2P_HELLO:
+ *size = ISIS_P2PHELLO_HDRLEN;
+ break;
+ case L1_LINK_STATE:
+ case L2_LINK_STATE:
+ case FS_LINK_STATE:
+ *size = ISIS_LSP_HDR_LEN;
+ break;
+ case L1_COMPLETE_SEQ_NUM:
+ case L2_COMPLETE_SEQ_NUM:
+ *size = ISIS_CSNP_HDRLEN;
+ break;
+ case L1_PARTIAL_SEQ_NUM:
+ case L2_PARTIAL_SEQ_NUM:
+ *size = ISIS_PSNP_HDRLEN;
+ break;
+ default:
+ return 1;
+ }
+ *size += ISIS_FIXED_HDR_LEN;
+ return 0;
+}
+
+/*
+ * PDU Dispatcher
+ */
+
+int isis_handle_pdu(struct isis_circuit *circuit, uint8_t *ssnpa)
+{
+ int retval = ISIS_OK;
+ size_t pdu_start = stream_get_getp(circuit->rcv_stream);
+ size_t pdu_end = stream_get_endp(circuit->rcv_stream);
+ char raw_pdu[pdu_end - pdu_start];
+
+ stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start,
+ pdu_end - pdu_start);
+
+ /* Verify that at least the 8 bytes fixed header have been received */
+ if (stream_get_endp(circuit->rcv_stream) < ISIS_FIXED_HDR_LEN) {
+ flog_err(EC_ISIS_PACKET, "PDU is too short to be IS-IS.");
+ return ISIS_ERROR;
+ }
+
+ uint8_t idrp = stream_getc(circuit->rcv_stream);
+ uint8_t length = stream_getc(circuit->rcv_stream);
+ uint8_t version1 = stream_getc(circuit->rcv_stream);
+ uint8_t id_len = stream_getc(circuit->rcv_stream);
+ uint8_t pdu_type = stream_getc(circuit->rcv_stream)
+ & 0x1f; /* bits 6-8 are reserved */
+ uint8_t version2 = stream_getc(circuit->rcv_stream);
+
+ stream_forward_getp(circuit->rcv_stream, 1); /* reserved */
+ uint8_t max_area_addrs = stream_getc(circuit->rcv_stream);
+
+ pdu_counter_count(circuit->area->pdu_rx_counters, pdu_type);
+
+ if (idrp == ISO9542_ESIS) {
+ flog_err(EC_LIB_DEVELOPMENT,
+ "No support for ES-IS packet IDRP=%hhx", idrp);
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_ERROR;
+ }
+
+ if (idrp != ISO10589_ISIS) {
+ flog_err(EC_ISIS_PACKET, "Not an IS-IS packet IDRP=%hhx",
+ idrp);
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_ERROR;
+ }
+
+ if (version1 != 1) {
+ zlog_warn("Unsupported ISIS version %hhu", version1);
+#ifndef FABRICD
+ /* send northbound notification */
+ isis_notif_version_skew(circuit, version1, raw_pdu,
+ sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_WARNING;
+ }
+
+ if (id_len != 0 && id_len != ISIS_SYS_ID_LEN) {
+ flog_err(
+ EC_ISIS_PACKET,
+ "IDFieldLengthMismatch: ID Length field in a received PDU %hhu, while the parameter for this IS is %u",
+ id_len, ISIS_SYS_ID_LEN);
+ circuit->id_len_mismatches++;
+ if (circuit->is_type == IS_LEVEL_1) {
+ circuit->area->id_len_mismatches[0]++;
+ } else if (circuit->is_type == IS_LEVEL_2) {
+ circuit->area->id_len_mismatches[1]++;
+ } else {
+ circuit->area->id_len_mismatches[0]++;
+ circuit->area->id_len_mismatches[1]++;
+ }
+
+#ifndef FABRICD
+ /* send northbound notification */
+ isis_notif_id_len_mismatch(circuit, id_len, raw_pdu,
+ sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_ERROR;
+ }
+
+ uint8_t expected_length;
+ if (pdu_size(pdu_type, &expected_length)) {
+ zlog_warn("Unsupported ISIS PDU %hhu", pdu_type);
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_WARNING;
+ }
+
+ if (length != expected_length) {
+ flog_err(EC_ISIS_PACKET,
+ "Expected fixed header length = %hhu but got %hhu",
+ expected_length, length);
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_ERROR;
+ }
+
+ if (stream_get_endp(circuit->rcv_stream) < length) {
+ flog_err(
+ EC_ISIS_PACKET,
+ "PDU is too short to contain fixed header of given PDU type.");
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_ERROR;
+ }
+
+ if (version2 != 1) {
+ zlog_warn("Unsupported ISIS PDU version %hhu", version2);
+#ifndef FABRICD
+ /* send northbound notification */
+ isis_notif_version_skew(circuit, version2, raw_pdu,
+ sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_WARNING;
+ }
+
+ if (circuit->is_passive) {
+ zlog_warn("Received ISIS PDU on passive circuit %s",
+ circuit->interface->name);
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_WARNING;
+ }
+
+ /* either 3 or 0 */
+ if (pdu_type != FS_LINK_STATE /* FS PDU doesn't contain max area addr
+ field */
+ && max_area_addrs != 0
+ && max_area_addrs != circuit->isis->max_area_addrs) {
+ flog_err(
+ EC_ISIS_PACKET,
+ "maximumAreaAddressesMismatch: maximumAreaAdresses in a received PDU %hhu while the parameter for this IS is %u",
+ max_area_addrs, circuit->isis->max_area_addrs);
+ circuit->max_area_addr_mismatches++;
+#ifndef FABRICD
+ /* send northbound notification */
+ isis_notif_max_area_addr_mismatch(circuit, max_area_addrs,
+ raw_pdu, sizeof(raw_pdu));
+#endif /* ifndef FABRICD */
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_ERROR;
+ }
+
+ switch (pdu_type) {
+ case L1_LAN_HELLO:
+ case L2_LAN_HELLO:
+ case P2P_HELLO:
+ if (fabricd && pdu_type != P2P_HELLO) {
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_ERROR;
+ }
+
+ retval = process_hello(pdu_type, circuit, ssnpa);
+ break;
+ case L1_LINK_STATE:
+ case L2_LINK_STATE:
+ case FS_LINK_STATE:
+ if (fabricd && pdu_type != L2_LINK_STATE &&
+ pdu_type != FS_LINK_STATE) {
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_ERROR;
+ }
+
+ retval = process_lsp(pdu_type, circuit, ssnpa, max_area_addrs);
+ break;
+ case L1_COMPLETE_SEQ_NUM:
+ case L2_COMPLETE_SEQ_NUM:
+ case L1_PARTIAL_SEQ_NUM:
+ case L2_PARTIAL_SEQ_NUM:
+ retval = process_snp(pdu_type, circuit, ssnpa);
+ break;
+ default:
+ pdu_counter_count_drop(circuit->area, pdu_type);
+ return ISIS_ERROR;
+ }
+
+ if (retval != ISIS_OK)
+ pdu_counter_count_drop(circuit->area, pdu_type);
+
+ return retval;
+}
+
+void isis_receive(struct event *thread)
+{
+ struct isis_circuit *circuit;
+ uint8_t ssnpa[ETH_ALEN];
+
+ /*
+ * Get the circuit
+ */
+ circuit = EVENT_ARG(thread);
+ assert(circuit);
+
+ circuit->t_read = NULL;
+
+ isis_circuit_stream(circuit, &circuit->rcv_stream);
+
+#if ISIS_METHOD != ISIS_METHOD_BPF
+ int retval;
+
+ retval = circuit->rx(circuit, ssnpa);
+
+ if (retval == ISIS_OK)
+ isis_handle_pdu(circuit, ssnpa);
+#else // ISIS_METHOD != ISIS_METHOD_BPF
+ circuit->rx(circuit, ssnpa);
+#endif
+
+ /*
+ * prepare for next packet.
+ */
+ if (!circuit->is_passive)
+ isis_circuit_prepare(circuit);
+}
+
+/*
+ * SEND SIDE
+ */
+void fill_fixed_hdr(uint8_t pdu_type, struct stream *stream)
+{
+ uint8_t length;
+
+ if (pdu_size(pdu_type, &length))
+ assert(!"Unknown PDU Type");
+
+ stream_putc(stream, ISO10589_ISIS); /* IDRP */
+ stream_putc(stream, length); /* Length of fixed header */
+ stream_putc(stream, 1); /* Version/Protocol ID Extension 1 */
+ stream_putc(stream, 0); /* ID Length, 0 => 6 */
+ stream_putc(stream, pdu_type);
+ stream_putc(stream, 1); /* Subversion */
+ stream_putc(stream, 0); /* Reserved */
+ stream_putc(stream, 0); /* Max Area Addresses 0 => 3 */
+}
+
+static uint8_t hello_pdu_type(struct isis_circuit *circuit, int level)
+{
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST)
+ return (level == IS_LEVEL_1) ? L1_LAN_HELLO : L2_LAN_HELLO;
+ else
+ return P2P_HELLO;
+}
+
+static void put_hello_hdr(struct isis_circuit *circuit, int level,
+ size_t *len_pointer)
+{
+ uint8_t pdu_type = hello_pdu_type(circuit, level);
+
+ isis_circuit_stream(circuit, &circuit->snd_stream);
+ fill_fixed_hdr(pdu_type, circuit->snd_stream);
+
+ stream_putc(circuit->snd_stream, circuit->is_type);
+ stream_put(circuit->snd_stream, circuit->isis->sysid, ISIS_SYS_ID_LEN);
+
+ uint32_t holdtime = circuit->hello_multiplier[level - 1]
+ * circuit->hello_interval[level - 1];
+
+ if (holdtime > 0xffff)
+ holdtime = 0xffff;
+
+ stream_putw(circuit->snd_stream, holdtime);
+ *len_pointer = stream_get_endp(circuit->snd_stream);
+ stream_putw(circuit->snd_stream, 0); /* length is filled in later */
+
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ uint8_t *desig_is = (level == IS_LEVEL_1)
+ ? circuit->u.bc.l1_desig_is
+ : circuit->u.bc.l2_desig_is;
+ stream_putc(circuit->snd_stream, circuit->priority[level - 1]);
+ stream_put(circuit->snd_stream, desig_is, ISIS_SYS_ID_LEN + 1);
+ } else {
+ stream_putc(circuit->snd_stream, circuit->circuit_id);
+ }
+}
+
+int send_hello(struct isis_circuit *circuit, int level)
+{
+ size_t len_pointer;
+ int retval;
+
+ if (circuit->is_passive)
+ return ISIS_OK;
+
+ if (circuit->interface->mtu == 0) {
+ zlog_warn("circuit has zero MTU");
+ return ISIS_WARNING;
+ }
+
+ put_hello_hdr(circuit, level, &len_pointer);
+
+ struct isis_tlvs *tlvs = isis_alloc_tlvs();
+
+ isis_tlvs_add_auth(tlvs, &circuit->passwd);
+
+ if (!listcount(circuit->area->area_addrs)) {
+ isis_free_tlvs(tlvs);
+ return ISIS_WARNING;
+ }
+
+ isis_tlvs_add_area_addresses(tlvs, circuit->area->area_addrs);
+
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ isis_tlvs_add_lan_neighbors(
+ tlvs, circuit->u.bc.lan_neighs[level - 1]);
+ } else if (circuit->circ_type == CIRCUIT_T_P2P
+ && !circuit->disable_threeway_adj) {
+ uint32_t ext_circuit_id = circuit->idx;
+ if (circuit->u.p2p.neighbor) {
+ uint8_t threeway_state;
+
+ if (fabricd_initial_sync_is_in_progress(circuit->area)
+ && fabricd_initial_sync_circuit(circuit->area) != circuit)
+ threeway_state = ISIS_THREEWAY_DOWN;
+ else
+ threeway_state = circuit->u.p2p.neighbor->threeway_state;
+ isis_tlvs_add_threeway_adj(tlvs,
+ threeway_state,
+ ext_circuit_id,
+ circuit->u.p2p.neighbor->sysid,
+ circuit->u.p2p.neighbor->ext_circuit_id);
+ } else {
+ isis_tlvs_add_threeway_adj(tlvs,
+ ISIS_THREEWAY_DOWN,
+ ext_circuit_id,
+ NULL, 0);
+ }
+ }
+
+ isis_tlvs_set_protocols_supported(tlvs, &circuit->nlpids);
+
+ /*
+ * MT Supported TLV
+ *
+ * TLV gets included if no topology is enabled on the interface,
+ * if one topology other than #0 is enabled, or if multiple topologies
+ * are enabled.
+ */
+ struct isis_circuit_mt_setting **mt_settings;
+ unsigned int mt_count;
+
+ mt_settings = circuit_mt_settings(circuit, &mt_count);
+ if (mt_count == 0 && area_is_mt(circuit->area)) {
+ tlvs->mt_router_info_empty = true;
+ } else if ((mt_count == 1
+ && mt_settings[0]->mtid != ISIS_MT_IPV4_UNICAST)
+ || (mt_count > 1)) {
+ for (unsigned int i = 0; i < mt_count; i++)
+ isis_tlvs_add_mt_router_info(tlvs, mt_settings[i]->mtid,
+ false, false);
+ }
+
+ if (circuit->ip_router) {
+ struct list *circuit_ip_addrs = fabricd_ip_addrs(circuit);
+
+ if (circuit_ip_addrs)
+ isis_tlvs_add_ipv4_addresses(tlvs, circuit_ip_addrs);
+ }
+
+ if (circuit->ipv6_router)
+ isis_tlvs_add_ipv6_addresses(tlvs, circuit->ipv6_link);
+
+ /* RFC6119 section 4 define TLV 233 to provide Global IPv6 address */
+ if (circuit->ipv6_router)
+ isis_tlvs_add_global_ipv6_addresses(tlvs,
+ circuit->ipv6_non_link);
+
+ bool should_pad_hello =
+ circuit->pad_hellos == ISIS_HELLO_PADDING_ALWAYS ||
+ (circuit->pad_hellos ==
+ ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION &&
+ circuit->upadjcount[0] + circuit->upadjcount[1] == 0);
+
+ if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer,
+ should_pad_hello, false)) {
+ isis_free_tlvs(tlvs);
+ return ISIS_WARNING; /* XXX: Maybe Log TLV structure? */
+ }
+
+ if (IS_DEBUG_ADJ_PACKETS) {
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ zlog_debug(
+ "ISIS-Adj (%s): Sending L%d LAN IIH on %s, length %zd",
+ circuit->area->area_tag, level,
+ circuit->interface->name,
+ stream_get_endp(circuit->snd_stream));
+ } else {
+ zlog_debug(
+ "ISIS-Adj (%s): Sending P2P IIH on %s, length %zd",
+ circuit->area->area_tag,
+ circuit->interface->name,
+ stream_get_endp(circuit->snd_stream));
+ }
+ if (IS_DEBUG_PACKET_DUMP)
+ zlog_dump_data(STREAM_DATA(circuit->snd_stream),
+ stream_get_endp(circuit->snd_stream));
+ }
+
+ isis_free_tlvs(tlvs);
+
+ pdu_counter_count(circuit->area->pdu_tx_counters,
+ hello_pdu_type(circuit, level));
+ retval = circuit->tx(circuit, level);
+ if (retval != ISIS_OK)
+ flog_err(EC_ISIS_PACKET,
+ "ISIS-Adj (%s): Send L%d IIH on %s failed",
+ circuit->area->area_tag, level,
+ circuit->interface->name);
+
+ return retval;
+}
+
+static void send_hello_cb(struct event *thread)
+{
+ struct isis_circuit_arg *arg = EVENT_ARG(thread);
+ assert(arg);
+
+ struct isis_circuit *circuit = arg->circuit;
+ int level = arg->level;
+
+ assert(circuit);
+
+ if (circuit->circ_type == CIRCUIT_T_P2P) {
+ circuit->u.p2p.t_send_p2p_hello = NULL;
+ send_hello(circuit, 1);
+ send_hello_sched(circuit, ISIS_LEVEL1,
+ 1000 * circuit->hello_interval[1]);
+ return;
+ }
+
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST) {
+ zlog_warn("ISIS-Hello (%s): Trying to send hello on unknown circuit type %d",
+ circuit->area->area_tag, circuit->circ_type);
+ return;
+ }
+
+ circuit->u.bc.t_send_lan_hello[level - 1] = NULL;
+ if (!(circuit->is_type & level)) {
+ zlog_warn("ISIS-Hello (%s): Trying to send L%d IIH in L%d-only circuit",
+ circuit->area->area_tag, level, 3 - level);
+ return;
+ }
+
+ if (circuit->u.bc.run_dr_elect[level - 1])
+ isis_dr_elect(circuit, level);
+
+ send_hello(circuit, level);
+
+ /* set next timer thread */
+ send_hello_sched(circuit, level, 1000 * circuit->hello_interval[level - 1]);
+}
+
+static void _send_hello_sched(struct isis_circuit *circuit,
+ struct event **threadp, int level, long delay)
+{
+ if (*threadp) {
+ if (event_timer_remain_msec(*threadp) < (unsigned long)delay)
+ return;
+
+ EVENT_OFF(*threadp);
+ }
+
+ event_add_timer_msec(master, send_hello_cb,
+ &circuit->level_arg[level - 1],
+ isis_jitter(delay, IIH_JITTER), threadp);
+}
+
+void send_hello_sched(struct isis_circuit *circuit, int level, long delay)
+{
+ if (circuit->circ_type == CIRCUIT_T_P2P) {
+ _send_hello_sched(circuit, &circuit->u.p2p.t_send_p2p_hello,
+ ISIS_LEVEL1, delay);
+ return;
+ }
+
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST) {
+ zlog_warn("%s: encountered unknown circuit type %d on %s",
+ __func__, circuit->circ_type,
+ circuit->interface->name);
+ return;
+ }
+
+ for (int loop_level = ISIS_LEVEL1; loop_level <= ISIS_LEVEL2; loop_level++) {
+ if (!(loop_level & level))
+ continue;
+
+ _send_hello_sched(
+ circuit,
+ &circuit->u.bc.t_send_lan_hello[loop_level - 1],
+ loop_level,
+ delay
+ );
+ }
+}
+
+
+/*
+ * Count the maximum number of lsps that can be accommodated by a given size.
+ */
+#define LSP_ENTRIES_LEN (10 + ISIS_SYS_ID_LEN)
+static uint16_t get_max_lsp_count(uint16_t size)
+{
+ uint16_t tlv_count;
+ uint16_t lsp_count;
+ uint16_t remaining_size;
+
+ /* First count the full size TLVs */
+ tlv_count = size / MAX_LSP_ENTRIES_TLV_SIZE;
+ lsp_count = tlv_count * (MAX_LSP_ENTRIES_TLV_SIZE / LSP_ENTRIES_LEN);
+
+ /* The last TLV, if any */
+ remaining_size = size % MAX_LSP_ENTRIES_TLV_SIZE;
+ if (remaining_size - 2 >= LSP_ENTRIES_LEN)
+ lsp_count += (remaining_size - 2) / LSP_ENTRIES_LEN;
+
+ return lsp_count;
+}
+
+int send_csnp(struct isis_circuit *circuit, int level)
+{
+ if (lspdb_count(&circuit->area->lspdb[level - 1]) == 0)
+ return ISIS_OK;
+
+ uint8_t pdu_type = (level == ISIS_LEVEL1) ? L1_COMPLETE_SEQ_NUM
+ : L2_COMPLETE_SEQ_NUM;
+
+ isis_circuit_stream(circuit, &circuit->snd_stream);
+ fill_fixed_hdr(pdu_type, circuit->snd_stream);
+
+ size_t len_pointer = stream_get_endp(circuit->snd_stream);
+
+ stream_putw(circuit->snd_stream, 0);
+ stream_put(circuit->snd_stream, circuit->isis->sysid, ISIS_SYS_ID_LEN);
+ /* with zero circuit id - ref 9.10, 9.11 */
+ stream_putc(circuit->snd_stream, 0);
+
+ size_t start_pointer = stream_get_endp(circuit->snd_stream);
+ stream_put(circuit->snd_stream, 0, ISIS_SYS_ID_LEN + 2);
+ size_t end_pointer = stream_get_endp(circuit->snd_stream);
+ stream_put(circuit->snd_stream, 0, ISIS_SYS_ID_LEN + 2);
+
+ struct isis_passwd *passwd = (level == ISIS_LEVEL1)
+ ? &circuit->area->area_passwd
+ : &circuit->area->domain_passwd;
+
+ struct isis_tlvs *tlvs = isis_alloc_tlvs();
+
+ if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND))
+ isis_tlvs_add_auth(tlvs, passwd);
+
+ size_t tlv_start = stream_get_endp(circuit->snd_stream);
+ if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, false,
+ false)) {
+ isis_free_tlvs(tlvs);
+ return ISIS_WARNING;
+ }
+ isis_free_tlvs(tlvs);
+
+ uint16_t num_lsps =
+ get_max_lsp_count(STREAM_WRITEABLE(circuit->snd_stream));
+
+ uint8_t start[ISIS_SYS_ID_LEN + 2];
+ memset(start, 0x00, ISIS_SYS_ID_LEN + 2);
+ uint8_t stop[ISIS_SYS_ID_LEN + 2];
+ memset(stop, 0xff, ISIS_SYS_ID_LEN + 2);
+
+ bool loop = true;
+ while (loop) {
+ tlvs = isis_alloc_tlvs();
+ if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND))
+ isis_tlvs_add_auth(tlvs, passwd);
+
+ struct isis_lsp *last_lsp;
+ isis_tlvs_add_csnp_entries(tlvs, start, stop, num_lsps,
+ &circuit->area->lspdb[level - 1],
+ &last_lsp);
+ /*
+ * Update the stop lsp_id before encoding this CSNP.
+ */
+ if (tlvs->lsp_entries.count < num_lsps) {
+ memset(stop, 0xff, ISIS_SYS_ID_LEN + 2);
+ } else {
+ memcpy(stop, last_lsp->hdr.lsp_id, sizeof(stop));
+ }
+
+ memcpy(STREAM_DATA(circuit->snd_stream) + start_pointer, start,
+ ISIS_SYS_ID_LEN + 2);
+ memcpy(STREAM_DATA(circuit->snd_stream) + end_pointer, stop,
+ ISIS_SYS_ID_LEN + 2);
+ stream_set_endp(circuit->snd_stream, tlv_start);
+ if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer,
+ false, false)) {
+ isis_free_tlvs(tlvs);
+ return ISIS_WARNING;
+ }
+
+ if (IS_DEBUG_SNP_PACKETS) {
+ zlog_debug(
+ "ISIS-Snp (%s): Sending L%d CSNP on %s, length %zd",
+ circuit->area->area_tag, level,
+ circuit->interface->name,
+ stream_get_endp(circuit->snd_stream));
+ log_multiline(LOG_DEBUG, " ", "%s",
+ isis_format_tlvs(tlvs, NULL));
+ if (IS_DEBUG_PACKET_DUMP)
+ zlog_dump_data(
+ STREAM_DATA(circuit->snd_stream),
+ stream_get_endp(circuit->snd_stream));
+ }
+
+ pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type);
+ int retval = circuit->tx(circuit, level);
+ if (retval != ISIS_OK) {
+ flog_err(EC_ISIS_PACKET,
+ "ISIS-Snp (%s): Send L%d CSNP on %s failed",
+ circuit->area->area_tag, level,
+ circuit->interface->name);
+ isis_free_tlvs(tlvs);
+ return retval;
+ }
+
+ /*
+ * Start lsp_id of the next CSNP should be one plus the
+ * stop lsp_id in this current CSNP.
+ */
+ memcpy(start, stop, ISIS_SYS_ID_LEN + 2);
+ loop = false;
+ for (int i = ISIS_SYS_ID_LEN + 1; i >= 0; --i) {
+ if (start[i] < (uint8_t)0xff) {
+ start[i] += 1;
+ loop = true;
+ break;
+ }
+ }
+ memset(stop, 0xff, ISIS_SYS_ID_LEN + 2);
+ isis_free_tlvs(tlvs);
+ }
+
+ return ISIS_OK;
+}
+
+void send_l1_csnp(struct event *thread)
+{
+ struct isis_circuit *circuit;
+
+ circuit = EVENT_ARG(thread);
+ assert(circuit);
+
+ circuit->t_send_csnp[0] = NULL;
+
+ if ((circuit->circ_type == CIRCUIT_T_BROADCAST
+ && circuit->u.bc.is_dr[0])
+ || circuit->circ_type == CIRCUIT_T_P2P) {
+ send_csnp(circuit, 1);
+ }
+ /* set next timer thread */
+ event_add_timer(master, send_l1_csnp, circuit,
+ isis_jitter(circuit->csnp_interval[0], CSNP_JITTER),
+ &circuit->t_send_csnp[0]);
+}
+
+void send_l2_csnp(struct event *thread)
+{
+ struct isis_circuit *circuit;
+
+ circuit = EVENT_ARG(thread);
+ assert(circuit);
+
+ circuit->t_send_csnp[1] = NULL;
+
+ if ((circuit->circ_type == CIRCUIT_T_BROADCAST
+ && circuit->u.bc.is_dr[1])
+ || circuit->circ_type == CIRCUIT_T_P2P) {
+ send_csnp(circuit, 2);
+ }
+ /* set next timer thread */
+ event_add_timer(master, send_l2_csnp, circuit,
+ isis_jitter(circuit->csnp_interval[1], CSNP_JITTER),
+ &circuit->t_send_csnp[1]);
+}
+
+/*
+ * 7.3.15.4 action on expiration of partial SNP interval
+ * level 1
+ */
+static int send_psnp(int level, struct isis_circuit *circuit)
+{
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST
+ && circuit->u.bc.is_dr[level - 1])
+ return ISIS_OK;
+
+ if (lspdb_count(&circuit->area->lspdb[level - 1]) == 0)
+ return ISIS_OK;
+
+ if (!circuit->snd_stream)
+ return ISIS_ERROR;
+
+ uint8_t pdu_type = (level == ISIS_LEVEL1) ? L1_PARTIAL_SEQ_NUM
+ : L2_PARTIAL_SEQ_NUM;
+
+ isis_circuit_stream(circuit, &circuit->snd_stream);
+ fill_fixed_hdr(pdu_type, circuit->snd_stream);
+
+ size_t len_pointer = stream_get_endp(circuit->snd_stream);
+ stream_putw(circuit->snd_stream, 0); /* length is filled in later */
+ stream_put(circuit->snd_stream, circuit->isis->sysid, ISIS_SYS_ID_LEN);
+ stream_putc(circuit->snd_stream, circuit->idx);
+
+ struct isis_passwd *passwd = (level == ISIS_LEVEL1)
+ ? &circuit->area->area_passwd
+ : &circuit->area->domain_passwd;
+
+ struct isis_tlvs *tlvs = isis_alloc_tlvs();
+
+ if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND))
+ isis_tlvs_add_auth(tlvs, passwd);
+
+ size_t tlv_start = stream_get_endp(circuit->snd_stream);
+ if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, false,
+ false)) {
+ isis_free_tlvs(tlvs);
+ return ISIS_WARNING;
+ }
+ isis_free_tlvs(tlvs);
+
+ uint16_t num_lsps =
+ get_max_lsp_count(STREAM_WRITEABLE(circuit->snd_stream));
+
+ while (1) {
+ struct isis_lsp *lsp;
+
+ tlvs = isis_alloc_tlvs();
+ if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND))
+ isis_tlvs_add_auth(tlvs, passwd);
+
+ frr_each (lspdb, &circuit->area->lspdb[level - 1], lsp) {
+ if (ISIS_CHECK_FLAG(lsp->SSNflags, circuit))
+ isis_tlvs_add_lsp_entry(tlvs, lsp);
+
+ if (tlvs->lsp_entries.count == num_lsps)
+ break;
+ }
+
+ if (!tlvs->lsp_entries.count) {
+ isis_free_tlvs(tlvs);
+ return ISIS_OK;
+ }
+
+ stream_set_endp(circuit->snd_stream, tlv_start);
+ if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer,
+ false, false)) {
+ isis_free_tlvs(tlvs);
+ return ISIS_WARNING;
+ }
+
+ if (IS_DEBUG_SNP_PACKETS) {
+ zlog_debug(
+ "ISIS-Snp (%s): Sending L%d PSNP on %s, length %zd",
+ circuit->area->area_tag, level,
+ circuit->interface->name,
+ stream_get_endp(circuit->snd_stream));
+ log_multiline(LOG_DEBUG, " ", "%s",
+ isis_format_tlvs(tlvs, NULL));
+ if (IS_DEBUG_PACKET_DUMP)
+ zlog_dump_data(
+ STREAM_DATA(circuit->snd_stream),
+ stream_get_endp(circuit->snd_stream));
+ }
+
+ pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type);
+ int retval = circuit->tx(circuit, level);
+ if (retval != ISIS_OK) {
+ flog_err(EC_ISIS_PACKET,
+ "ISIS-Snp (%s): Send L%d PSNP on %s failed",
+ circuit->area->area_tag, level,
+ circuit->interface->name);
+ isis_free_tlvs(tlvs);
+ return retval;
+ }
+
+ /*
+ * sending succeeded, we can clear SSN flags of this circuit
+ * for the LSPs in list
+ */
+ struct isis_lsp_entry *entry_head;
+ entry_head = (struct isis_lsp_entry *)tlvs->lsp_entries.head;
+ for (struct isis_lsp_entry *entry = entry_head; entry;
+ entry = entry->next)
+ ISIS_CLEAR_FLAG(entry->lsp->SSNflags, circuit);
+ isis_free_tlvs(tlvs);
+ }
+
+ return ISIS_OK;
+}
+
+void send_l1_psnp(struct event *thread)
+{
+
+ struct isis_circuit *circuit;
+
+ circuit = EVENT_ARG(thread);
+ assert(circuit);
+
+ circuit->t_send_psnp[0] = NULL;
+
+ send_psnp(1, circuit);
+ /* set next timer thread */
+ event_add_timer(master, send_l1_psnp, circuit,
+ isis_jitter(circuit->psnp_interval[0], PSNP_JITTER),
+ &circuit->t_send_psnp[0]);
+}
+
+/*
+ * 7.3.15.4 action on expiration of partial SNP interval
+ * level 2
+ */
+void send_l2_psnp(struct event *thread)
+{
+ struct isis_circuit *circuit;
+
+ circuit = EVENT_ARG(thread);
+ assert(circuit);
+
+ circuit->t_send_psnp[1] = NULL;
+
+ send_psnp(2, circuit);
+
+ /* set next timer thread */
+ event_add_timer(master, send_l2_psnp, circuit,
+ isis_jitter(circuit->psnp_interval[1], PSNP_JITTER),
+ &circuit->t_send_psnp[1]);
+}
+
+/*
+ * ISO 10589 - 7.3.14.3
+ */
+void send_lsp(struct isis_circuit *circuit, struct isis_lsp *lsp,
+ enum isis_tx_type tx_type)
+{
+ int clear_srm = 1;
+ int retval = ISIS_OK;
+
+ if (circuit->state != C_STATE_UP || circuit->is_passive == 1)
+ goto out;
+
+ /*
+ * Do not send if levels do not match
+ */
+ if (!(lsp->level & circuit->is_type))
+ goto out;
+
+ /*
+ * Do not send if we do not have adjacencies in state up on the circuit
+ */
+ if (circuit->upadjcount[lsp->level - 1] == 0)
+ goto out;
+
+ /* stream_copy will assert and stop program execution if LSP is larger
+ * than
+ * the circuit's MTU. So handle and log this case here. */
+ if (stream_get_endp(lsp->pdu) > stream_get_size(circuit->snd_stream)) {
+ flog_err(
+ EC_ISIS_PACKET,
+ "ISIS-Upd (%s): Can't send L%d LSP %pLS, seq 0x%08x, cksum 0x%04hx, lifetime %hus on %s. LSP Size is %zu while interface stream size is %zu.",
+ circuit->area->area_tag, lsp->level, lsp->hdr.lsp_id,
+ lsp->hdr.seqno, lsp->hdr.checksum,
+ lsp->hdr.rem_lifetime, circuit->interface->name,
+ stream_get_endp(lsp->pdu),
+ stream_get_size(circuit->snd_stream));
+#ifndef FABRICD
+ /* send a northbound notification */
+ isis_notif_lsp_too_large(circuit, stream_get_endp(lsp->pdu),
+ lsp->hdr.lsp_id);
+#endif /* ifndef FABRICD */
+ if (IS_DEBUG_PACKET_DUMP)
+ zlog_dump_data(STREAM_DATA(lsp->pdu),
+ stream_get_endp(lsp->pdu));
+ retval = ISIS_ERROR;
+ goto out;
+ }
+
+ /* copy our lsp to the send buffer */
+ stream_copy(circuit->snd_stream, lsp->pdu);
+
+ if (tx_type == TX_LSP_CIRCUIT_SCOPED) {
+ stream_putc_at(circuit->snd_stream, 4, FS_LINK_STATE);
+ stream_putc_at(circuit->snd_stream, 7,
+ L2_CIRCUIT_FLOODING_SCOPE);
+ }
+
+ if (IS_DEBUG_UPDATE_PACKETS) {
+ zlog_debug(
+ "ISIS-Upd (%s): Sending %sL%d LSP %pLS, seq 0x%08x, cksum 0x%04hx, lifetime %hus on %s",
+ circuit->area->area_tag,
+ (tx_type == TX_LSP_CIRCUIT_SCOPED) ? "Circuit scoped "
+ : "",
+ lsp->level, lsp->hdr.lsp_id, lsp->hdr.seqno,
+ lsp->hdr.checksum, lsp->hdr.rem_lifetime,
+ circuit->interface->name);
+ if (IS_DEBUG_PACKET_DUMP)
+ zlog_dump_data(STREAM_DATA(circuit->snd_stream),
+ stream_get_endp(circuit->snd_stream));
+ }
+
+ uint8_t pdu_type = (tx_type == TX_LSP_CIRCUIT_SCOPED) ? FS_LINK_STATE
+ : (lsp->level == ISIS_LEVEL1) ? L1_LINK_STATE
+ : L2_LINK_STATE;
+
+ clear_srm = 0;
+ pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type);
+ retval = circuit->tx(circuit, lsp->level);
+ if (retval != ISIS_OK) {
+ flog_err(EC_ISIS_PACKET,
+ "ISIS-Upd (%s): Send L%d LSP on %s failed %s",
+ circuit->area->area_tag, lsp->level,
+ circuit->interface->name,
+ (retval == ISIS_WARNING) ? "temporarily"
+ : "permanently");
+ }
+
+out:
+ if (clear_srm
+ || (retval == ISIS_OK && circuit->circ_type == CIRCUIT_T_BROADCAST)
+ || (retval != ISIS_OK && retval != ISIS_WARNING)) {
+ /* SRM flag will trigger retransmission. We will not retransmit
+ * if we
+ * encountered a fatal error.
+ * On success, they should only be cleared if it's a broadcast
+ * circuit.
+ * On a P2P circuit, we will wait for the ack from the neighbor
+ * to clear
+ * the fag.
+ */
+ isis_tx_queue_del(circuit->tx_queue, lsp);
+ }
+}
+
+void isis_log_pdu_drops(struct isis_area *area, const char *pdu_type)
+{
+ uint64_t total_drops = 0;
+
+ for (int i = 0; i < PDU_COUNTER_SIZE; i++) {
+ if (!area->pdu_drop_counters[i])
+ continue;
+ total_drops += area->pdu_drop_counters[i];
+ }
+
+ zlog_info("PDU drop detected of type: %s. %" PRIu64
+ " Total Drops; %" PRIu64 " L1 IIH drops; %" PRIu64
+ " L2 IIH drops; %" PRIu64 " P2P IIH drops; %" PRIu64
+ " L1 LSP drops; %" PRIu64 " L2 LSP drops; %" PRIu64
+ " FS LSP drops; %" PRIu64 " L1 CSNP drops; %" PRIu64
+ " L2 CSNP drops; %" PRIu64 " L1 PSNP drops; %" PRIu64
+ " L2 PSNP drops.",
+ pdu_type, total_drops,
+ pdu_counter_get_count(area->pdu_drop_counters, L1_LAN_HELLO),
+ pdu_counter_get_count(area->pdu_drop_counters, L2_LAN_HELLO),
+ pdu_counter_get_count(area->pdu_drop_counters, P2P_HELLO),
+ pdu_counter_get_count(area->pdu_drop_counters, L1_LINK_STATE),
+ pdu_counter_get_count(area->pdu_drop_counters, L2_LINK_STATE),
+ pdu_counter_get_count(area->pdu_drop_counters, FS_LINK_STATE),
+ pdu_counter_get_count(area->pdu_drop_counters,
+ L1_COMPLETE_SEQ_NUM),
+ pdu_counter_get_count(area->pdu_drop_counters,
+ L2_COMPLETE_SEQ_NUM),
+ pdu_counter_get_count(area->pdu_drop_counters,
+ L1_PARTIAL_SEQ_NUM),
+ pdu_counter_get_count(area->pdu_drop_counters,
+ L2_PARTIAL_SEQ_NUM));
+}
diff --git a/isisd/isis_pdu.h b/isisd/isis_pdu.h
new file mode 100644
index 0000000..5303c61
--- /dev/null
+++ b/isisd/isis_pdu.h
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_pdu.h
+ * PDU processing
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#ifndef _ZEBRA_ISIS_PDU_H
+#define _ZEBRA_ISIS_PDU_H
+
+#include "isisd/isis_tx_queue.h"
+
+#ifdef __SUNPRO_C
+#pragma pack(1)
+#endif
+
+/*
+ * ISO 9542 - 7.5,7.6
+ *
+ * ES to IS Fixed Header
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | Intradomain Routeing Protocol Discriminator |
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | Length Indicator |
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | Version/Protocol ID extension |
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | Reserved = 0 |
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | 0 | 0 | 0 | PDU Type |
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | Holding Time | 2
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | Checksum | 2
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ */
+
+struct esis_fixed_hdr {
+ uint8_t idrp;
+ uint8_t length;
+ uint8_t version;
+ uint8_t id_len;
+ uint8_t pdu_type;
+ uint16_t holdtime;
+ uint16_t checksum;
+} __attribute__((packed));
+
+#define ESIS_FIXED_HDR_LEN 9
+
+#define ESH_PDU 2
+#define ISH_PDU 4
+#define RD_PDU 5
+
+#define ISIS_FIXED_HDR_LEN 8
+
+/*
+ * IS-IS PDU types.
+ */
+
+#define L1_LAN_HELLO 15
+#define L2_LAN_HELLO 16
+/*
+ * L1 and L2 LAN IS to IS Hello PDU header
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | Reserved | Circuit Type | 1
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * + Source ID + id_len
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | Holding Time | 2
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | PDU Length | 2
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | R | Priority | 1
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | LAN ID | id_len + 1
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ */
+struct isis_lan_hello_hdr {
+ uint8_t circuit_t;
+ uint8_t source_id[ISIS_SYS_ID_LEN];
+ uint16_t hold_time;
+ uint16_t pdu_len;
+ uint8_t prio;
+ uint8_t lan_id[ISIS_SYS_ID_LEN + 1];
+} __attribute__((packed));
+#define ISIS_LANHELLO_HDRLEN 19
+
+#define P2P_HELLO 17
+/*
+ * Point-to-point IS to IS hello PDU header
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | Reserved | Circuit Type | 1
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * + Source ID + id_len
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * + Holding Time + 2
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * + PDU Length + 2
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * | Local Circuit ID | 1
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ */
+struct isis_p2p_hello_hdr {
+ uint8_t circuit_t;
+ uint8_t source_id[ISIS_SYS_ID_LEN];
+ uint16_t hold_time;
+ uint16_t pdu_len;
+ uint8_t local_id;
+} __attribute__((packed));
+#define ISIS_P2PHELLO_HDRLEN 12
+
+#define L1_LINK_STATE 18
+#define L2_LINK_STATE 20
+#define FS_LINK_STATE 10
+#define L2_CIRCUIT_FLOODING_SCOPE 2
+struct isis_lsp_hdr {
+ uint16_t pdu_len;
+ uint16_t rem_lifetime;
+ uint8_t lsp_id[ISIS_SYS_ID_LEN + 2];
+ uint32_t seqno;
+ uint16_t checksum;
+ uint8_t lsp_bits;
+};
+#define ISIS_LSP_HDR_LEN 19
+
+/*
+ * Since the length field of LSP Entries TLV is one byte long, and each LSP
+ * entry is LSP_ENTRIES_LEN (16) bytes long, the maximum number of LSP entries
+ * can be accommodated in a TLV is
+ * 255 / 16 = 15.
+ *
+ * Therefore, the maximum length of the LSP Entries TLV is
+ * 16 * 15 + 2 (header) = 242 bytes.
+ */
+#define MAX_LSP_ENTRIES_TLV_SIZE 242
+
+#define L1_COMPLETE_SEQ_NUM 24
+#define L2_COMPLETE_SEQ_NUM 25
+/*
+ * L1 and L2 IS to IS complete sequence numbers PDU header
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * + PDU Length + 2
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * + Source ID + id_len + 1
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * + Start LSP ID + id_len + 2
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * + End LSP ID + id_len + 2
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ */
+struct isis_complete_seqnum_hdr {
+ uint16_t pdu_len;
+ uint8_t source_id[ISIS_SYS_ID_LEN + 1];
+ uint8_t start_lsp_id[ISIS_SYS_ID_LEN + 2];
+ uint8_t stop_lsp_id[ISIS_SYS_ID_LEN + 2];
+};
+#define ISIS_CSNP_HDRLEN 25
+
+#define L1_PARTIAL_SEQ_NUM 26
+#define L2_PARTIAL_SEQ_NUM 27
+/*
+ * L1 and L2 IS to IS partial sequence numbers PDU header
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * + PDU Length + 2
+ * +-------+-------+-------+-------+-------+-------+-------+-------+
+ * + Source ID + id_len + 1
+ * +---------------------------------------------------------------+
+ */
+struct isis_partial_seqnum_hdr {
+ uint16_t pdu_len;
+ uint8_t source_id[ISIS_SYS_ID_LEN + 1];
+};
+#define ISIS_PSNP_HDRLEN 9
+
+#ifdef __SUNPRO_C
+#pragma pack()
+#endif
+
+/*
+ * Function for receiving IS-IS PDUs
+ */
+void isis_receive(struct event *thread);
+
+/*
+ * calling arguments for snp_process ()
+ */
+#define ISIS_SNP_PSNP_FLAG 0
+#define ISIS_SNP_CSNP_FLAG 1
+
+#define ISIS_AUTH_MD5_SIZE 16U
+
+/*
+ * Sending functions
+ */
+void send_hello_sched(struct isis_circuit *circuit, int level, long delay);
+int send_csnp(struct isis_circuit *circuit, int level);
+void send_l1_csnp(struct event *thread);
+void send_l2_csnp(struct event *thread);
+void send_l1_psnp(struct event *thread);
+void send_l2_psnp(struct event *thread);
+void send_lsp(struct isis_circuit *circuit,
+ struct isis_lsp *lsp, enum isis_tx_type tx_type);
+void fill_fixed_hdr(uint8_t pdu_type, struct stream *stream);
+int send_hello(struct isis_circuit *circuit, int level);
+int isis_handle_pdu(struct isis_circuit *circuit, uint8_t *ssnpa);
+void isis_log_pdu_drops(struct isis_area *area, const char *pdu_type);
+
+#endif /* _ZEBRA_ISIS_PDU_H */
diff --git a/isisd/isis_pdu_counter.c b/isisd/isis_pdu_counter.c
new file mode 100644
index 0000000..a3605a3
--- /dev/null
+++ b/isisd/isis_pdu_counter.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Routing protocol - isis_pdu_counter.c
+ * Copyright (C) 2018 Christian Franke, for NetDEF Inc.
+ */
+
+#include <zebra.h>
+
+#include "vty.h"
+
+#include "isisd/isisd.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_pdu_counter.h"
+
+static int pdu_type_to_counter_index(uint8_t pdu_type)
+{
+ switch (pdu_type) {
+ case L1_LAN_HELLO:
+ return L1_LAN_HELLO_INDEX;
+ case L2_LAN_HELLO:
+ return L2_LAN_HELLO_INDEX;
+ case P2P_HELLO:
+ return P2P_HELLO_INDEX;
+ case L1_LINK_STATE:
+ return L1_LINK_STATE_INDEX;
+ case L2_LINK_STATE:
+ return L2_LINK_STATE_INDEX;
+ case FS_LINK_STATE:
+ return FS_LINK_STATE_INDEX;
+ case L1_COMPLETE_SEQ_NUM:
+ return L1_COMPLETE_SEQ_NUM_INDEX;
+ case L2_COMPLETE_SEQ_NUM:
+ return L2_COMPLETE_SEQ_NUM_INDEX;
+ case L1_PARTIAL_SEQ_NUM:
+ return L1_PARTIAL_SEQ_NUM_INDEX;
+ case L2_PARTIAL_SEQ_NUM:
+ return L2_PARTIAL_SEQ_NUM_INDEX;
+ default:
+ return -1;
+ }
+}
+
+static const char *pdu_counter_index_to_name(enum pdu_counter_index index)
+{
+ switch (index) {
+ case L1_LAN_HELLO_INDEX:
+ return " L1 IIH";
+ case L2_LAN_HELLO_INDEX:
+ return " L2 IIH";
+ case P2P_HELLO_INDEX:
+ return "P2P IIH";
+ case L1_LINK_STATE_INDEX:
+ return " L1 LSP";
+ case L2_LINK_STATE_INDEX:
+ return " L2 LSP";
+ case FS_LINK_STATE_INDEX:
+ return " FS LSP";
+ case L1_COMPLETE_SEQ_NUM_INDEX:
+ return "L1 CSNP";
+ case L2_COMPLETE_SEQ_NUM_INDEX:
+ return "L2 CSNP";
+ case L1_PARTIAL_SEQ_NUM_INDEX:
+ return "L1 PSNP";
+ case L2_PARTIAL_SEQ_NUM_INDEX:
+ return "L2 PSNP";
+ case PDU_COUNTER_SIZE:
+ return "???????";
+ }
+
+ assert(!"Reached end of function where we were not expecting to");
+}
+
+void pdu_counter_count(pdu_counter_t counter, uint8_t pdu_type)
+{
+ int index = pdu_type_to_counter_index(pdu_type);
+
+ if (index < 0)
+ return;
+
+ counter[index]++;
+}
+
+void pdu_counter_print(struct vty *vty, const char *prefix,
+ pdu_counter_t counter)
+{
+ for (int i = 0; i < PDU_COUNTER_SIZE; i++) {
+ if (!counter[i])
+ continue;
+ vty_out(vty, "%s%s: %" PRIu64 "\n", prefix,
+ pdu_counter_index_to_name(i), counter[i]);
+ }
+}
+
+void pdu_counter_count_drop(struct isis_area *area, uint8_t pdu_type)
+{
+ pdu_counter_count(area->pdu_drop_counters, pdu_type);
+
+ if (area->log_pdu_drops) {
+ isis_log_pdu_drops(
+ area, pdu_counter_index_to_name(
+ pdu_type_to_counter_index(pdu_type)));
+ }
+}
+
+uint64_t pdu_counter_get_count(pdu_counter_t counter, uint8_t pdu_type)
+{
+ int index = pdu_type_to_counter_index(pdu_type);
+
+ if (index < 0)
+ return -1;
+ return counter[index];
+}
diff --git a/isisd/isis_pdu_counter.h b/isisd/isis_pdu_counter.h
new file mode 100644
index 0000000..5c35b4f
--- /dev/null
+++ b/isisd/isis_pdu_counter.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Routing protocol - isis_pdu_counter.c
+ * Copyright (C) 2018 Christian Franke, for NetDEF Inc.
+ */
+#ifndef ISIS_PDU_COUNTER_H
+#define ISIS_PDU_COUNTER_H
+
+enum pdu_counter_index {
+ L1_LAN_HELLO_INDEX = 0,
+ L2_LAN_HELLO_INDEX,
+ P2P_HELLO_INDEX,
+ L1_LINK_STATE_INDEX,
+ L2_LINK_STATE_INDEX,
+ FS_LINK_STATE_INDEX,
+ L1_COMPLETE_SEQ_NUM_INDEX,
+ L2_COMPLETE_SEQ_NUM_INDEX,
+ L1_PARTIAL_SEQ_NUM_INDEX,
+ L2_PARTIAL_SEQ_NUM_INDEX,
+ PDU_COUNTER_SIZE
+};
+typedef uint64_t pdu_counter_t[PDU_COUNTER_SIZE];
+
+void pdu_counter_print(struct vty *vty, const char *prefix,
+ pdu_counter_t counter);
+void pdu_counter_count(pdu_counter_t counter, uint8_t pdu_type);
+void pdu_counter_count_drop(struct isis_area *area, uint8_t pdu_type);
+uint64_t pdu_counter_get_count(pdu_counter_t counter, uint8_t pdu_type);
+
+#endif
diff --git a/isisd/isis_pfpacket.c b/isisd/isis_pfpacket.c
new file mode 100644
index 0000000..af69fac
--- /dev/null
+++ b/isisd/isis_pfpacket.c
@@ -0,0 +1,424 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_pfpacket.c
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+#if ISIS_METHOD == ISIS_METHOD_PFPACKET
+#include <net/ethernet.h> /* the L2 protocols */
+#include <netpacket/packet.h>
+
+#include <linux/filter.h>
+
+#include "log.h"
+#include "network.h"
+#include "stream.h"
+#include "if.h"
+#include "lib_errors.h"
+#include "vrf.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_network.h"
+
+#include "privs.h"
+
+/* tcpdump -i eth0 'isis' -dd */
+static const struct sock_filter isisfilter[] = {
+ /* NB: we're in SOCK_DGRAM, so src/dst mac + length are stripped
+ * off! */
+ /* The following BPF filter accepts IS-IS over LLC and IS-IS over
+ * ethertype 0x00fe.
+ * BPF assembly:
+ * l0: ldh [0]
+ * l1: jeq #0xfefe, l2, l4
+ * l2: ldb [3]
+ * l3: jmp l7
+ * l4: ldh proto
+ * l5: jeq #0x00fe, l6, l9
+ * l6: ldb [0]
+ * l7: jeq #0x83, l8, l9
+ * l8: ret #0x40000
+ * l9: ret #0 */
+ {0x28, 0, 0, 0000000000}, {0x15, 0, 2, 0x0000fefe},
+ {0x30, 0, 0, 0x00000003}, {0x05, 0, 0, 0x00000003},
+ {0x28, 0, 0, 0xfffff000}, {0x15, 0, 3, 0x000000fe},
+ {0x30, 0, 0, 0000000000}, {0x15, 0, 1, 0x00000083},
+ {0x06, 0, 0, 0x00040000}, {0x06, 0, 0, 0000000000},
+};
+
+static const struct sock_fprog bpf = {
+ .len = array_size(isisfilter),
+ .filter = (struct sock_filter *)isisfilter,
+};
+
+/*
+ * Table 9 - Architectural constants for use with ISO 8802 subnetworks
+ * ISO 10589 - 8.4.8
+ */
+
+static const uint8_t ALL_L1_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x14};
+static const uint8_t ALL_L2_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x15};
+static const uint8_t ALL_ISS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x05};
+static const uint8_t ALL_ESS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x04};
+
+static uint8_t discard_buff[8192];
+
+/*
+ * if level is 0 we are joining p2p multicast
+ * FIXME: and the p2p multicast being ???
+ */
+static int isis_multicast_join(int fd, int registerto, int if_num)
+{
+ struct packet_mreq mreq;
+
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.mr_ifindex = if_num;
+ if (registerto) {
+ mreq.mr_type = PACKET_MR_MULTICAST;
+ mreq.mr_alen = ETH_ALEN;
+ if (registerto == 1)
+ memcpy(&mreq.mr_address, ALL_L1_ISS, ETH_ALEN);
+ else if (registerto == 2)
+ memcpy(&mreq.mr_address, ALL_L2_ISS, ETH_ALEN);
+ else if (registerto == 3)
+ memcpy(&mreq.mr_address, ALL_ISS, ETH_ALEN);
+ else
+ memcpy(&mreq.mr_address, ALL_ESS, ETH_ALEN);
+
+ } else {
+ mreq.mr_type = PACKET_MR_ALLMULTI;
+ }
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_EVENTS)
+ zlog_debug(
+ "%s: fd=%d, reg_to=%d, if_num=%d, address = %02x:%02x:%02x:%02x:%02x:%02x",
+ __func__, fd, registerto, if_num, mreq.mr_address[0],
+ mreq.mr_address[1], mreq.mr_address[2],
+ mreq.mr_address[3], mreq.mr_address[4],
+ mreq.mr_address[5]);
+#endif /* EXTREME_DEBUG */
+ if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq,
+ sizeof(struct packet_mreq))) {
+ zlog_warn("%s: setsockopt(): %s", __func__,
+ safe_strerror(errno));
+ return ISIS_WARNING;
+ }
+
+ return ISIS_OK;
+}
+
+static int open_packet_socket(struct isis_circuit *circuit)
+{
+ struct sockaddr_ll s_addr;
+ int fd, retval = ISIS_OK;
+ struct vrf *vrf = NULL;
+
+ vrf = circuit->interface->vrf;
+
+ fd = vrf_socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL), vrf->vrf_id,
+ vrf->name);
+
+ if (fd < 0) {
+ zlog_warn("%s: socket() failed %s", __func__,
+ safe_strerror(errno));
+ return ISIS_WARNING;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf))) {
+ zlog_warn("%s: SO_ATTACH_FILTER failed: %s", __func__,
+ safe_strerror(errno));
+ }
+
+ /*
+ * Bind to the physical interface
+ */
+ memset(&s_addr, 0, sizeof(s_addr));
+ s_addr.sll_family = AF_PACKET;
+ s_addr.sll_protocol = htons(ETH_P_ALL);
+ s_addr.sll_ifindex = circuit->interface->ifindex;
+
+ if (bind(fd, (struct sockaddr *)(&s_addr), sizeof(struct sockaddr_ll))
+ < 0) {
+ zlog_warn("%s: bind() failed: %s", __func__,
+ safe_strerror(errno));
+ close(fd);
+ return ISIS_WARNING;
+ }
+
+ circuit->fd = fd;
+
+ if (if_is_broadcast(circuit->interface)) {
+ /*
+ * Join to multicast groups
+ * according to
+ * 8.4.2 - Broadcast subnetwork IIH PDUs
+ * FIXME: is there a case only one will fail??
+ */
+ /* joining ALL_L1_ISS */
+ retval |= isis_multicast_join(circuit->fd, 1,
+ circuit->interface->ifindex);
+ /* joining ALL_L2_ISS */
+ retval |= isis_multicast_join(circuit->fd, 2,
+ circuit->interface->ifindex);
+ /* joining ALL_ISS (used in RFC 5309 p2p-over-lan as well) */
+ retval |= isis_multicast_join(circuit->fd, 3,
+ circuit->interface->ifindex);
+ } else {
+ retval = isis_multicast_join(circuit->fd, 0,
+ circuit->interface->ifindex);
+ }
+
+ return retval;
+}
+
+/*
+ * Create the socket and set the tx/rx funcs
+ */
+int isis_sock_init(struct isis_circuit *circuit)
+{
+ int retval = ISIS_OK;
+
+ frr_with_privs(&isisd_privs) {
+
+ retval = open_packet_socket(circuit);
+
+ if (retval != ISIS_OK) {
+ zlog_warn("%s: could not initialize the socket",
+ __func__);
+ break;
+ }
+
+ /* Assign Rx and Tx callbacks are based on real if type */
+ if (if_is_broadcast(circuit->interface)) {
+ circuit->tx = isis_send_pdu_bcast;
+ circuit->rx = isis_recv_pdu_bcast;
+ } else if (if_is_pointopoint(circuit->interface)) {
+ circuit->tx = isis_send_pdu_p2p;
+ circuit->rx = isis_recv_pdu_p2p;
+ } else {
+ zlog_warn("%s: unknown circuit type", __func__);
+ retval = ISIS_WARNING;
+ break;
+ }
+ }
+
+ return retval;
+}
+
+static inline int llc_check(uint8_t *llc)
+{
+ if (*llc != ISO_SAP || *(llc + 1) != ISO_SAP || *(llc + 2) != 3)
+ return 0;
+
+ return 1;
+}
+
+int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa)
+{
+ int bytesread, addr_len;
+ struct sockaddr_ll s_addr;
+ uint8_t llc[LLC_LEN];
+
+ addr_len = sizeof(s_addr);
+
+ memset(&s_addr, 0, sizeof(s_addr));
+
+ bytesread =
+ recvfrom(circuit->fd, (void *)&llc, LLC_LEN, MSG_PEEK,
+ (struct sockaddr *)&s_addr, (socklen_t *)&addr_len);
+
+ if ((bytesread < 0)
+ || (s_addr.sll_ifindex != (int)circuit->interface->ifindex)) {
+ if (bytesread < 0) {
+ zlog_warn(
+ "%s: ifname %s, fd %d, bytesread %d, recvfrom(): %s",
+ __func__, circuit->interface->name, circuit->fd,
+ bytesread, safe_strerror(errno));
+ }
+ if (s_addr.sll_ifindex != (int)circuit->interface->ifindex) {
+ zlog_warn(
+ "packet is received on multiple interfaces: socket interface %d, circuit interface %d, packet type %u",
+ s_addr.sll_ifindex, circuit->interface->ifindex,
+ s_addr.sll_pkttype);
+ }
+
+ /* get rid of the packet */
+ bytesread = recvfrom(circuit->fd, discard_buff,
+ sizeof(discard_buff), MSG_DONTWAIT,
+ (struct sockaddr *)&s_addr,
+ (socklen_t *)&addr_len);
+
+ if (bytesread < 0)
+ zlog_warn("%s: recvfrom() failed", __func__);
+
+ return ISIS_WARNING;
+ }
+ /*
+ * Filtering by llc field, discard packets sent by this host (other
+ * circuit)
+ */
+ if (!llc_check(llc) || s_addr.sll_pkttype == PACKET_OUTGOING) {
+ /* Read the packet into discard buff */
+ bytesread = recvfrom(circuit->fd, discard_buff,
+ sizeof(discard_buff), MSG_DONTWAIT,
+ (struct sockaddr *)&s_addr,
+ (socklen_t *)&addr_len);
+ if (bytesread < 0)
+ zlog_warn("%s: recvfrom() failed", __func__);
+ return ISIS_WARNING;
+ }
+
+ /* Ensure that we have enough space for a pdu padded to fill the mtu */
+ unsigned int max_size =
+ circuit->interface->mtu > circuit->interface->mtu6
+ ? circuit->interface->mtu
+ : circuit->interface->mtu6;
+ uint8_t temp_buff[max_size];
+ bytesread =
+ recvfrom(circuit->fd, temp_buff, max_size, MSG_DONTWAIT,
+ (struct sockaddr *)&s_addr, (socklen_t *)&addr_len);
+ if (bytesread < 0) {
+ zlog_warn("%s: recvfrom() failed", __func__);
+ return ISIS_WARNING;
+ }
+ /* then we lose the LLC */
+ stream_write(circuit->rcv_stream, temp_buff + LLC_LEN,
+ bytesread - LLC_LEN);
+ memcpy(ssnpa, &s_addr.sll_addr, s_addr.sll_halen);
+
+ return ISIS_OK;
+}
+
+int isis_recv_pdu_p2p(struct isis_circuit *circuit, uint8_t *ssnpa)
+{
+ int bytesread, addr_len;
+ struct sockaddr_ll s_addr;
+
+ memset(&s_addr, 0, sizeof(s_addr));
+ addr_len = sizeof(s_addr);
+
+ /* we can read directly to the stream */
+ (void)stream_recvfrom(
+ circuit->rcv_stream, circuit->fd, circuit->interface->mtu, 0,
+ (struct sockaddr *)&s_addr, (socklen_t *)&addr_len);
+
+ if (s_addr.sll_pkttype == PACKET_OUTGOING) {
+ /* Read the packet into discard buff */
+ bytesread = recvfrom(circuit->fd, discard_buff,
+ sizeof(discard_buff), MSG_DONTWAIT,
+ (struct sockaddr *)&s_addr,
+ (socklen_t *)&addr_len);
+ if (bytesread < 0)
+ zlog_warn("%s: recvfrom() failed", __func__);
+ return ISIS_WARNING;
+ }
+
+ /* If we don't have protocol type 0x00FE which is
+ * ISO over GRE we exit with pain :)
+ */
+ if (ntohs(s_addr.sll_protocol) != 0x00FE) {
+ zlog_warn("%s: protocol mismatch(): %X", __func__,
+ ntohs(s_addr.sll_protocol));
+ return ISIS_WARNING;
+ }
+
+ memcpy(ssnpa, &s_addr.sll_addr, s_addr.sll_halen);
+
+ return ISIS_OK;
+}
+
+int isis_send_pdu_bcast(struct isis_circuit *circuit, int level)
+{
+ struct msghdr msg;
+ struct iovec iov[2];
+ char temp_buff[LLC_LEN];
+
+ /* we need to do the LLC in here because of P2P circuits, which will
+ * not need it
+ */
+ struct sockaddr_ll sa;
+
+ stream_set_getp(circuit->snd_stream, 0);
+ memset(&sa, 0, sizeof(sa));
+ sa.sll_family = AF_PACKET;
+
+ size_t frame_size = stream_get_endp(circuit->snd_stream) + LLC_LEN;
+ sa.sll_protocol = htons(isis_ethertype(frame_size));
+ sa.sll_ifindex = circuit->interface->ifindex;
+ sa.sll_halen = ETH_ALEN;
+ /* RFC5309 section 4.1 recommends ALL_ISS */
+ if (circuit->circ_type == CIRCUIT_T_P2P)
+ memcpy(&sa.sll_addr, ALL_ISS, ETH_ALEN);
+ else if (level == 1)
+ memcpy(&sa.sll_addr, ALL_L1_ISS, ETH_ALEN);
+ else
+ memcpy(&sa.sll_addr, ALL_L2_ISS, ETH_ALEN);
+
+ /* on a broadcast circuit */
+ /* first we put the LLC in */
+ temp_buff[0] = 0xFE;
+ temp_buff[1] = 0xFE;
+ temp_buff[2] = 0x03;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = &sa;
+ msg.msg_namelen = sizeof(struct sockaddr_ll);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 2;
+ iov[0].iov_base = temp_buff;
+ iov[0].iov_len = LLC_LEN;
+ iov[1].iov_base = circuit->snd_stream->data;
+ iov[1].iov_len = stream_get_endp(circuit->snd_stream);
+
+ if (sendmsg(circuit->fd, &msg, 0) < 0) {
+ zlog_warn("IS-IS pfpacket: could not transmit packet on %s: %s",
+ circuit->interface->name, safe_strerror(errno));
+ if (ERRNO_IO_RETRY(errno))
+ return ISIS_WARNING;
+ return ISIS_ERROR;
+ }
+ return ISIS_OK;
+}
+
+int isis_send_pdu_p2p(struct isis_circuit *circuit, int level)
+{
+ struct sockaddr_ll sa;
+ ssize_t rv;
+
+ stream_set_getp(circuit->snd_stream, 0);
+ memset(&sa, 0, sizeof(sa));
+ sa.sll_family = AF_PACKET;
+ sa.sll_ifindex = circuit->interface->ifindex;
+ sa.sll_halen = ETH_ALEN;
+ if (level == 1)
+ memcpy(&sa.sll_addr, ALL_L1_ISS, ETH_ALEN);
+ else
+ memcpy(&sa.sll_addr, ALL_L2_ISS, ETH_ALEN);
+
+
+ /* lets try correcting the protocol */
+ sa.sll_protocol = htons(0x00FE);
+ rv = sendto(circuit->fd, circuit->snd_stream->data,
+ stream_get_endp(circuit->snd_stream), 0,
+ (struct sockaddr *)&sa, sizeof(struct sockaddr_ll));
+ if (rv < 0) {
+ zlog_warn("IS-IS pfpacket: could not transmit packet on %s: %s",
+ circuit->interface->name, safe_strerror(errno));
+ if (ERRNO_IO_RETRY(errno))
+ return ISIS_WARNING;
+ return ISIS_ERROR;
+ }
+ return ISIS_OK;
+}
+
+#endif /* ISIS_METHOD == ISIS_METHOD_PFPACKET */
diff --git a/isisd/isis_redist.c b/isisd/isis_redist.c
new file mode 100644
index 0000000..2cb08db
--- /dev/null
+++ b/isisd/isis_redist.c
@@ -0,0 +1,847 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_redist.c
+ *
+ * Copyright (C) 2013-2015 Christian Franke <chris@opensourcerouting.org>
+ */
+
+#include <zebra.h>
+
+#include "command.h"
+#include "if.h"
+#include "linklist.h"
+#include "memory.h"
+#include "prefix.h"
+#include "routemap.h"
+#include "stream.h"
+#include "table.h"
+#include "vty.h"
+#include "srcdest_table.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_route.h"
+#include "isisd/isis_zebra.h"
+
+DEFINE_MTYPE_STATIC(ISISD, ISIS_EXT_ROUTE, "ISIS redistributed route");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_EXT_INFO, "ISIS redistributed route info");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_RMAP_NAME, "ISIS redistribute route-map name");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_REDISTRIBUTE, "ISIS redistribute");
+
+static int redist_protocol(int family)
+{
+ if (family == AF_INET)
+ return 0;
+ if (family == AF_INET6)
+ return 1;
+
+ assert(!"Unsupported address family!");
+ return 0;
+}
+
+afi_t afi_for_redist_protocol(int protocol)
+{
+ if (protocol == 0)
+ return AFI_IP;
+ if (protocol == 1)
+ return AFI_IP6;
+
+ assert(!"Unknown redist protocol!");
+ return AFI_IP;
+}
+
+static struct route_table *get_ext_info(struct isis *i, int family)
+{
+ int protocol = redist_protocol(family);
+
+ return i->ext_info[protocol];
+}
+
+static struct isis_redist *isis_redist_lookup(struct isis_area *area,
+ int family, int type, int level,
+ uint16_t table)
+{
+ int protocol = redist_protocol(family);
+ struct listnode *node;
+ struct isis_redist *red;
+
+ if (area->redist_settings[protocol][type][level - 1]) {
+ for (ALL_LIST_ELEMENTS_RO(area->redist_settings[protocol][type]
+ [level - 1],
+ node, red))
+ if (red->table == table)
+ return red;
+ }
+ return NULL;
+}
+
+static struct isis_redist *isis_redist_get(struct isis_area *area, int family,
+ int type, int level, uint16_t table)
+{
+ struct isis_redist *red;
+ int protocol;
+
+ red = isis_redist_lookup(area, family, type, level, table);
+ if (red)
+ return red;
+
+ protocol = redist_protocol(family);
+ if (area->redist_settings[protocol][type][level - 1] == NULL)
+ area->redist_settings[protocol][type][level - 1] = list_new();
+
+ red = XCALLOC(MTYPE_ISIS_REDISTRIBUTE, sizeof(struct isis_redist));
+ red->table = table;
+
+ listnode_add(area->redist_settings[protocol][type][level - 1], red);
+ return red;
+}
+
+struct route_table *get_ext_reach(struct isis_area *area, int family, int level)
+{
+ int protocol = redist_protocol(family);
+
+ return area->ext_reach[protocol][level - 1];
+}
+
+/* Install external reachability information into a
+ * specific area for a specific level.
+ * Schedule an lsp regenerate if necessary */
+static void isis_redist_install(struct isis_area *area, int level,
+ const struct prefix *p,
+ const struct prefix_ipv6 *src_p,
+ struct isis_ext_info *info)
+{
+ int family = p->family;
+ struct route_table *er_table = get_ext_reach(area, family, level);
+ struct route_node *er_node;
+
+ if (!er_table) {
+ zlog_warn(
+ "%s: External reachability table of area %s is not initialized.",
+ __func__, area->area_tag);
+ return;
+ }
+
+ er_node = srcdest_rnode_get(er_table, p, src_p);
+ if (er_node->info) {
+ route_unlock_node(er_node);
+
+ /* Don't update/reschedule lsp generation if nothing changed. */
+ if (!memcmp(er_node->info, info, sizeof(*info)))
+ return;
+ } else {
+ er_node->info = XMALLOC(MTYPE_ISIS_EXT_INFO, sizeof(*info));
+ }
+
+ memcpy(er_node->info, info, sizeof(*info));
+ lsp_regenerate_schedule(area, level, 0);
+}
+
+/* Remove external reachability information from a
+ * specific area for a specific level.
+ * Schedule an lsp regenerate if necessary. */
+static void isis_redist_uninstall(struct isis_area *area, int level,
+ const struct prefix *p,
+ const struct prefix_ipv6 *src_p)
+{
+ int family = p->family;
+ struct route_table *er_table = get_ext_reach(area, family, level);
+ struct route_node *er_node;
+
+ if (!er_table) {
+ zlog_warn(
+ "%s: External reachability table of area %s is not initialized.",
+ __func__, area->area_tag);
+ return;
+ }
+
+ er_node = srcdest_rnode_lookup(er_table, p, src_p);
+ if (!er_node)
+ return;
+ else
+ route_unlock_node(er_node);
+
+ if (!er_node->info)
+ return;
+
+ XFREE(MTYPE_ISIS_EXT_INFO, er_node->info);
+ route_unlock_node(er_node);
+ lsp_regenerate_schedule(area, level, 0);
+}
+
+/* Update external reachability info of area for a given level
+ * and prefix, using the given redistribution settings. */
+static void isis_redist_update_ext_reach(struct isis_area *area, int level,
+ struct isis_redist *redist,
+ const struct prefix *p,
+ const struct prefix_ipv6 *src_p,
+ struct isis_ext_info *info)
+{
+ struct isis_ext_info area_info;
+ route_map_result_t map_ret;
+
+ memcpy(&area_info, info, sizeof(area_info));
+ area_info.metric = redist->metric;
+
+ if (redist->map_name) {
+ map_ret = route_map_apply(redist->map, p, &area_info);
+ if (map_ret == RMAP_DENYMATCH)
+ area_info.distance = 255;
+ }
+
+ /* Allow synthesized default routes only on always orignate */
+ if (area_info.origin == DEFAULT_ROUTE
+ && redist->redist != DEFAULT_ORIGINATE_ALWAYS)
+ area_info.distance = 255;
+
+ if (area_info.distance < 255)
+ isis_redist_install(area, level, p, src_p, &area_info);
+ else
+ isis_redist_uninstall(area, level, p, src_p);
+}
+
+static void isis_redist_ensure_default(struct isis *isis, int family)
+{
+ struct prefix p;
+ struct route_table *ei_table = get_ext_info(isis, family);
+ struct route_node *ei_node;
+ struct isis_ext_info *info;
+
+ if (family == AF_INET) {
+ p.family = AF_INET;
+ p.prefixlen = 0;
+ memset(&p.u.prefix4, 0, sizeof(p.u.prefix4));
+ } else if (family == AF_INET6) {
+ p.family = AF_INET6;
+ p.prefixlen = 0;
+ memset(&p.u.prefix6, 0, sizeof(p.u.prefix6));
+ } else
+ assert(!"Unknown family!");
+
+ ei_node = srcdest_rnode_get(ei_table, &p, NULL);
+ if (ei_node->info) {
+ route_unlock_node(ei_node);
+ return;
+ }
+
+ ei_node->info =
+ XCALLOC(MTYPE_ISIS_EXT_INFO, sizeof(struct isis_ext_info));
+
+ info = ei_node->info;
+ info->origin = DEFAULT_ROUTE;
+ info->distance = 254;
+ info->metric = MAX_WIDE_PATH_METRIC;
+}
+
+static int _isis_redist_table_is_present(const struct lyd_node *dnode, void *arg)
+{
+ struct isis_redist_table_present_args *rtda = arg;
+
+ /* This entry is the caller, so skip it. */
+ if (yang_dnode_get_uint16(dnode, "table") !=
+ (uint16_t)atoi(rtda->rtda_table))
+ return YANG_ITER_CONTINUE;
+
+ /* found */
+ rtda->rtda_found = true;
+ return YANG_ITER_CONTINUE;
+}
+
+static int _isis_redist_table_get_first_cb(const struct lyd_node *dnode,
+ void *arg)
+{
+ uint16_t *table = arg;
+
+ *table = yang_dnode_get_uint16(dnode, "table");
+ return YANG_ITER_STOP;
+}
+
+uint16_t isis_redist_table_get_first(const struct vty *vty,
+ struct isis_redist_table_present_args *rtda)
+{
+ uint16_t table = 0;
+
+ yang_dnode_iterate(_isis_redist_table_get_first_cb, &table,
+ vty->candidate_config->dnode,
+ "%s/redistribute/%s[protocol='table'][level='%s']/table",
+ VTY_CURR_XPATH, rtda->rtda_ip, rtda->rtda_level);
+ return table;
+}
+
+bool isis_redist_table_is_present(const struct vty *vty,
+ struct isis_redist_table_present_args *rtda)
+{
+ rtda->rtda_found = false;
+ yang_dnode_iterate(_isis_redist_table_is_present, rtda,
+ vty->candidate_config->dnode,
+ "%s/redistribute/%s[protocol='table'][level='%s']/table",
+ VTY_CURR_XPATH, rtda->rtda_ip, rtda->rtda_level);
+
+ return rtda->rtda_found;
+}
+
+/* Handle notification about route being added */
+void isis_redist_add(struct isis *isis, int type, struct prefix *p,
+ struct prefix_ipv6 *src_p, uint8_t distance,
+ uint32_t metric, const route_tag_t tag, uint16_t table)
+{
+ int family = p->family;
+ struct route_table *ei_table = get_ext_info(isis, family);
+ struct route_node *ei_node;
+ struct isis_ext_info *info;
+ struct listnode *node;
+ struct isis_area *area;
+ int level;
+ struct isis_redist *redist;
+
+ zlog_debug("%s: New route %pFX from %s: distance %d.", __func__, p,
+ zebra_route_string(type), distance);
+
+ if (!ei_table) {
+ zlog_warn("%s: External information table not initialized.",
+ __func__);
+ return;
+ }
+
+ ei_node = srcdest_rnode_get(ei_table, p, src_p);
+ if (ei_node->info)
+ route_unlock_node(ei_node);
+ else
+ ei_node->info = XCALLOC(MTYPE_ISIS_EXT_INFO,
+ sizeof(struct isis_ext_info));
+
+ info = ei_node->info;
+ info->origin = type;
+ info->distance = distance;
+ info->metric = metric;
+ info->tag = tag;
+
+ if (is_default_prefix(p)
+ && (!src_p || !src_p->prefixlen)) {
+ type = DEFAULT_ROUTE;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area))
+ for (level = 1; level <= ISIS_LEVELS; level++) {
+ redist = isis_redist_lookup(area, family, type, level,
+ table);
+ if (!redist || !redist->redist)
+ continue;
+
+ isis_redist_update_ext_reach(area, level, redist, p,
+ src_p, info);
+ }
+}
+
+void isis_redist_delete(struct isis *isis, int type, struct prefix *p,
+ struct prefix_ipv6 *src_p, uint16_t table)
+{
+ int family = p->family;
+ struct route_table *ei_table = get_ext_info(isis, family);
+ struct route_node *ei_node;
+ struct listnode *node;
+ struct isis_area *area;
+ int level;
+ struct isis_redist *redist;
+
+ zlog_debug("%s: Removing route %pFX from %s.", __func__, p,
+ zebra_route_string(type));
+
+ if (is_default_prefix(p)
+ && (!src_p || !src_p->prefixlen)) {
+ /* Don't remove default route but add synthetic route for use
+ * by "default-information originate always". Areas without the
+ * "always" setting will ignore routes with origin
+ * DEFAULT_ROUTE. */
+ isis_redist_add(isis, DEFAULT_ROUTE, p, NULL, 254,
+ MAX_WIDE_PATH_METRIC, 0, table);
+ return;
+ }
+
+ if (!ei_table) {
+ zlog_warn("%s: External information table not initialized.",
+ __func__);
+ return;
+ }
+
+ ei_node = srcdest_rnode_lookup(ei_table, p, src_p);
+ if (!ei_node || !ei_node->info) {
+ zlog_warn(
+ "%s: Got a delete for %s route %pFX, but that route was never added.",
+ __func__, zebra_route_string(type), p);
+ if (ei_node)
+ route_unlock_node(ei_node);
+ return;
+ }
+ route_unlock_node(ei_node);
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area))
+ for (level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+ redist = isis_redist_lookup(area, family, type, level,
+ table);
+ if (!redist || !redist->redist)
+ continue;
+
+ isis_redist_uninstall(area, level, p, src_p);
+ }
+
+ XFREE(MTYPE_ISIS_EXT_INFO, ei_node->info);
+ route_unlock_node(ei_node);
+}
+
+static void isis_redist_routemap_set(struct isis_redist *redist,
+ const char *routemap)
+{
+ if (redist->map_name) {
+ XFREE(MTYPE_ISIS_RMAP_NAME, redist->map_name);
+ route_map_counter_decrement(redist->map);
+ redist->map = NULL;
+ }
+
+ if (routemap && strlen(routemap)) {
+ redist->map_name = XSTRDUP(MTYPE_ISIS_RMAP_NAME, routemap);
+ redist->map = route_map_lookup_by_name(routemap);
+ route_map_counter_increment(redist->map);
+ }
+}
+
+void isis_redist_free(struct isis *isis)
+{
+ struct route_node *rn;
+ int i;
+
+ for (i = 0; i < REDIST_PROTOCOL_COUNT; i++) {
+ if (!isis->ext_info[i])
+ continue;
+
+ for (rn = route_top(isis->ext_info[i]); rn;
+ rn = srcdest_route_next(rn)) {
+ if (rn->info)
+ XFREE(MTYPE_ISIS_EXT_INFO, rn->info);
+ }
+
+ route_table_finish(isis->ext_info[i]);
+ isis->ext_info[i] = NULL;
+ }
+}
+
+void isis_redist_set(struct isis_area *area, int level, int family, int type,
+ uint32_t metric, const char *routemap, int originate_type,
+ uint16_t table)
+{
+ int protocol = redist_protocol(family);
+ struct isis_redist *redist = isis_redist_get(area, family, type, level,
+ table);
+ int i;
+ struct route_table *ei_table;
+ struct route_node *rn;
+ struct isis_ext_info *info;
+
+ redist->redist = (type == DEFAULT_ROUTE) ? originate_type : 1;
+ redist->metric = metric;
+ isis_redist_routemap_set(redist, routemap);
+
+ if (!area->ext_reach[protocol][level - 1]) {
+ area->ext_reach[protocol][level - 1] = srcdest_table_init();
+ }
+
+ for (i = 0; i < REDIST_PROTOCOL_COUNT; i++) {
+ if (!area->isis->ext_info[i]) {
+ area->isis->ext_info[i] = srcdest_table_init();
+ }
+ }
+
+ isis_zebra_redistribute_set(afi_for_redist_protocol(protocol), type,
+ area->isis->vrf_id, redist->table);
+
+ if (type == DEFAULT_ROUTE && originate_type == DEFAULT_ORIGINATE_ALWAYS)
+ isis_redist_ensure_default(area->isis, family);
+
+ ei_table = get_ext_info(area->isis, family);
+ for (rn = route_top(ei_table); rn; rn = srcdest_route_next(rn)) {
+ if (!rn->info)
+ continue;
+ info = rn->info;
+
+ const struct prefix *p, *src_p;
+
+ srcdest_rnode_prefixes(rn, &p, &src_p);
+
+ if (type == DEFAULT_ROUTE) {
+ if (!is_default_prefix(p)
+ || (src_p && src_p->prefixlen)) {
+ continue;
+ }
+ } else {
+ if (info->origin != type)
+ continue;
+ }
+
+ isis_redist_update_ext_reach(area, level, redist, p,
+ (const struct prefix_ipv6 *)src_p,
+ info);
+ }
+}
+
+void isis_redist_unset(struct isis_area *area, int level, int family, int type,
+ uint16_t table)
+{
+ struct isis_redist *redist = isis_redist_lookup(area, family, type,
+ level, table);
+ struct route_table *er_table = get_ext_reach(area, family, level);
+ struct route_node *rn;
+ struct isis_ext_info *info;
+ struct list *redist_list;
+ int protocol = redist_protocol(family);
+
+ if (!redist || !redist->redist)
+ return;
+
+ redist->redist = 0;
+
+ redist_list = area->redist_settings[protocol][type][level - 1];
+ listnode_delete(redist_list, redist);
+ XFREE(MTYPE_ISIS_REDISTRIBUTE, redist);
+
+ if (!er_table) {
+ zlog_warn("%s: External reachability table uninitialized.",
+ __func__);
+ return;
+ }
+
+ for (rn = route_top(er_table); rn; rn = srcdest_route_next(rn)) {
+ if (!rn->info)
+ continue;
+ info = rn->info;
+
+ const struct prefix *p, *src_p;
+ srcdest_rnode_prefixes(rn, &p, &src_p);
+
+ if (type == DEFAULT_ROUTE) {
+ if (!is_default_prefix(p)
+ || (src_p && src_p->prefixlen)) {
+ continue;
+ }
+ } else {
+ if (info->origin != type)
+ continue;
+ }
+
+ XFREE(MTYPE_ISIS_EXT_INFO, rn->info);
+ route_unlock_node(rn);
+ }
+
+ lsp_regenerate_schedule(area, level, 0);
+ isis_zebra_redistribute_unset(afi_for_redist_protocol(protocol), type,
+ area->isis->vrf_id, table);
+}
+
+void isis_redist_area_finish(struct isis_area *area)
+{
+ struct route_node *rn;
+ int protocol;
+ int level;
+ int type;
+ struct isis_redist *redist;
+ struct listnode *node, *nnode;
+ struct list *redist_list;
+
+ for (protocol = 0; protocol < REDIST_PROTOCOL_COUNT; protocol++)
+ for (level = 0; level < ISIS_LEVELS; level++) {
+ for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++) {
+ redist_list = area->redist_settings[protocol]
+ [type][level];
+ if (!redist_list)
+ continue;
+ for (ALL_LIST_ELEMENTS(redist_list, node, nnode,
+ redist)) {
+ redist->redist = 0;
+ XFREE(MTYPE_ISIS_RMAP_NAME,
+ redist->map_name);
+ isis_zebra_redistribute_unset(
+ afi_for_redist_protocol(protocol),
+ type, area->isis->vrf_id,
+ redist->table);
+ listnode_delete(redist_list, redist);
+ XFREE(MTYPE_ISIS_REDISTRIBUTE, redist);
+ }
+ list_delete(&redist_list);
+ }
+ if (!area->ext_reach[protocol][level])
+ continue;
+ for (rn = route_top(area->ext_reach[protocol][level]);
+ rn; rn = srcdest_route_next(rn)) {
+ if (rn->info)
+ XFREE(MTYPE_ISIS_EXT_INFO, rn->info);
+ }
+ route_table_finish(area->ext_reach[protocol][level]);
+ area->ext_reach[protocol][level] = NULL;
+ }
+}
+
+#ifdef FABRICD
+DEFUN (isis_redistribute,
+ isis_redistribute_cmd,
+ "redistribute <ipv4 " PROTO_IP_REDIST_STR "|ipv6 " PROTO_IP6_REDIST_STR ">"
+ " [{metric (0-16777215)|route-map RMAP_NAME}]",
+ REDIST_STR
+ "Redistribute IPv4 routes\n"
+ PROTO_IP_REDIST_HELP
+ "Redistribute IPv6 routes\n"
+ PROTO_IP6_REDIST_HELP
+ "Metric for redistributed routes\n"
+ "ISIS default metric\n"
+ "Route map reference\n"
+ "Pointer to route-map entries\n")
+{
+ int idx_afi = 1;
+ int idx_protocol = 2;
+ int idx_metric_rmap = 1;
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+ int family;
+ int afi;
+ int type;
+ int level;
+ unsigned long metric = 0;
+ const char *routemap = NULL;
+
+ family = str2family(argv[idx_afi]->text);
+ if (family < 0)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ afi = family2afi(family);
+ if (!afi)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ type = proto_redistnum(afi, argv[idx_protocol]->text);
+ if (type < 0)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ level = 2;
+
+ if ((area->is_type & level) != level) {
+ vty_out(vty, "Node is not a level-%d IS\n", level);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (argv_find(argv, argc, "metric", &idx_metric_rmap)) {
+ metric = strtoul(argv[idx_metric_rmap + 1]->arg, NULL, 10);
+ }
+
+ idx_metric_rmap = 1;
+ if (argv_find(argv, argc, "route-map", &idx_metric_rmap)) {
+ routemap = argv[idx_metric_rmap + 1]->arg;
+ }
+
+ isis_redist_set(area, level, family, type, metric, routemap, 0, 0);
+ return 0;
+}
+
+DEFUN (no_isis_redistribute,
+ no_isis_redistribute_cmd,
+ "no redistribute <ipv4 " PROTO_IP_REDIST_STR "|ipv6 " PROTO_IP6_REDIST_STR ">",
+ NO_STR
+ REDIST_STR
+ "Redistribute IPv4 routes\n"
+ PROTO_IP_REDIST_HELP
+ "Redistribute IPv6 routes\n"
+ PROTO_IP6_REDIST_HELP)
+{
+ int idx_afi = 2;
+ int idx_protocol = 3;
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+ int type;
+ int level;
+ int family;
+ int afi;
+
+ family = str2family(argv[idx_afi]->arg);
+ if (family < 0)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ afi = family2afi(family);
+ if (!afi)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ type = proto_redistnum(afi, argv[idx_protocol]->text);
+ if (type < 0)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ level = 2;
+
+ isis_redist_unset(area, level, family, type, 0);
+ return 0;
+}
+
+DEFUN (isis_default_originate,
+ isis_default_originate_cmd,
+ "default-information originate <ipv4|ipv6> [always] [{metric (0-16777215)|route-map RMAP_NAME}]",
+ "Control distribution of default information\n"
+ "Distribute a default route\n"
+ "Distribute default route for IPv4\n"
+ "Distribute default route for IPv6\n"
+ "Always advertise default route\n"
+ "Metric for default route\n"
+ "ISIS default metric\n"
+ "Route map reference\n"
+ "Pointer to route-map entries\n")
+{
+ int idx_afi = 2;
+ int idx_always = fabricd ? 3 : 4;
+ int idx_metric_rmap = 1;
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+ int family;
+ int originate_type = DEFAULT_ORIGINATE;
+ int level;
+ unsigned long metric = 0;
+ const char *routemap = NULL;
+
+ family = str2family(argv[idx_afi]->text);
+ if (family < 0)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ level = 2;
+
+ if ((area->is_type & level) != level) {
+ vty_out(vty, "Node is not a level-%d IS\n", level);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (argc > idx_always && strmatch(argv[idx_always]->text, "always")) {
+ originate_type = DEFAULT_ORIGINATE_ALWAYS;
+ idx_metric_rmap++;
+ }
+
+ if (argv_find(argv, argc, "metric", &idx_metric_rmap)) {
+ metric = strtoul(argv[idx_metric_rmap + 1]->arg, NULL, 10);
+ }
+
+ idx_metric_rmap = 1;
+ if (argv_find(argv, argc, "route-map", &idx_metric_rmap)) {
+ routemap = argv[idx_metric_rmap + 1]->arg;
+ }
+
+ if (family == AF_INET6 && originate_type != DEFAULT_ORIGINATE_ALWAYS) {
+ vty_out(vty,
+ "Zebra doesn't implement default-originate for IPv6 yet\n");
+ vty_out(vty,
+ "so use with care or use default-originate always.\n");
+ }
+
+ isis_redist_set(area, level, family, DEFAULT_ROUTE, metric, routemap,
+ originate_type, 0);
+ return 0;
+}
+
+DEFUN (no_isis_default_originate,
+ no_isis_default_originate_cmd,
+ "no default-information originate <ipv4|ipv6>",
+ NO_STR
+ "Control distribution of default information\n"
+ "Distribute a default route\n"
+ "Distribute default route for IPv4\n"
+ "Distribute default route for IPv6\n")
+{
+ int idx_afi = 3;
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+ int family;
+ int level;
+
+ family = str2family(argv[idx_afi]->text);
+ if (family < 0)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ level = 2;
+
+ isis_redist_unset(area, level, family, DEFAULT_ROUTE, 0);
+ return 0;
+}
+#endif /* ifdef FABRICD */
+
+int isis_redist_config_write(struct vty *vty, struct isis_area *area,
+ int family)
+{
+ int type;
+ int level;
+ int write = 0;
+ struct isis_redist *redist;
+ struct list *redist_list;
+ const char *family_str;
+ struct listnode *node;
+
+ if (family == AF_INET)
+ family_str = "ipv4";
+ else if (family == AF_INET6)
+ family_str = "ipv6";
+ else
+ return 0;
+
+ for (type = 0; type < ZEBRA_ROUTE_MAX; type++) {
+ if (type == PROTO_TYPE)
+ continue;
+
+ for (level = 1; level <= ISIS_LEVELS; level++) {
+ redist_list = area->redist_settings[redist_protocol(
+ family)][type][level - 1];
+ if (!redist_list)
+ continue;
+ for (ALL_LIST_ELEMENTS_RO(redist_list, node, redist)) {
+ if (!redist->redist)
+ continue;
+ vty_out(vty, " redistribute %s %s", family_str,
+ zebra_route_string(type));
+ if (type == ZEBRA_ROUTE_TABLE)
+ vty_out(vty, " %u", redist->table);
+ if (!fabricd)
+ vty_out(vty, " level-%d", level);
+ if (redist->metric)
+ vty_out(vty, " metric %u",
+ redist->metric);
+ if (redist->map_name)
+ vty_out(vty, " route-map %s",
+ redist->map_name);
+ vty_out(vty, "\n");
+ write++;
+ }
+ }
+ }
+
+ for (level = 1; level <= ISIS_LEVELS; level++) {
+ redist = isis_redist_lookup(area, family, DEFAULT_ROUTE, level,
+ 0);
+ if (!redist)
+ continue;
+ if (!redist->redist)
+ continue;
+ vty_out(vty, " default-information originate %s",
+ family_str);
+ if (!fabricd)
+ vty_out(vty, " level-%d", level);
+ if (redist->redist == DEFAULT_ORIGINATE_ALWAYS)
+ vty_out(vty, " always");
+ if (redist->metric)
+ vty_out(vty, " metric %u", redist->metric);
+ if (redist->map_name)
+ vty_out(vty, " route-map %s", redist->map_name);
+ vty_out(vty, "\n");
+ write++;
+ }
+
+ return write;
+}
+
+void isis_redist_init(void)
+{
+#ifdef FABRICD
+ install_element(ROUTER_NODE, &isis_redistribute_cmd);
+ install_element(ROUTER_NODE, &no_isis_redistribute_cmd);
+
+ install_element(ROUTER_NODE, &isis_default_originate_cmd);
+ install_element(ROUTER_NODE, &no_isis_default_originate_cmd);
+#endif /* ifdef FABRICD */
+}
diff --git a/isisd/isis_redist.h b/isisd/isis_redist.h
new file mode 100644
index 0000000..688f27e
--- /dev/null
+++ b/isisd/isis_redist.h
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_redist.h
+ *
+ * Copyright (C) 2013-2015 Christian Franke <chris@opensourcerouting.org>
+ */
+
+#ifndef ISIS_REDIST_H
+#define ISIS_REDIST_H
+
+#define REDIST_PROTOCOL_COUNT 2
+
+#define DEFAULT_ROUTE ZEBRA_ROUTE_MAX
+#define DEFAULT_ORIGINATE 1
+#define DEFAULT_ORIGINATE_ALWAYS 2
+
+struct isis_ext_info {
+ int origin;
+ uint32_t metric;
+ uint8_t distance;
+ route_tag_t tag;
+};
+
+struct isis_redist {
+ int redist;
+ uint32_t metric;
+ char *map_name;
+ struct route_map *map;
+ uint16_t table;
+};
+
+struct isis_redist_table_present_args {
+ /* from filter.h, struct acl_dup_args */
+ const char *rtda_ip;
+ const char *rtda_level;
+ const char *rtda_table;
+ bool rtda_found;
+};
+
+struct isis;
+struct isis_area;
+struct prefix;
+struct prefix_ipv6;
+struct vty;
+
+afi_t afi_for_redist_protocol(int protocol);
+
+struct route_table *get_ext_reach(struct isis_area *area, int family,
+ int level);
+void isis_redist_add(struct isis *isis, int type, struct prefix *p,
+ struct prefix_ipv6 *src_p, uint8_t distance,
+ uint32_t metric, route_tag_t tag, uint16_t instance);
+void isis_redist_delete(struct isis *isis, int type, struct prefix *p,
+ struct prefix_ipv6 *src_p, uint16_t tableid);
+int isis_redist_config_write(struct vty *vty, struct isis_area *area,
+ int family);
+void isis_redist_init(void);
+void isis_redist_area_finish(struct isis_area *area);
+
+void isis_redist_set(struct isis_area *area, int level, int family, int type,
+ uint32_t metric, const char *routemap, int originate_type,
+ uint16_t table);
+void isis_redist_unset(struct isis_area *area, int level, int family, int type,
+ uint16_t table);
+
+void isis_redist_free(struct isis *isis);
+
+bool isis_redist_table_is_present(const struct vty *vty,
+ struct isis_redist_table_present_args *rtda);
+uint16_t isis_redist_table_get_first(const struct vty *vty,
+ struct isis_redist_table_present_args *rtda);
+#endif
diff --git a/isisd/isis_route.c b/isisd/isis_route.c
new file mode 100644
index 0000000..be92dcc
--- /dev/null
+++ b/isisd/isis_route.c
@@ -0,0 +1,975 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_route.c
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ *
+ * based on ../ospf6d/ospf6_route.[ch]
+ * by Yasuhiro Ohara
+ */
+
+#include <zebra.h>
+
+#include "frrevent.h"
+#include "linklist.h"
+#include "vty.h"
+#include "log.h"
+#include "lib_errors.h"
+#include "memory.h"
+#include "prefix.h"
+#include "hash.h"
+#include "if.h"
+#include "table.h"
+#include "srcdest_table.h"
+
+#include "isis_constants.h"
+#include "isis_common.h"
+#include "isis_flags.h"
+#include "isisd.h"
+#include "isis_misc.h"
+#include "isis_adjacency.h"
+#include "isis_circuit.h"
+#include "isis_pdu.h"
+#include "isis_lsp.h"
+#include "isis_spf.h"
+#include "isis_spf_private.h"
+#include "isis_route.h"
+#include "isis_zebra.h"
+#include "isis_flex_algo.h"
+
+DEFINE_MTYPE_STATIC(ISISD, ISIS_NEXTHOP, "ISIS nexthop");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_ROUTE_INFO, "ISIS route info");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_ROUTE_TABLE_INFO, "ISIS route table info");
+
+
+DEFINE_HOOK(isis_route_update_hook,
+ (struct isis_area * area, struct prefix *prefix,
+ struct isis_route_info *route_info),
+ (area, prefix, route_info));
+
+static struct isis_nexthop *nexthoplookup(struct list *nexthops, int family,
+ union g_addr *ip, ifindex_t ifindex);
+static void isis_route_update(struct isis_area *area, struct prefix *prefix,
+ struct prefix_ipv6 *src_p,
+ struct isis_route_info *route_info);
+
+static struct mpls_label_stack *
+label_stack_dup(const struct mpls_label_stack *const orig)
+{
+ struct mpls_label_stack *copy;
+ int array_size;
+
+ if (orig == NULL)
+ return NULL;
+
+ array_size = orig->num_labels * sizeof(mpls_label_t);
+ copy = XCALLOC(MTYPE_ISIS_NEXTHOP_LABELS,
+ sizeof(struct mpls_label_stack) + array_size);
+ copy->num_labels = orig->num_labels;
+ memcpy(copy->label, orig->label, array_size);
+ return copy;
+}
+
+static struct isis_nexthop *
+isis_nexthop_create(int family, const union g_addr *const ip, ifindex_t ifindex)
+{
+ struct isis_nexthop *nexthop;
+
+ nexthop = XCALLOC(MTYPE_ISIS_NEXTHOP, sizeof(struct isis_nexthop));
+
+ nexthop->family = family;
+ nexthop->ifindex = ifindex;
+ nexthop->ip = *ip;
+
+ return nexthop;
+}
+
+static struct isis_nexthop *
+isis_nexthop_dup(const struct isis_nexthop *const orig)
+{
+ struct isis_nexthop *nexthop;
+
+ nexthop = isis_nexthop_create(orig->family, &orig->ip, orig->ifindex);
+ memcpy(nexthop->sysid, orig->sysid, ISIS_SYS_ID_LEN);
+ nexthop->sr = orig->sr;
+ nexthop->label_stack = label_stack_dup(orig->label_stack);
+
+ return nexthop;
+}
+
+void isis_nexthop_delete(struct isis_nexthop *nexthop)
+{
+ XFREE(MTYPE_ISIS_NEXTHOP_LABELS, nexthop->label_stack);
+ XFREE(MTYPE_ISIS_NEXTHOP, nexthop);
+}
+
+static struct list *isis_nexthop_list_dup(const struct list *orig)
+{
+ struct list *copy;
+ struct listnode *node;
+ struct isis_nexthop *nh;
+ struct isis_nexthop *nhcopy;
+
+ copy = list_new();
+ for (ALL_LIST_ELEMENTS_RO(orig, node, nh)) {
+ nhcopy = isis_nexthop_dup(nh);
+ listnode_add(copy, nhcopy);
+ }
+ return copy;
+}
+
+static struct isis_nexthop *nexthoplookup(struct list *nexthops, int family,
+ union g_addr *ip, ifindex_t ifindex)
+{
+ struct listnode *node;
+ struct isis_nexthop *nh;
+
+ for (ALL_LIST_ELEMENTS_RO(nexthops, node, nh)) {
+ if (nh->ifindex != ifindex)
+ continue;
+
+ /* if the IP is unspecified, return the first nexthop found on
+ * the interface
+ */
+ if (!ip)
+ return nh;
+
+ if (nh->family != family)
+ continue;
+
+ switch (family) {
+ case AF_INET:
+ if (IPV4_ADDR_CMP(&nh->ip.ipv4, &ip->ipv4))
+ continue;
+ break;
+ case AF_INET6:
+ if (IPV6_ADDR_CMP(&nh->ip.ipv6, &ip->ipv6))
+ continue;
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unknown address family [%d]", __func__,
+ family);
+ exit(1);
+ }
+
+ return nh;
+ }
+
+ return NULL;
+}
+
+void adjinfo2nexthop(int family, struct list *nexthops,
+ struct isis_adjacency *adj, struct isis_sr_psid_info *sr,
+ struct mpls_label_stack *label_stack)
+{
+ struct isis_nexthop *nh;
+ union g_addr ip = {};
+
+ switch (family) {
+ case AF_INET:
+ for (unsigned int i = 0; i < adj->ipv4_address_count; i++) {
+ ip.ipv4 = adj->ipv4_addresses[i];
+
+ if (!nexthoplookup(nexthops, AF_INET, &ip,
+ adj->circuit->interface->ifindex)) {
+ nh = isis_nexthop_create(
+ AF_INET, &ip,
+ adj->circuit->interface->ifindex);
+ memcpy(nh->sysid, adj->sysid, sizeof(nh->sysid));
+ if (sr)
+ nh->sr = *sr;
+ nh->label_stack = label_stack;
+ listnode_add(nexthops, nh);
+ break;
+ }
+ }
+ break;
+ case AF_INET6:
+ for (unsigned int i = 0; i < adj->ll_ipv6_count; i++) {
+ ip.ipv6 = adj->ll_ipv6_addrs[i];
+
+ if (!nexthoplookup(nexthops, AF_INET6, &ip,
+ adj->circuit->interface->ifindex)) {
+ nh = isis_nexthop_create(
+ AF_INET6, &ip,
+ adj->circuit->interface->ifindex);
+ memcpy(nh->sysid, adj->sysid, sizeof(nh->sysid));
+ if (sr)
+ nh->sr = *sr;
+ nh->label_stack = label_stack;
+ listnode_add(nexthops, nh);
+ break;
+ }
+ }
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address family [%d]",
+ __func__, family);
+ exit(1);
+ }
+}
+
+static void isis_route_add_dummy_nexthops(struct isis_route_info *rinfo,
+ const uint8_t *sysid,
+ struct isis_sr_psid_info *sr,
+ struct mpls_label_stack *label_stack)
+{
+ struct isis_nexthop *nh;
+
+ nh = XCALLOC(MTYPE_ISIS_NEXTHOP, sizeof(struct isis_nexthop));
+ memcpy(nh->sysid, sysid, sizeof(nh->sysid));
+ nh->sr = *sr;
+ nh->label_stack = label_stack;
+ listnode_add(rinfo->nexthops, nh);
+}
+
+static struct isis_route_info *
+isis_route_info_new(struct prefix *prefix, struct prefix_ipv6 *src_p,
+ uint32_t cost, uint32_t depth, struct isis_sr_psid_info *sr,
+ struct list *adjacencies, bool allow_ecmp)
+{
+ struct isis_route_info *rinfo;
+ struct isis_vertex_adj *vadj;
+ struct listnode *node;
+
+ rinfo = XCALLOC(MTYPE_ISIS_ROUTE_INFO, sizeof(struct isis_route_info));
+
+ rinfo->nexthops = list_new();
+ for (ALL_LIST_ELEMENTS_RO(adjacencies, node, vadj)) {
+ struct isis_spf_adj *sadj = vadj->sadj;
+ struct isis_adjacency *adj = sadj->adj;
+ struct isis_sr_psid_info *sr = &vadj->sr;
+ struct mpls_label_stack *label_stack = vadj->label_stack;
+
+ /*
+ * Create dummy nexthops when running SPF on a testing
+ * environment.
+ */
+ if (CHECK_FLAG(im->options, F_ISIS_UNIT_TEST)) {
+ isis_route_add_dummy_nexthops(rinfo, sadj->id, sr,
+ label_stack);
+ if (!allow_ecmp)
+ break;
+ continue;
+ }
+
+ /* check for force resync this route */
+ if (CHECK_FLAG(adj->circuit->flags,
+ ISIS_CIRCUIT_FLAPPED_AFTER_SPF))
+ SET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC);
+
+ /* update neighbor router address */
+ switch (prefix->family) {
+ case AF_INET:
+ if (depth == 2 && prefix->prefixlen == IPV4_MAX_BITLEN)
+ adj->router_address = prefix->u.prefix4;
+ break;
+ case AF_INET6:
+ if (depth == 2 && prefix->prefixlen == IPV6_MAX_BITLEN
+ && (!src_p || !src_p->prefixlen)) {
+ adj->router_address6 = prefix->u.prefix6;
+ }
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unknown address family [%d]", __func__,
+ prefix->family);
+ exit(1);
+ }
+ adjinfo2nexthop(prefix->family, rinfo->nexthops, adj, sr,
+ label_stack);
+ if (!allow_ecmp)
+ break;
+ }
+
+ rinfo->cost = cost;
+ rinfo->depth = depth;
+ rinfo->sr_algo[sr->algorithm] = *sr;
+ rinfo->sr_algo[sr->algorithm].nexthops = rinfo->nexthops;
+ rinfo->sr_algo[sr->algorithm].nexthops_backup =
+ rinfo->backup ? rinfo->backup->nexthops : NULL;
+
+ return rinfo;
+}
+
+static void isis_route_info_delete(struct isis_route_info *route_info)
+{
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ if (!route_info->sr_algo[i].present)
+ continue;
+
+ if (route_info->sr_algo[i].nexthops == route_info->nexthops)
+ continue;
+
+ route_info->sr_algo[i].nexthops->del =
+ (void (*)(void *))isis_nexthop_delete;
+ list_delete(&route_info->sr_algo[i].nexthops);
+ }
+
+ if (route_info->nexthops) {
+ route_info->nexthops->del =
+ (void (*)(void *))isis_nexthop_delete;
+ list_delete(&route_info->nexthops);
+ }
+
+ XFREE(MTYPE_ISIS_ROUTE_INFO, route_info);
+}
+
+void isis_route_node_cleanup(struct route_table *table, struct route_node *node)
+{
+ if (node->info)
+ isis_route_info_delete(node->info);
+}
+
+struct isis_route_table_info *isis_route_table_info_alloc(uint8_t algorithm)
+{
+ struct isis_route_table_info *info;
+
+ info = XCALLOC(MTYPE_ISIS_ROUTE_TABLE_INFO, sizeof(*info));
+ info->algorithm = algorithm;
+ return info;
+}
+
+void isis_route_table_info_free(void *info)
+{
+ XFREE(MTYPE_ISIS_ROUTE_TABLE_INFO, info);
+}
+
+uint8_t isis_route_table_algorithm(const struct route_table *table)
+{
+ const struct isis_route_table_info *info = table->info;
+
+ return info ? info->algorithm : 0;
+}
+
+static bool isis_sr_psid_info_same(struct isis_sr_psid_info *new,
+ struct isis_sr_psid_info *old)
+{
+ if (new->present != old->present)
+ return false;
+
+ if (new->label != old->label)
+ return false;
+
+ if (new->sid.flags != old->sid.flags
+ || new->sid.value != old->sid.value)
+ return false;
+
+ if (new->sid.algorithm != old->sid.algorithm)
+ return false;
+
+ return true;
+}
+
+static bool isis_label_stack_same(struct mpls_label_stack *new,
+ struct mpls_label_stack *old)
+{
+ if (!new && !old)
+ return true;
+ if (!new || !old)
+ return false;
+ if (new->num_labels != old->num_labels)
+ return false;
+ if (memcmp(&new->label, &old->label,
+ sizeof(mpls_label_t) * new->num_labels))
+ return false;
+
+ return true;
+}
+
+static int isis_route_info_same(struct isis_route_info *new,
+ struct isis_route_info *old, char *buf,
+ size_t buf_size)
+{
+ struct listnode *node;
+ struct isis_nexthop *new_nh, *old_nh;
+
+ if (new->cost != old->cost) {
+ if (buf)
+ snprintf(buf, buf_size, "cost (old: %u, new: %u)",
+ old->cost, new->cost);
+ return 0;
+ }
+
+ if (new->depth != old->depth) {
+ if (buf)
+ snprintf(buf, buf_size, "depth (old: %u, new: %u)",
+ old->depth, new->depth);
+ return 0;
+ }
+
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ struct isis_sr_psid_info new_sr_algo;
+ struct isis_sr_psid_info old_sr_algo;
+
+ new_sr_algo = new->sr_algo[i];
+ old_sr_algo = old->sr_algo[i];
+
+ if (!isis_sr_psid_info_same(&new_sr_algo, &old_sr_algo)) {
+ if (buf)
+ snprintf(
+ buf, buf_size,
+ "SR input label algo-%u (old: %s, new: %s)",
+ i, old_sr_algo.present ? "yes" : "no",
+ new_sr_algo.present ? "yes" : "no");
+ return 0;
+ }
+ }
+
+ if (new->nexthops->count != old->nexthops->count) {
+ if (buf)
+ snprintf(buf, buf_size, "nhops num (old: %u, new: %u)",
+ old->nexthops->count, new->nexthops->count);
+ return 0;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(new->nexthops, node, new_nh)) {
+ old_nh = nexthoplookup(old->nexthops, new_nh->family,
+ &new_nh->ip, new_nh->ifindex);
+ if (!old_nh) {
+ if (buf)
+ snprintf(buf, buf_size,
+ "new nhop"); /* TODO: print nhop */
+ return 0;
+ }
+ if (!isis_sr_psid_info_same(&new_nh->sr, &old_nh->sr)) {
+ if (buf)
+ snprintf(buf, buf_size, "nhop SR label");
+ return 0;
+ }
+ if (!isis_label_stack_same(new_nh->label_stack,
+ old_nh->label_stack)) {
+ if (buf)
+ snprintf(buf, buf_size, "nhop label stack");
+ return 0;
+ }
+ }
+
+ /* only the resync flag needs to be checked */
+ if (CHECK_FLAG(new->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC)
+ != CHECK_FLAG(old->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC)) {
+ if (buf)
+ snprintf(buf, buf_size, "resync flag");
+ return 0;
+ }
+
+ return 1;
+}
+
+struct isis_route_info *
+isis_route_create(struct prefix *prefix, struct prefix_ipv6 *src_p,
+ uint32_t cost, uint32_t depth, struct isis_sr_psid_info *sr,
+ struct list *adjacencies, bool allow_ecmp,
+ struct isis_area *area, struct route_table *table)
+{
+ struct route_node *route_node;
+ struct isis_route_info *rinfo_new, *rinfo_old, *route_info = NULL;
+ char change_buf[64];
+
+ if (!table)
+ return NULL;
+
+ rinfo_new = isis_route_info_new(prefix, src_p, cost, depth, sr,
+ adjacencies, allow_ecmp);
+ route_node = srcdest_rnode_get(table, prefix, src_p);
+
+ rinfo_old = route_node->info;
+ if (!rinfo_old) {
+ if (IS_DEBUG_RTE_EVENTS)
+ zlog_debug("ISIS-Rte (%s) route created: %pFX",
+ area->area_tag, prefix);
+ route_info = rinfo_new;
+ UNSET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+ } else {
+ route_unlock_node(route_node);
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_RTE_EVENTS)
+ zlog_debug("ISIS-Rte (%s) route already exists: %pFX",
+ area->area_tag, prefix);
+#endif /* EXTREME_DEBUG */
+ if (isis_route_info_same(rinfo_new, rinfo_old, change_buf,
+ sizeof(change_buf))) {
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_RTE_EVENTS)
+ zlog_debug(
+ "ISIS-Rte (%s) route unchanged: %pFX",
+ area->area_tag, prefix);
+#endif /* EXTREME_DEBUG */
+ isis_route_info_delete(rinfo_new);
+ route_info = rinfo_old;
+ } else {
+ if (IS_DEBUG_RTE_EVENTS)
+ zlog_debug(
+ "ISIS-Rte (%s): route changed: %pFX, change: %s",
+ area->area_tag, prefix, change_buf);
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
+ rinfo_new->sr_algo_previous[i] =
+ rinfo_old->sr_algo[i];
+ isis_route_info_delete(rinfo_old);
+ route_info = rinfo_new;
+ UNSET_FLAG(route_info->flag,
+ ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+ }
+ }
+
+ SET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ACTIVE);
+ route_node->info = route_info;
+
+ return route_info;
+}
+
+void isis_route_delete(struct isis_area *area, struct route_node *rode,
+ struct route_table *table)
+{
+ struct isis_route_info *rinfo;
+ char buff[SRCDEST2STR_BUFFER];
+ struct prefix *prefix;
+ struct prefix_ipv6 *src_p;
+
+ /* for log */
+ srcdest_rnode2str(rode, buff, sizeof(buff));
+
+ srcdest_rnode_prefixes(rode, (const struct prefix **)&prefix,
+ (const struct prefix **)&src_p);
+
+ rinfo = rode->info;
+ if (rinfo == NULL) {
+ if (IS_DEBUG_RTE_EVENTS)
+ zlog_debug(
+ "ISIS-Rte: tried to delete non-existent route %s",
+ buff);
+ return;
+ }
+
+ if (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) {
+ UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE);
+ if (IS_DEBUG_RTE_EVENTS)
+ zlog_debug("ISIS-Rte: route delete %s", buff);
+ isis_route_update(area, prefix, src_p, rinfo);
+ }
+ isis_route_info_delete(rinfo);
+ rode->info = NULL;
+ route_unlock_node(rode);
+}
+
+static void isis_route_remove_previous_sid(struct isis_area *area,
+ struct prefix *prefix,
+ struct isis_route_info *route_info)
+{
+ /*
+ * Explicitly uninstall previous Prefix-SID label if it has
+ * changed or was removed.
+ */
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ if (route_info->sr_algo_previous[i].present &&
+ (!route_info->sr_algo[i].present ||
+ route_info->sr_algo_previous[i].label !=
+ route_info->sr_algo[i].label))
+ isis_zebra_prefix_sid_uninstall(
+ area, prefix, route_info,
+ &route_info->sr_algo_previous[i]);
+ }
+}
+
+static void set_merge_route_info_sr_algo(struct isis_route_info *mrinfo,
+ struct isis_route_info *rinfo)
+{
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ if (rinfo->sr_algo[i].present) {
+ assert(i == rinfo->sr_algo[i].algorithm);
+ assert(rinfo->nexthops);
+ assert(rinfo->backup ? rinfo->backup->nexthops != NULL
+ : true);
+
+ if (mrinfo->sr_algo[i].nexthops != NULL &&
+ mrinfo->sr_algo[i].nexthops != mrinfo->nexthops) {
+ mrinfo->sr_algo[i].nexthops->del =
+ (void (*)(void *))isis_nexthop_delete;
+ list_delete(&mrinfo->sr_algo[i].nexthops);
+ }
+
+ mrinfo->sr_algo[i] = rinfo->sr_algo[i];
+ mrinfo->sr_algo[i].nexthops = isis_nexthop_list_dup(
+ rinfo->sr_algo[i].nexthops);
+ }
+ }
+
+ UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+ UNSET_FLAG(mrinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+}
+
+static void isis_route_update(struct isis_area *area, struct prefix *prefix,
+ struct prefix_ipv6 *src_p,
+ struct isis_route_info *route_info)
+{
+ if (area == NULL)
+ return;
+
+ if (CHECK_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ACTIVE)) {
+ if (CHECK_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED))
+ return;
+
+ isis_route_remove_previous_sid(area, prefix, route_info);
+
+ /* Install route. */
+ isis_zebra_route_add_route(area->isis, prefix, src_p,
+ route_info);
+
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ struct isis_sr_psid_info sr_algo;
+
+ sr_algo = route_info->sr_algo[i];
+
+ /*
+ * Install/reinstall Prefix-SID label.
+ */
+ if (sr_algo.present)
+ isis_zebra_prefix_sid_install(area, prefix,
+ &sr_algo);
+
+ hook_call(isis_route_update_hook, area, prefix,
+ route_info);
+ }
+
+ hook_call(isis_route_update_hook, area, prefix, route_info);
+
+ SET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+ UNSET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC);
+ } else {
+ /* Uninstall Prefix-SID label. */
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
+ if (route_info->sr_algo[i].present)
+ isis_zebra_prefix_sid_uninstall(
+ area, prefix, route_info,
+ &route_info->sr_algo[i]);
+
+ /* Uninstall route. */
+ isis_zebra_route_del_route(area->isis, prefix, src_p,
+ route_info);
+ hook_call(isis_route_update_hook, area, prefix, route_info);
+
+ UNSET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+ }
+}
+
+static void _isis_route_verify_table(struct isis_area *area,
+ struct route_table *table,
+ struct route_table *table_backup,
+ struct route_table **tables)
+{
+ struct route_node *rnode, *drnode;
+ struct isis_route_info *rinfo;
+#ifdef EXTREME_DEBUG
+ char buff[SRCDEST2STR_BUFFER];
+#endif /* EXTREME_DEBUG */
+ uint8_t algorithm = isis_route_table_algorithm(table);
+
+ for (rnode = route_top(table); rnode;
+ rnode = srcdest_route_next(rnode)) {
+ if (rnode->info == NULL)
+ continue;
+ rinfo = rnode->info;
+
+ struct prefix *dst_p;
+ struct prefix_ipv6 *src_p;
+
+ srcdest_rnode_prefixes(rnode,
+ (const struct prefix **)&dst_p,
+ (const struct prefix **)&src_p);
+
+ /* Link primary route to backup route. */
+ if (table_backup) {
+ struct route_node *rnode_bck;
+
+ rnode_bck = srcdest_rnode_lookup(table_backup, dst_p,
+ src_p);
+ if (rnode_bck) {
+ rinfo->backup = rnode_bck->info;
+ rinfo->sr_algo[algorithm].nexthops_backup =
+ rinfo->backup->nexthops;
+ UNSET_FLAG(rinfo->flag,
+ ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+ } else if (rinfo->backup) {
+ rinfo->backup = NULL;
+ rinfo->sr_algo[algorithm].nexthops_backup =
+ NULL;
+ UNSET_FLAG(rinfo->flag,
+ ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+ }
+ }
+
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_RTE_EVENTS) {
+ srcdest2str(dst_p, src_p, buff, sizeof(buff));
+ zlog_debug(
+ "ISIS-Rte (%s): route validate: %s %s %s %s",
+ area->area_tag,
+ (CHECK_FLAG(rinfo->flag,
+ ISIS_ROUTE_FLAG_ZEBRA_SYNCED)
+ ? "synced"
+ : "not-synced"),
+ (CHECK_FLAG(rinfo->flag,
+ ISIS_ROUTE_FLAG_ZEBRA_RESYNC)
+ ? "resync"
+ : "not-resync"),
+ (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE)
+ ? "active"
+ : "inactive"),
+ buff);
+ }
+#endif /* EXTREME_DEBUG */
+
+ isis_route_update(area, dst_p, src_p, rinfo);
+
+ if (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE))
+ continue;
+
+ /* In case the verify is not for a merge, we use a single table
+ * directly for
+ * validating => no problems with deleting routes. */
+ if (!tables) {
+ isis_route_delete(area, rnode, table);
+ continue;
+ }
+
+ /* If we work on a merged table,
+ * therefore we must
+ * delete node from each table as well before deleting
+ * route info. */
+ for (int i = 0; tables[i]; i++) {
+ drnode = srcdest_rnode_lookup(tables[i], dst_p, src_p);
+ if (!drnode)
+ continue;
+
+ route_unlock_node(drnode);
+
+ if (drnode->info != rnode->info)
+ continue;
+
+ drnode->info = NULL;
+ route_unlock_node(drnode);
+ }
+
+ isis_route_delete(area, rnode, table);
+ }
+}
+
+static void _isis_route_verify_merge(struct isis_area *area,
+ struct route_table **tables,
+ struct route_table **tables_backup,
+ int tree);
+
+void isis_route_verify_table(struct isis_area *area, struct route_table *table,
+ struct route_table *table_backup, int tree)
+{
+ struct route_table *tables[SR_ALGORITHM_COUNT] = {table};
+ struct route_table *tables_backup[SR_ALGORITHM_COUNT] = {table_backup};
+#ifndef FABRICD
+ int tables_next = 1;
+ int level = area->is_type == IS_LEVEL_1 ? ISIS_LEVEL1 : ISIS_LEVEL2;
+ struct listnode *node;
+ struct flex_algo *fa;
+ struct isis_flex_algo_data *data;
+
+ for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node, fa)) {
+ data = fa->data;
+ tables[tables_next] =
+ data->spftree[tree][level - 1]->route_table;
+ tables_backup[tables_next] =
+ data->spftree[tree][level - 1]->route_table_backup;
+ _isis_route_verify_table(area, tables[tables_next],
+ tables_backup[tables_next], NULL);
+ tables_next++;
+ }
+#endif /* ifndef FABRICD */
+
+ _isis_route_verify_merge(area, tables, tables_backup, tree);
+}
+
+/* Function to validate route tables for L1L2 areas. In this case we can't use
+ * level route tables directly, we have to merge them at first. L1 routes are
+ * preferred over the L2 ones.
+ *
+ * Merge algorithm is trivial (at least for now). All L1 paths are copied into
+ * merge table at first, then L2 paths are added if L1 path for same prefix
+ * doesn't already exists there.
+ *
+ * FIXME: Is it right place to do it at all? Maybe we should push both levels
+ * to the RIB with different zebra route types and let RIB handle this? */
+void isis_route_verify_merge(struct isis_area *area,
+ struct route_table *level1_table,
+ struct route_table *level1_table_backup,
+ struct route_table *level2_table,
+ struct route_table *level2_table_backup, int tree)
+{
+ struct route_table *tables[] = {level1_table, level2_table, NULL};
+ struct route_table *tables_backup[] = {level1_table_backup,
+ level2_table_backup, NULL};
+ _isis_route_verify_merge(area, tables, tables_backup, tree);
+}
+
+static void _isis_route_verify_merge(struct isis_area *area,
+ struct route_table **tables,
+ struct route_table **tables_backup,
+ int tree)
+{
+ struct route_table *merge;
+ struct route_node *rnode, *mrnode;
+
+ merge = srcdest_table_init();
+
+ for (int i = 0; tables[i]; i++) {
+ uint8_t algorithm = isis_route_table_algorithm(tables[i]);
+ for (rnode = route_top(tables[i]); rnode;
+ rnode = srcdest_route_next(rnode)) {
+ struct isis_route_info *rinfo = rnode->info;
+ struct route_node *rnode_bck;
+
+ if (!rinfo)
+ continue;
+
+ struct prefix *prefix;
+ struct prefix_ipv6 *src_p;
+
+ srcdest_rnode_prefixes(rnode,
+ (const struct prefix **)&prefix,
+ (const struct prefix **)&src_p);
+
+ /* Link primary route to backup route. */
+ rnode_bck = srcdest_rnode_lookup(tables_backup[i],
+ prefix, src_p);
+ if (rnode_bck) {
+ rinfo->backup = rnode_bck->info;
+ rinfo->sr_algo[algorithm].nexthops_backup =
+ rinfo->backup->nexthops;
+ UNSET_FLAG(rinfo->flag,
+ ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+ } else if (rinfo->backup) {
+ rinfo->backup = NULL;
+ rinfo->sr_algo[algorithm].nexthops_backup =
+ NULL;
+ UNSET_FLAG(rinfo->flag,
+ ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+ }
+
+ mrnode = srcdest_rnode_get(merge, prefix, src_p);
+ struct isis_route_info *mrinfo = mrnode->info;
+ if (mrinfo) {
+ route_unlock_node(mrnode);
+ set_merge_route_info_sr_algo(mrinfo, rinfo);
+
+ if (CHECK_FLAG(mrinfo->flag,
+ ISIS_ROUTE_FLAG_ACTIVE)) {
+ /* Clear the ZEBRA_SYNCED flag on the
+ * L2 route when L1 wins, otherwise L2
+ * won't get reinstalled when L1
+ * disappears.
+ */
+ UNSET_FLAG(
+ rinfo->flag,
+ ISIS_ROUTE_FLAG_ZEBRA_SYNCED
+ );
+ continue;
+ } else if (CHECK_FLAG(rinfo->flag,
+ ISIS_ROUTE_FLAG_ACTIVE)) {
+ /* Clear the ZEBRA_SYNCED flag on the L1
+ * route when L2 wins, otherwise L1
+ * won't get reinstalled when it
+ * reappears.
+ */
+ UNSET_FLAG(
+ mrinfo->flag,
+ ISIS_ROUTE_FLAG_ZEBRA_SYNCED
+ );
+ } else if (
+ CHECK_FLAG(
+ mrinfo->flag,
+ ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) {
+ continue;
+ }
+ } else {
+ mrnode->info = rnode->info;
+ }
+ }
+ }
+
+ _isis_route_verify_table(area, merge, NULL, tables);
+ route_table_finish(merge);
+}
+
+void isis_route_invalidate_table(struct isis_area *area,
+ struct route_table *table)
+{
+ struct route_node *rode;
+ struct isis_route_info *rinfo;
+ uint8_t algorithm = isis_route_table_algorithm(table);
+ for (rode = route_top(table); rode; rode = srcdest_route_next(rode)) {
+ if (rode->info == NULL)
+ continue;
+ rinfo = rode->info;
+
+ if (rinfo->backup) {
+ rinfo->backup = NULL;
+ rinfo->sr_algo[algorithm].nexthops_backup = NULL;
+ /*
+ * For now, always force routes that have backup
+ * nexthops to be reinstalled.
+ */
+ UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+ }
+ UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE);
+ }
+}
+
+void isis_route_switchover_nexthop(struct isis_area *area,
+ struct route_table *table, int family,
+ union g_addr *nexthop_addr,
+ ifindex_t ifindex)
+{
+ const char *ifname = NULL, *vrfname = NULL;
+ struct isis_route_info *rinfo;
+ struct prefix_ipv6 *src_p;
+ struct route_node *rnode;
+ vrf_id_t vrf_id;
+ struct prefix *prefix;
+
+ if (IS_DEBUG_EVENTS) {
+ if (area && area->isis) {
+ vrf_id = area->isis->vrf_id;
+ vrfname = vrf_id_to_name(vrf_id);
+ ifname = ifindex2ifname(ifindex, vrf_id);
+ }
+ zlog_debug("%s: initiating fast-reroute %s on VRF %s iface %s",
+ __func__, family2str(family), vrfname ? vrfname : "",
+ ifname ? ifname : "");
+ }
+
+ for (rnode = route_top(table); rnode;
+ rnode = srcdest_route_next(rnode)) {
+ if (!rnode->info)
+ continue;
+ rinfo = rnode->info;
+
+ if (!rinfo->backup)
+ continue;
+
+ if (!nexthoplookup(rinfo->nexthops, family, nexthop_addr,
+ ifindex))
+ continue;
+
+ srcdest_rnode_prefixes(rnode, (const struct prefix **)&prefix,
+ (const struct prefix **)&src_p);
+
+ /* Switchover route. */
+ isis_route_remove_previous_sid(area, prefix, rinfo);
+ UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
+ isis_route_update(area, prefix, src_p, rinfo->backup);
+
+ isis_route_info_delete(rinfo);
+
+ rnode->info = NULL;
+ route_unlock_node(rnode);
+ }
+}
diff --git a/isisd/isis_route.h b/isisd/isis_route.h
new file mode 100644
index 0000000..4d49a5a
--- /dev/null
+++ b/isisd/isis_route.h
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_route.h
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ *
+ * based on ../ospf6d/ospf6_route.[ch]
+ * by Yasuhiro Ohara
+ */
+#ifndef _ZEBRA_ISIS_ROUTE_H
+#define _ZEBRA_ISIS_ROUTE_H
+
+#include "lib/nexthop.h"
+
+struct isis_nexthop {
+ ifindex_t ifindex;
+ int family;
+ union g_addr ip;
+ uint8_t sysid[ISIS_SYS_ID_LEN];
+ struct isis_sr_psid_info sr;
+ struct mpls_label_stack *label_stack;
+};
+
+struct isis_route_info {
+#define ISIS_ROUTE_FLAG_ACTIVE 0x01 /* active route for the prefix */
+#define ISIS_ROUTE_FLAG_ZEBRA_SYNCED 0x02 /* set when route synced to zebra */
+#define ISIS_ROUTE_FLAG_ZEBRA_RESYNC 0x04 /* set when route needs to sync */
+ uint8_t flag;
+ uint32_t cost;
+ uint32_t depth;
+ struct isis_sr_psid_info sr_algo[SR_ALGORITHM_COUNT];
+ struct isis_sr_psid_info sr_algo_previous[SR_ALGORITHM_COUNT];
+ struct list *nexthops;
+ struct isis_route_info *backup;
+};
+
+struct isis_route_table_info {
+ uint8_t algorithm;
+};
+
+DECLARE_HOOK(isis_route_update_hook,
+ (struct isis_area * area, struct prefix *prefix,
+ struct isis_route_info *route_info),
+ (area, prefix, route_info));
+
+void isis_nexthop_delete(struct isis_nexthop *nexthop);
+void adjinfo2nexthop(int family, struct list *nexthops,
+ struct isis_adjacency *adj, struct isis_sr_psid_info *sr,
+ struct mpls_label_stack *label_stack);
+struct isis_route_info *
+isis_route_create(struct prefix *prefix, struct prefix_ipv6 *src_p,
+ uint32_t cost, uint32_t depth, struct isis_sr_psid_info *sr,
+ struct list *adjacencies, bool allow_ecmp,
+ struct isis_area *area, struct route_table *table);
+void isis_route_delete(struct isis_area *area, struct route_node *rode,
+ struct route_table *table);
+
+/* Walk the given table and install new routes to zebra and remove old ones.
+ * route status is tracked using ISIS_ROUTE_FLAG_ACTIVE */
+void isis_route_verify_table(struct isis_area *area, struct route_table *table,
+ struct route_table *table_backup, int tree);
+
+/* Same as isis_route_verify_table, but merge L1 and L2 routes before */
+void isis_route_verify_merge(struct isis_area *area,
+ struct route_table *level1_table,
+ struct route_table *level1_table_backup,
+ struct route_table *level2_table,
+ struct route_table *level2_table_backup, int tree);
+
+/* Unset ISIS_ROUTE_FLAG_ACTIVE on all routes. Used before running spf. */
+void isis_route_invalidate_table(struct isis_area *area,
+ struct route_table *table);
+
+/* Cleanup route node when freeing routing table. */
+void isis_route_node_cleanup(struct route_table *table,
+ struct route_node *node);
+
+
+void isis_route_switchover_nexthop(struct isis_area *area,
+ struct route_table *table, int family,
+ union g_addr *nexthop_addr,
+ ifindex_t ifindex);
+
+struct isis_route_table_info *isis_route_table_info_alloc(uint8_t algorithm);
+void isis_route_table_info_free(void *info);
+uint8_t isis_route_table_algorithm(const struct route_table *table);
+
+#endif /* _ZEBRA_ISIS_ROUTE_H */
diff --git a/isisd/isis_routemap.c b/isisd/isis_routemap.c
new file mode 100644
index 0000000..9be133c
--- /dev/null
+++ b/isisd/isis_routemap.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_routemap.c
+ *
+ * Copyright (C) 2013-2015 Christian Franke <chris@opensourcerouting.org>
+ */
+
+#include <zebra.h>
+
+#include "command.h"
+#include "filter.h"
+#include "hash.h"
+#include "if.h"
+#include "linklist.h"
+#include "log.h"
+#include "memory.h"
+#include "prefix.h"
+#include "plist.h"
+#include "routemap.h"
+#include "table.h"
+#include "frrevent.h"
+#include "vty.h"
+
+#include "isis_constants.h"
+#include "isis_common.h"
+#include "isis_flags.h"
+#include "isisd.h"
+#include "isis_misc.h"
+#include "isis_adjacency.h"
+#include "isis_circuit.h"
+#include "isis_pdu.h"
+#include "isis_lsp.h"
+#include "isis_spf.h"
+#include "isis_route.h"
+#include "isis_zebra.h"
+#include "isis_routemap.h"
+
+static enum route_map_cmd_result_t
+route_match_ip_address(void *rule, const struct prefix *prefix, void *object)
+{
+ struct access_list *alist;
+
+ alist = access_list_lookup(AFI_IP, (char *)rule);
+ if (access_list_apply(alist, prefix) != FILTER_DENY)
+ return RMAP_MATCH;
+
+ return RMAP_NOMATCH;
+}
+
+static void *route_match_ip_address_compile(const char *arg)
+{
+ return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
+}
+
+static void route_match_ip_address_free(void *rule)
+{
+ XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+static const struct route_map_rule_cmd route_match_ip_address_cmd = {
+ "ip address",
+ route_match_ip_address,
+ route_match_ip_address_compile,
+ route_match_ip_address_free
+};
+
+/* ------------------------------------------------------------*/
+
+static enum route_map_cmd_result_t
+route_match_ip_address_prefix_list(void *rule, const struct prefix *prefix,
+ void *object)
+{
+ struct prefix_list *plist;
+
+ plist = prefix_list_lookup(AFI_IP, (char *)rule);
+ if (prefix_list_apply(plist, prefix) != PREFIX_DENY)
+ return RMAP_MATCH;
+
+ return RMAP_NOMATCH;
+}
+
+static void *route_match_ip_address_prefix_list_compile(const char *arg)
+{
+ return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
+}
+
+static void route_match_ip_address_prefix_list_free(void *rule)
+{
+ XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+static const struct route_map_rule_cmd
+ route_match_ip_address_prefix_list_cmd = {
+ "ip address prefix-list",
+ route_match_ip_address_prefix_list,
+ route_match_ip_address_prefix_list_compile,
+ route_match_ip_address_prefix_list_free
+};
+
+/* ------------------------------------------------------------*/
+
+/* `match tag TAG' */
+/* Match function return 1 if match is success else return zero. */
+static enum route_map_cmd_result_t
+route_match_tag(void *rule, const struct prefix *p, void *object)
+{
+ route_tag_t *tag;
+ struct isis_ext_info *info;
+ route_tag_t info_tag;
+
+ tag = rule;
+ info = object;
+
+ info_tag = info->tag;
+ if (info_tag == *tag)
+ return RMAP_MATCH;
+ else
+ return RMAP_NOMATCH;
+}
+
+/* Route map commands for tag matching. */
+static const struct route_map_rule_cmd route_match_tag_cmd = {
+ "tag",
+ route_match_tag,
+ route_map_rule_tag_compile,
+ route_map_rule_tag_free,
+};
+
+/* ------------------------------------------------------------*/
+
+static enum route_map_cmd_result_t
+route_match_ipv6_address(void *rule, const struct prefix *prefix, void *object)
+{
+ struct access_list *alist;
+
+ alist = access_list_lookup(AFI_IP6, (char *)rule);
+ if (access_list_apply(alist, prefix) != FILTER_DENY)
+ return RMAP_MATCH;
+
+ return RMAP_NOMATCH;
+}
+
+static void *route_match_ipv6_address_compile(const char *arg)
+{
+ return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
+}
+
+static void route_match_ipv6_address_free(void *rule)
+{
+ XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+static const struct route_map_rule_cmd route_match_ipv6_address_cmd = {
+ "ipv6 address",
+ route_match_ipv6_address,
+ route_match_ipv6_address_compile,
+ route_match_ipv6_address_free
+};
+
+/* ------------------------------------------------------------*/
+
+static enum route_map_cmd_result_t
+route_match_ipv6_address_prefix_list(void *rule, const struct prefix *prefix,
+ void *object)
+{
+ struct prefix_list *plist;
+
+ plist = prefix_list_lookup(AFI_IP6, (char *)rule);
+ if (prefix_list_apply(plist, prefix) != PREFIX_DENY)
+ return RMAP_MATCH;
+
+ return RMAP_NOMATCH;
+}
+
+static void *route_match_ipv6_address_prefix_list_compile(const char *arg)
+{
+ return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
+}
+
+static void route_match_ipv6_address_prefix_list_free(void *rule)
+{
+ XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+static const struct route_map_rule_cmd
+ route_match_ipv6_address_prefix_list_cmd = {
+ "ipv6 address prefix-list",
+ route_match_ipv6_address_prefix_list,
+ route_match_ipv6_address_prefix_list_compile,
+ route_match_ipv6_address_prefix_list_free
+};
+
+/* ------------------------------------------------------------*/
+
+static enum route_map_cmd_result_t
+route_set_metric(void *rule, const struct prefix *prefix, void *object)
+{
+ uint32_t *metric;
+ struct isis_ext_info *info;
+
+ metric = rule;
+ info = object;
+
+ info->metric = *metric;
+
+ return RMAP_OKAY;
+}
+
+static void *route_set_metric_compile(const char *arg)
+{
+ unsigned long metric;
+ char *endp;
+ uint32_t *ret;
+
+ metric = strtoul(arg, &endp, 10);
+ if (arg[0] == '\0' || *endp != '\0' || metric > MAX_WIDE_PATH_METRIC)
+ return NULL;
+
+ ret = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*ret));
+ *ret = metric;
+
+ return ret;
+}
+
+static void route_set_metric_free(void *rule)
+{
+ XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+static const struct route_map_rule_cmd route_set_metric_cmd = {
+ "metric",
+ route_set_metric,
+ route_set_metric_compile,
+ route_set_metric_free
+};
+
+void isis_route_map_init(void)
+{
+ route_map_init();
+
+ route_map_match_ip_address_hook(generic_match_add);
+ route_map_no_match_ip_address_hook(generic_match_delete);
+
+ route_map_match_ip_address_prefix_list_hook(generic_match_add);
+ route_map_no_match_ip_address_prefix_list_hook(generic_match_delete);
+
+ route_map_match_ipv6_address_hook(generic_match_add);
+ route_map_no_match_ipv6_address_hook(generic_match_delete);
+
+ route_map_match_ipv6_address_prefix_list_hook(generic_match_add);
+ route_map_no_match_ipv6_address_prefix_list_hook(generic_match_delete);
+
+ route_map_match_tag_hook(generic_match_add);
+ route_map_no_match_tag_hook(generic_match_delete);
+
+ route_map_set_metric_hook(generic_set_add);
+ route_map_no_set_metric_hook(generic_set_delete);
+
+ route_map_install_match(&route_match_ip_address_cmd);
+ route_map_install_match(&route_match_ip_address_prefix_list_cmd);
+ route_map_install_match(&route_match_ipv6_address_cmd);
+ route_map_install_match(&route_match_ipv6_address_prefix_list_cmd);
+ route_map_install_match(&route_match_tag_cmd);
+ route_map_install_set(&route_set_metric_cmd);
+}
diff --git a/isisd/isis_routemap.h b/isisd/isis_routemap.h
new file mode 100644
index 0000000..758043f
--- /dev/null
+++ b/isisd/isis_routemap.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_routemap.h
+ *
+ * Copyright (C) 2013-2015 Christian Franke <chris@opensourcerouting.org>
+ */
+#ifndef ISIS_ROUTEMAP_H
+#define ISIS_ROUTEMAP_H
+
+void isis_route_map_init(void);
+
+#endif
diff --git a/isisd/isis_snmp.c b/isisd/isis_snmp.c
new file mode 100644
index 0000000..f9e3780
--- /dev/null
+++ b/isisd/isis_snmp.c
@@ -0,0 +1,3459 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ISIS SNMP support
+ * Copyright (C) 2020 Volta Networks, Inc.
+ * Aleksey Romanov
+ */
+
+/*
+ * This is minimal read-only implementations providing isisReadOnlyCompliance
+ */
+
+#include <zebra.h>
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+
+#include "vrf.h"
+#include "if.h"
+#include "log.h"
+#include "prefix.h"
+#include "table.h"
+#include "command.h"
+#include "memory.h"
+#include "smux.h"
+#include "libfrr.h"
+#include "lib/version.h"
+#include "lib/zclient.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_network.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_dynhn.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_dr.h"
+#include "isisd/isis_nb.h"
+#include "isisd/isisd.h"
+
+/* ISIS-MIB. */
+#define ISIS_MIB 1, 3, 6, 1, 2, 1, 138
+
+#define ISIS_OBJECTS 1
+#define ISIS_SYSTEM 1, 1
+#define ISIS_SYSLEVEL 1, 2
+#define ISIS_CIRC 1, 3
+#define ISIS_CIRC_LEVEL_VALUES 1, 4
+#define ISIS_COUNTERS 1, 5
+#define ISIS_ISADJ 1, 6
+
+/************************ isisSystemGroup ************************/
+
+/* isisSysObject */
+#define ISIS_SYS_OBJECT 1, 1, 1
+#define ISIS_SYS_VERSION 1
+#define ISIS_SYS_LEVELTYPE 2
+#define ISIS_SYS_ID 3
+#define ISIS_SYS_MAXPATHSPLITS 4
+#define ISIS_SYS_MAXLSPGENINT 5
+#define ISIS_SYS_POLLESHELLORATE 6
+#define ISIS_SYS_WAITTIME 7
+#define ISIS_SYS_ADMINSTATE 8
+#define ISIS_SYS_L2TOL1LEAKING 9
+#define ISIS_SYS_MAXAGE 10
+#define ISIS_SYS_RECEIVELSPBUFFERSIZE 11
+#define ISIS_SYS_PROTSUPPORTED 12
+#define ISIS_SYS_NOTIFICATIONENABLE 13
+
+/* isisManAreaAddrEntry */
+#define ISIS_MANAREA_ADDRENTRY 1, 1, 2, 1
+#define ISIS_MANAREA_ADDREXISTSTATE 2
+
+/* isisAreaAddrEntry */
+#define ISIS_AREA_ADDRENTRY 1, 1, 3, 1
+#define ISIS_AREA_ADDR 1
+
+/* isisSummAddrEntry */
+#define ISIS_SUMM_ADDRENTRY 1, 1, 4, 1
+#define ISIS_SUMM_ADDREXISTSTATE 4
+#define ISIS_SUMM_ADDRMETRIC 5
+#define ISIS_SUMM_ADDRFULLMETRIC 6
+
+/* isisRedistributeAddrEntry */
+#define ISIS_REDISTRIBUTE_ADDRENTRY 1, 1, 5, 1
+#define ISIS_REDISTRIBUTE_ADDREXISTSTATE 3
+
+/* isisRouterEntry */
+#define ISIS_ROUTER_ENTRY 1, 1, 6, 1
+#define ISIS_ROUTER_HOSTNAME 3
+#define ISIS_ROUTER_ID 4
+
+/* isisSysLevelTable */
+#define ISIS_SYSLEVEL_ENTRY 1, 2, 1, 1
+#define ISIS_SYSLEVEL_ORIGLSPBUFFSIZE 2
+#define ISIS_SYSLEVEL_MINLSPGENINT 3
+#define ISIS_SYSLEVEL_STATE 4
+#define ISIS_SYSLEVEL_SETOVERLOAD 5
+#define ISIS_SYSLEVEL_SETOVERLOADUNTIL 6
+#define ISIS_SYSLEVEL_METRICSTYLE 7
+#define ISIS_SYSLEVEL_SPFCONSIDERS 8
+#define ISIS_SYSLEVEL_TEENABLED 9
+
+
+/* isisSystemCounterEntry */
+#define ISIS_SYSTEM_COUNTER_ENTRY 1, 5, 1, 1
+#define ISIS_SYSSTAT_CORRLSPS 2
+#define ISIS_SYSSTAT_AUTHTYPEFAILS 3
+#define ISIS_SYSSTAT_AUTHFAILS 4
+#define ISIS_SYSSTAT_LSPDBASEOLOADS 5
+#define ISIS_SYSSTAT_MANADDRDROPFROMAREAS 6
+#define ISIS_SYSSTAT_ATTMPTTOEXMAXSEQNUMS 7
+#define ISIS_SYSSTAT_SEQNUMSKIPS 8
+#define ISIS_SYSSTAT_OWNLSPPURGES 9
+#define ISIS_SYSSTAT_IDFIELDLENMISMATCHES 10
+#define ISIS_SYSSTAT_PARTCHANGES 11
+#define ISIS_SYSSTAT_SPFRUNS 12
+#define ISIS_SYSSTAT_LSPERRORS 13
+
+
+/************************ isisCircuitGroup ************************/
+
+/* Scalar directly under isisCirc */
+#define ISIS_NEXTCIRC_INDEX 1
+
+/* isisCircEntry */
+#define ISIS_CIRC_ENTRY 1, 3, 2, 1
+#define ISIS_CIRC_IFINDEX 2
+#define ISIS_CIRC_ADMINSTATE 3
+#define ISIS_CIRC_EXISTSTATE 4
+#define ISIS_CIRC_TYPE 5
+#define ISIS_CIRC_EXTDOMAIN 6
+#define ISIS_CIRC_LEVELTYPE 7
+#define ISIS_CIRC_PASSIVECIRCUIT 8
+#define ISIS_CIRC_MESHGROUPENABLED 9
+#define ISIS_CIRC_MESHGROUP 10
+#define ISIS_CIRC_SMALLHELLOS 11
+#define ISIS_CIRC_LASTUPTIME 12
+#define ISIS_CIRC_3WAYENABLED 13
+#define ISIS_CIRC_EXTENDEDCIRCID 14
+
+/* isisCircLevelEntry */
+#define ISIS_CIRCLEVEL_ENTRY 1, 4, 1, 1
+#define ISIS_CIRCLEVEL_METRIC 2
+#define ISIS_CIRCLEVEL_WIDEMETRIC 3
+#define ISIS_CIRCLEVEL_ISPRIORITY 4
+#define ISIS_CIRCLEVEL_IDOCTET 5
+#define ISIS_CIRCLEVEL_ID 6
+#define ISIS_CIRCLEVEL_DESIS 7
+#define ISIS_CIRCLEVEL_HELLOMULTIPLIER 8
+#define ISIS_CIRCLEVEL_HELLOTIMER 9
+#define ISIS_CIRCLEVEL_DRHELLOTIMER 10
+#define ISIS_CIRCLEVEL_LSPTHROTTLE 11
+#define ISIS_CIRCLEVEL_MINLSPRETRANSINT 12
+#define ISIS_CIRCLEVEL_CSNPINTERVAL 13
+#define ISIS_CIRCLEVEL_PARTSNPINTERVAL 14
+
+/* isisCircuitCounterEntry */
+#define ISIS_CIRC_COUNTER_ENTRY 1, 5, 2, 1
+#define ISIS_CIRC_ADJCHANGES 2
+#define ISIS_CIRC_NUMADJ 3
+#define ISIS_CIRC_INITFAILS 4
+#define ISIS_CIRC_REJADJS 5
+#define ISIS_CIRC_IDFIELDLENMISMATCHES 6
+#define ISIS_CIRC_MAXAREAADDRMISMATCHES 7
+#define ISIS_CIRC_AUTHTYPEFAILS 8
+#define ISIS_CIRC_AUTHFAILS 9
+#define ISIS_CIRC_LANDESISCHANGES 10
+
+
+/************************ isisISAdjGroup ************************/
+
+/* isisISAdjEntry */
+#define ISIS_ISADJ_ENTRY 1, 6, 1, 1
+#define ISIS_ISADJ_STATE 2
+#define ISIS_ISADJ_3WAYSTATE 3
+#define ISIS_ISADJ_NEIGHSNPAADDRESS 4
+#define ISIS_ISADJ_NEIGHSYSTYPE 5
+#define ISIS_ISADJ_NEIGHSYSID 6
+#define ISIS_ISADJ_NBREXTENDEDCIRCID 7
+#define ISIS_ISADJ_USAGE 8
+#define ISIS_ISADJ_HOLDTIMER 9
+#define ISIS_ISADJ_NEIGHPRIORITY 10
+#define ISIS_ISADJ_LASTUPTIME 11
+
+/* isisISAdjAreadAddrEntry */
+#define ISIS_ISADJAREA_ADDRENTRY 1, 6, 2, 1
+#define ISIS_ISADJAREA_ADDRESS 2
+
+/* isisISAdjIPAddrEntry*/
+#define ISIS_ISADJIPADDR_ENTRY 1, 6, 3, 1
+#define ISIS_ISADJIPADDR_TYPE 2
+#define ISIS_ISADJIPADDR_ADDRESS 3
+
+
+/* isisISAdjProtSuppEntty */
+
+#define ISIS_ISADJPROTSUPP_ENTRY 1, 6, 4, 1
+#define ISIS_ISADJPROTSUPP_PROTOCOL 1
+
+
+/************************ Trap data variables ************************/
+#define ISIS_NOTIFICATION_ENTRY 1, 10, 1
+#define ISIS_NOTIF_SYLELVELINDEX 1
+#define ISIS_NOTIF_CIRCIFINDEX 2
+#define ISIS_PDU_LSPID 3
+#define ISIS_PDU_FRAGMENT 4
+#define ISIS_PDU_FIELDLEN 5
+#define ISIS_PDU_MAXAREAADDR 6
+#define ISIS_PDU_PROTOVER 7
+#define ISIS_PDU_LSPSIZE 8
+#define ISIS_PDU_ORIGBUFFERSIZE 9
+#define ISIS_PDU_BUFFERSIZE 10
+#define ISIS_PDU_PROTSUPP 11
+#define ISIS_ADJ_STATE 12
+#define ISIS_ERROR_OFFSET 13
+#define ISIS_ERROR_TLVTYPE 14
+#define ISIS_NOTIF_AREAADDR 15
+
+/************************ Traps ************************/
+#define ISIS_NOTIFICATIONS ISIS_MIB, 0
+#define ISIS_TRAP_DB_OVERLOAD 1
+#define ISIS_TRAP_MAN_ADDR_DROP 2
+#define ISIS_TRAP_CORRUPTED_LSP 3
+#define ISIS_TRAP_LSP_EXCEED_MAX 4
+#define ISIS_TRAP_ID_LEN_MISMATCH 5
+#define ISIS_TRAP_MAX_AREA_ADDR_MISMATCH 6
+#define ISIS_TRAP_OWN_LSP_PURGE 7
+#define ISIS_TRAP_SEQNO_SKIPPED 8
+#define ISIS_TRAP_AUTHEN_TYPE_FAILURE 9
+#define ISIS_TRAP_AUTHEN_FAILURE 10
+#define ISIS_TRAP_VERSION_SKEW 11
+#define ISIS_TRAP_AREA_MISMATCH 12
+#define ISIS_TRAP_REJ_ADJACENCY 13
+#define ISIS_TRAP_LSP_TOO_LARGE 14
+#define ISIS_TRAP_LSP_BUFFSIZE_MISMATCH 15
+#define ISIS_TRAP_PROTSUPP_MISMATCH 16
+#define ISIS_TRAP_ADJ_STATE_CHANGE 17
+#define ISIS_TRAP_LSP_ERROR 18
+
+/* Change this definition if number of traps changes */
+#define ISIS_TRAP_LAST_TRAP ISIS_TRAP_LSP_ERROR + 1
+
+#define ISIS_SNMP_TRAP_VAR 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0
+
+
+/* SNMP value hack. */
+#define COUNTER32 ASN_COUNTER
+#define INTEGER ASN_INTEGER
+#define UNSIGNED32 ASN_GAUGE
+#define TIMESTAMP ASN_TIMETICKS
+#define TIMETICKS ASN_TIMETICKS
+#define STRING ASN_OCTET_STR
+
+/* Declare static local variables for convenience. */
+SNMP_LOCAL_VARIABLES
+
+/*
+ * Define time function, it serves two purposes
+ * 1. Uses unint32_t for unix time and encapsulates
+ * sing extension issues in conversion from time_t
+ *
+ * 2. I could be replaced in unit test environment
+ */
+
+/* ISIS-MIB instances. */
+static oid isis_oid[] = {ISIS_MIB};
+
+/* SNMP trap variable */
+static oid isis_snmp_trap_var[] = {ISIS_SNMP_TRAP_VAR};
+
+/* SNMP trap values (others are calculated on the fly */
+static oid isis_snmp_notifications[] = {ISIS_NOTIFICATIONS};
+static oid isis_snmp_trap_val_db_overload[] = {ISIS_NOTIFICATIONS,
+ ISIS_TRAP_DB_OVERLOAD};
+static oid isis_snmp_trap_val_lsp_exceed_max[] = {ISIS_NOTIFICATIONS,
+ ISIS_TRAP_LSP_EXCEED_MAX};
+static oid isis_snmp_trap_val_area_mismatch[] = {ISIS_NOTIFICATIONS,
+ ISIS_TRAP_AREA_MISMATCH};
+static oid isis_snmp_trap_val_lsp_error[] = {ISIS_NOTIFICATIONS,
+ ISIS_TRAP_LSP_ERROR};
+
+/*
+ * Trap vars under 'isisNotifications': note: we use full names of variables
+ * scalar index
+ */
+static oid isis_snmp_trap_data_var_sys_level_index[] = {
+ ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_NOTIF_SYLELVELINDEX, 0};
+static oid isis_snmp_trap_data_var_circ_if_index[] = {
+ ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_NOTIF_CIRCIFINDEX, 0};
+static oid isis_snmp_trap_data_var_pdu_lsp_id[] = {
+ ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_LSPID, 0};
+static oid isis_snmp_trap_data_var_pdu_fragment[] = {
+ ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_FRAGMENT, 0};
+static oid isis_snmp_trap_data_var_pdu_field_len[] = {
+ ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_FIELDLEN, 0};
+static oid isis_snmp_trap_data_var_pdu_max_area_addr[] = {
+ ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_MAXAREAADDR, 0};
+static oid isis_snmp_trap_data_var_pdu_proto_ver[] = {
+ ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_PROTOVER, 0};
+static oid isis_snmp_trap_data_var_pdu_lsp_size[] = {
+ ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_LSPSIZE, 0};
+static oid isis_snmp_trap_data_var_adj_state[] = {
+ ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_ADJ_STATE, 0};
+static oid isis_snmp_trap_data_var_error_offset[] = {
+ ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_ERROR_OFFSET, 0};
+static oid isis_snmp_trap_data_var_error_tlv_type[] = {
+ ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_ERROR_TLVTYPE, 0};
+
+/*
+ * Other variables used by traps: note we use full names of variables and
+ * reserve space for index
+ */
+static oid isis_snmp_trap_data_var_sys_level_state[] = {
+ ISIS_MIB, ISIS_SYSLEVEL_ENTRY, ISIS_SYSLEVEL_STATE, 0};
+
+/* Throttle time values for traps */
+static time_t isis_snmp_trap_timestamp[ISIS_TRAP_LAST_TRAP]; /* ?? 1 */
+
+/* Max len of raw-pdu in traps */
+#define ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN (64)
+
+/*
+ * Just to save on typing we have a shortcut structure
+ * to specify mib layout as prefix/leaf combination
+ */
+#define ISIS_SNMP_PREF_LEN_MAX 10
+struct isis_var_prefix {
+ FindVarMethod *findVar;
+ uint8_t ivd_pref_len;
+ oid ivd_pref[ISIS_SNMP_PREF_LEN_MAX];
+};
+
+
+/* Find-val functions */
+static uint8_t *isis_snmp_find_sys_object(struct variable *, oid *, size_t *,
+ int, size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_man_area(struct variable *, oid *, size_t *, int,
+ size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_area_addr(struct variable *, oid *, size_t *,
+ int, size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_summ_addr(struct variable *, oid *, size_t *,
+ int, size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_redistribute_addr(struct variable *, oid *,
+ size_t *, int, size_t *,
+ WriteMethod **);
+
+static uint8_t *isis_snmp_find_router(struct variable *, oid *, size_t *, int,
+ size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_sys_level(struct variable *, oid *, size_t *,
+ int, size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_system_counter(struct variable *, oid *,
+ size_t *, int, size_t *,
+ WriteMethod **);
+
+static uint8_t *isis_snmp_find_next_circ_index(struct variable *, oid *,
+ size_t *, int, size_t *,
+ WriteMethod **);
+
+static uint8_t *isis_snmp_find_circ(struct variable *, oid *, size_t *, int,
+ size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_circ_level(struct variable *, oid *, size_t *,
+ int, size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_circ_counter(struct variable *, oid *, size_t *,
+ int, size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_isadj(struct variable *, oid *, size_t *, int,
+ size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_isadj_area(struct variable *, oid *, size_t *,
+ int, size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_isadj_ipaddr(struct variable *, oid *, size_t *,
+ int, size_t *, WriteMethod **);
+
+static uint8_t *isis_snmp_find_isadj_prot_supp(struct variable *, oid *,
+ size_t *, int, size_t *,
+ WriteMethod **);
+
+/*
+ * Just to save on typing we have a shortcut structure
+ * to specify mib layout, we populate the rest of the data
+ * during initialization
+ */
+#define ISIS_PREF_LEN_MAX (6)
+
+struct isis_func_to_prefix {
+ FindVarMethod *ihtp_func;
+ oid ihtp_pref_oid[ISIS_PREF_LEN_MAX];
+ uint8_t ihtp_pref_len;
+};
+
+static struct isis_func_to_prefix isis_func_to_prefix_arr[] = {
+ {isis_snmp_find_sys_object, {ISIS_SYS_OBJECT}, 3},
+ {isis_snmp_find_man_area, {ISIS_MANAREA_ADDRENTRY}, 4},
+ {isis_snmp_find_area_addr, {ISIS_AREA_ADDRENTRY}, 4},
+ {isis_snmp_find_summ_addr, {ISIS_SUMM_ADDRENTRY}, 4},
+ {isis_snmp_find_redistribute_addr, {ISIS_REDISTRIBUTE_ADDRENTRY}, 4},
+ {isis_snmp_find_router, {ISIS_ROUTER_ENTRY}, 4},
+ {isis_snmp_find_sys_level, {ISIS_SYSLEVEL_ENTRY}, 4},
+ {isis_snmp_find_system_counter, {ISIS_SYSTEM_COUNTER_ENTRY}, 4},
+ {isis_snmp_find_next_circ_index, {ISIS_CIRC}, 2},
+ {isis_snmp_find_circ, {ISIS_CIRC_ENTRY}, 4},
+ {isis_snmp_find_circ_level, {ISIS_CIRCLEVEL_ENTRY}, 4},
+ {isis_snmp_find_circ_counter, {ISIS_CIRC_COUNTER_ENTRY}, 4},
+ {isis_snmp_find_isadj, {ISIS_ISADJ_ENTRY}, 4},
+ {isis_snmp_find_isadj_area, {ISIS_ISADJAREA_ADDRENTRY}, 4},
+ {isis_snmp_find_isadj_ipaddr, {ISIS_ISADJIPADDR_ENTRY}, 4},
+ {isis_snmp_find_isadj_prot_supp, {ISIS_ISADJPROTSUPP_ENTRY}, 4},
+};
+static size_t isis_func_to_prefix_count = array_size(isis_func_to_prefix_arr);
+
+static struct variable isis_var_arr[] = {
+ {ISIS_SYS_VERSION, INTEGER, RONLY, isis_snmp_find_sys_object},
+ {ISIS_SYS_LEVELTYPE, INTEGER, RONLY, isis_snmp_find_sys_object},
+ {ISIS_SYS_ID, STRING, RONLY, isis_snmp_find_sys_object},
+ {ISIS_SYS_MAXPATHSPLITS, UNSIGNED32, RONLY, isis_snmp_find_sys_object},
+ {ISIS_SYS_MAXLSPGENINT, UNSIGNED32, RONLY, isis_snmp_find_sys_object},
+ {ISIS_SYS_POLLESHELLORATE, UNSIGNED32, RONLY,
+ isis_snmp_find_sys_object},
+ {ISIS_SYS_WAITTIME, UNSIGNED32, RONLY, isis_snmp_find_sys_object},
+ {ISIS_SYS_ADMINSTATE, INTEGER, RONLY, isis_snmp_find_sys_object},
+ {ISIS_SYS_L2TOL1LEAKING, INTEGER, RONLY, isis_snmp_find_sys_object},
+ {ISIS_SYS_MAXAGE, UNSIGNED32, RONLY, isis_snmp_find_sys_object},
+ {ISIS_SYS_RECEIVELSPBUFFERSIZE, UNSIGNED32, RONLY,
+ isis_snmp_find_sys_object},
+ {ISIS_SYS_PROTSUPPORTED, STRING, RONLY, isis_snmp_find_sys_object},
+ {ISIS_SYS_NOTIFICATIONENABLE, INTEGER, RONLY,
+ isis_snmp_find_sys_object},
+ {ISIS_MANAREA_ADDREXISTSTATE, INTEGER, RONLY, isis_snmp_find_man_area},
+ {ISIS_AREA_ADDR, STRING, RONLY, isis_snmp_find_area_addr},
+ {ISIS_SUMM_ADDREXISTSTATE, INTEGER, RONLY, isis_snmp_find_summ_addr},
+ {ISIS_SUMM_ADDRMETRIC, UNSIGNED32, RONLY, isis_snmp_find_summ_addr},
+ {ISIS_SUMM_ADDRFULLMETRIC, UNSIGNED32, RONLY, isis_snmp_find_summ_addr},
+ {ISIS_REDISTRIBUTE_ADDREXISTSTATE, INTEGER, RONLY,
+ isis_snmp_find_redistribute_addr},
+ {ISIS_ROUTER_HOSTNAME, STRING, RONLY, isis_snmp_find_router},
+ {ISIS_ROUTER_ID, UNSIGNED32, RONLY, isis_snmp_find_router},
+ {ISIS_SYSLEVEL_ORIGLSPBUFFSIZE, UNSIGNED32, RONLY,
+ isis_snmp_find_sys_level},
+ {ISIS_SYSLEVEL_MINLSPGENINT, UNSIGNED32, RONLY,
+ isis_snmp_find_sys_level},
+ {ISIS_SYSLEVEL_STATE, INTEGER, RONLY, isis_snmp_find_sys_level},
+ {ISIS_SYSLEVEL_SETOVERLOAD, INTEGER, RONLY, isis_snmp_find_sys_level},
+ {ISIS_SYSLEVEL_SETOVERLOADUNTIL, UNSIGNED32, RONLY,
+ isis_snmp_find_sys_level},
+ {ISIS_SYSLEVEL_METRICSTYLE, INTEGER, RONLY, isis_snmp_find_sys_level},
+ {ISIS_SYSLEVEL_SPFCONSIDERS, INTEGER, RONLY, isis_snmp_find_sys_level},
+ {ISIS_SYSLEVEL_TEENABLED, INTEGER, RONLY, isis_snmp_find_sys_level},
+ {ISIS_SYSSTAT_CORRLSPS, COUNTER32, RONLY,
+ isis_snmp_find_system_counter},
+ {ISIS_SYSSTAT_AUTHTYPEFAILS, COUNTER32, RONLY,
+ isis_snmp_find_system_counter},
+ {ISIS_SYSSTAT_AUTHFAILS, COUNTER32, RONLY,
+ isis_snmp_find_system_counter},
+ {ISIS_SYSSTAT_LSPDBASEOLOADS, COUNTER32, RONLY,
+ isis_snmp_find_system_counter},
+ {ISIS_SYSSTAT_MANADDRDROPFROMAREAS, COUNTER32, RONLY,
+ isis_snmp_find_system_counter},
+ {ISIS_SYSSTAT_ATTMPTTOEXMAXSEQNUMS, COUNTER32, RONLY,
+ isis_snmp_find_system_counter},
+ {ISIS_SYSSTAT_SEQNUMSKIPS, COUNTER32, RONLY,
+ isis_snmp_find_system_counter},
+ {ISIS_SYSSTAT_OWNLSPPURGES, COUNTER32, RONLY,
+ isis_snmp_find_system_counter},
+ {ISIS_SYSSTAT_IDFIELDLENMISMATCHES, COUNTER32, RONLY,
+ isis_snmp_find_system_counter},
+ {ISIS_SYSSTAT_PARTCHANGES, COUNTER32, RONLY,
+ isis_snmp_find_system_counter},
+ {ISIS_SYSSTAT_SPFRUNS, COUNTER32, RONLY, isis_snmp_find_system_counter},
+ {ISIS_SYSSTAT_LSPERRORS, COUNTER32, RONLY,
+ isis_snmp_find_system_counter},
+ {ISIS_NEXTCIRC_INDEX, UNSIGNED32, RONLY,
+ isis_snmp_find_next_circ_index},
+ {ISIS_CIRC_IFINDEX, INTEGER, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_ADMINSTATE, INTEGER, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_EXISTSTATE, INTEGER, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_TYPE, INTEGER, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_EXTDOMAIN, INTEGER, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_LEVELTYPE, INTEGER, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_PASSIVECIRCUIT, INTEGER, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_MESHGROUPENABLED, INTEGER, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_MESHGROUP, UNSIGNED32, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_SMALLHELLOS, INTEGER, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_LASTUPTIME, TIMESTAMP, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_3WAYENABLED, INTEGER, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRC_EXTENDEDCIRCID, UNSIGNED32, RONLY, isis_snmp_find_circ},
+ {ISIS_CIRCLEVEL_METRIC, UNSIGNED32, RONLY, isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_WIDEMETRIC, UNSIGNED32, RONLY,
+ isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_ISPRIORITY, UNSIGNED32, RONLY,
+ isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_IDOCTET, UNSIGNED32, RONLY, isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_ID, STRING, RONLY, isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_DESIS, STRING, RONLY, isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_HELLOMULTIPLIER, UNSIGNED32, RONLY,
+ isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_HELLOTIMER, UNSIGNED32, RONLY,
+ isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_DRHELLOTIMER, UNSIGNED32, RONLY,
+ isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_LSPTHROTTLE, UNSIGNED32, RONLY,
+ isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_MINLSPRETRANSINT, UNSIGNED32, RONLY,
+ isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_CSNPINTERVAL, UNSIGNED32, RONLY,
+ isis_snmp_find_circ_level},
+ {ISIS_CIRCLEVEL_PARTSNPINTERVAL, UNSIGNED32, RONLY,
+ isis_snmp_find_circ_level},
+ {ISIS_CIRC_ADJCHANGES, COUNTER32, RONLY, isis_snmp_find_circ_counter},
+ {ISIS_CIRC_NUMADJ, UNSIGNED32, RONLY, isis_snmp_find_circ_counter},
+ {ISIS_CIRC_INITFAILS, COUNTER32, RONLY, isis_snmp_find_circ_counter},
+ {ISIS_CIRC_REJADJS, COUNTER32, RONLY, isis_snmp_find_circ_counter},
+ {ISIS_CIRC_IDFIELDLENMISMATCHES, COUNTER32, RONLY,
+ isis_snmp_find_circ_counter},
+ {ISIS_CIRC_MAXAREAADDRMISMATCHES, COUNTER32, RONLY,
+ isis_snmp_find_circ_counter},
+ {ISIS_CIRC_AUTHTYPEFAILS, COUNTER32, RONLY,
+ isis_snmp_find_circ_counter},
+ {ISIS_CIRC_AUTHFAILS, COUNTER32, RONLY, isis_snmp_find_circ_counter},
+ {ISIS_CIRC_LANDESISCHANGES, COUNTER32, RONLY,
+ isis_snmp_find_circ_counter},
+ {ISIS_ISADJ_STATE, INTEGER, RONLY, isis_snmp_find_isadj},
+ {ISIS_ISADJ_3WAYSTATE, INTEGER, RONLY, isis_snmp_find_isadj},
+ {ISIS_ISADJ_NEIGHSNPAADDRESS, STRING, RONLY, isis_snmp_find_isadj},
+ {ISIS_ISADJ_NEIGHSYSTYPE, INTEGER, RONLY, isis_snmp_find_isadj},
+ {ISIS_ISADJ_NEIGHSYSID, STRING, RONLY, isis_snmp_find_isadj},
+ {ISIS_ISADJ_NBREXTENDEDCIRCID, UNSIGNED32, RONLY, isis_snmp_find_isadj},
+ {ISIS_ISADJ_USAGE, INTEGER, RONLY, isis_snmp_find_isadj},
+ {ISIS_ISADJ_HOLDTIMER, UNSIGNED32, RONLY, isis_snmp_find_isadj},
+ {ISIS_ISADJ_NEIGHPRIORITY, UNSIGNED32, RONLY, isis_snmp_find_isadj},
+ {ISIS_ISADJ_LASTUPTIME, TIMESTAMP, RONLY, isis_snmp_find_isadj},
+ {ISIS_ISADJAREA_ADDRESS, STRING, RONLY, isis_snmp_find_isadj_area},
+ {ISIS_ISADJIPADDR_TYPE, INTEGER, RONLY, isis_snmp_find_isadj_ipaddr},
+ {ISIS_ISADJIPADDR_ADDRESS, STRING, RONLY, isis_snmp_find_isadj_ipaddr},
+ {ISIS_ISADJPROTSUPP_PROTOCOL, INTEGER, RONLY,
+ isis_snmp_find_isadj_prot_supp},
+};
+
+static const size_t isis_var_count = array_size(isis_var_arr);
+
+/* Minimal set of hard-coded data */
+#define ISIS_VERSION (1)
+
+/* If sys-id is not set use this value */
+static uint8_t isis_null_sysid[ISIS_SYS_ID_LEN];
+
+/* OSI addr-len */
+#define ISIS_SNMP_OSI_ADDR_LEN_MAX (20)
+
+/*
+ * The implementation has a fixed max-path splits value
+ * of 64 (see ISIS_MAX_PATH_SPLITS), the max mib value
+ * is 32.
+ *
+ * FIXME(aromanov): should we return 32 or 64?
+ */
+#define ISIS_SNMP_MAX_PATH_SPLITS (32)
+
+#define ISIS_SNMP_ADMIN_STATE_ON (1)
+
+#define ISIS_SNMP_ROW_STATUS_ACTIVE (1)
+
+#define ISIS_SNMP_LEVEL_STATE_OFF (1)
+#define ISIS_SNMP_LEVEL_STATE_ON (2)
+#define ISIS_SNMP_LEVEL_STATE_WAITING (3)
+#define ISIS_SNMP_LEVEL_STATE_OVERLOADED (4)
+
+#define ISIS_SNMP_TRUTH_VALUE_TRUE (1)
+#define ISIS_SNMP_TRUTH_VALUE_FALSE (2)
+
+#define ISIS_SNMP_METRIC_STYLE_NARROW (1)
+#define ISIS_SNMP_METRIC_STYLE_WIDE (2)
+#define ISIS_SNMP_METRIC_STYLE_BOTH (3)
+
+#define ISIS_SNMP_MESH_GROUP_INACTIVE (1)
+
+#define ISIS_SNMP_ADJ_STATE_DOWN (1)
+#define ISIS_SNMP_ADJ_STATE_INITIALIZING (2)
+#define ISIS_SNMP_ADJ_STATE_UP (3)
+#define ISIS_SNMP_ADJ_STATE_FAILED (4)
+
+static inline uint32_t isis_snmp_adj_state(enum isis_adj_state state)
+{
+ switch (state) {
+ case ISIS_ADJ_UNKNOWN:
+ return ISIS_SNMP_ADJ_STATE_DOWN;
+ case ISIS_ADJ_INITIALIZING:
+ return ISIS_SNMP_ADJ_STATE_INITIALIZING;
+ case ISIS_ADJ_UP:
+ return ISIS_SNMP_ADJ_STATE_UP;
+ case ISIS_ADJ_DOWN:
+ return ISIS_SNMP_ADJ_STATE_FAILED;
+ }
+
+ return 0; /* not reached */
+}
+
+#define ISIS_SNMP_ADJ_NEIGHTYPE_IS_L1 (1)
+#define ISIS_SNMP_ADJ_NEIGHTYPE_IS_L2 (2)
+#define ISIS_SNMP_ADJ_NEIGHTYPE_IS_L1_L2 (3)
+#define ISIS_SNMP_ADJ_NEIGHTYPE_UNKNOWN (4)
+
+static inline uint32_t isis_snmp_adj_neightype(enum isis_system_type type)
+{
+ switch (type) {
+ case ISIS_SYSTYPE_UNKNOWN:
+ case ISIS_SYSTYPE_ES:
+ return ISIS_SNMP_ADJ_NEIGHTYPE_UNKNOWN;
+ case ISIS_SYSTYPE_IS:
+ return ISIS_SNMP_ADJ_NEIGHTYPE_IS_L1_L2;
+ case ISIS_SYSTYPE_L1_IS:
+ return ISIS_SNMP_ADJ_NEIGHTYPE_IS_L1;
+ case ISIS_SYSTYPE_L2_IS:
+ return ISIS_SNMP_ADJ_NEIGHTYPE_IS_L2;
+ }
+
+ return 0; /* not reached */
+}
+
+#define ISIS_SNMP_INET_TYPE_V4 (1)
+#define ISIS_SNMP_INET_TYPE_V6 (2)
+
+#define ISIS_SNMP_P2P_CIRCUIT (3)
+
+/* Protocols supported value */
+static uint8_t isis_snmp_protocols_supported = 0x7; /* All: iso, ipv4, ipv6 */
+
+#define SNMP_CIRCUITS_MAX (512)
+
+static struct isis_circuit *snmp_circuits[SNMP_CIRCUITS_MAX];
+static uint32_t snmp_circuit_id_last;
+
+static int isis_circuit_snmp_id_gen(struct isis_circuit *circuit)
+{
+ uint32_t id;
+ uint32_t i;
+
+ id = snmp_circuit_id_last;
+ id++;
+
+ /* find next unused entry */
+ for (i = 0; i < SNMP_CIRCUITS_MAX; i++) {
+ if (id >= SNMP_CIRCUITS_MAX) {
+ id = 0;
+ continue;
+ }
+
+ if (id == 0)
+ continue;
+
+ if (snmp_circuits[id] == NULL)
+ break;
+
+ id++;
+ }
+
+ if (i == SNMP_CIRCUITS_MAX) {
+ zlog_warn("Could not allocate a smmp-circuit-id");
+ return 0;
+ }
+
+ snmp_circuits[id] = circuit;
+ snmp_circuit_id_last = id;
+ circuit->snmp_id = id;
+
+ return 0;
+}
+
+static int isis_circuit_snmp_id_free(struct isis_circuit *circuit)
+{
+ snmp_circuits[circuit->snmp_id] = NULL;
+ circuit->snmp_id = 0;
+ return 0;
+}
+
+/*
+ * Convenience function to move to the next circuit,
+ */
+static struct isis_circuit *isis_snmp_circuit_next(struct isis_circuit *circuit)
+{
+ uint32_t start;
+ uint32_t off;
+
+ start = 1;
+
+ if (circuit != NULL)
+ start = circuit->snmp_id + 1;
+
+ for (off = start; off < SNMP_CIRCUITS_MAX; off++) {
+ circuit = snmp_circuits[off];
+
+ if (circuit != NULL)
+ return circuit;
+ }
+
+ return NULL;
+}
+
+/*
+ * Convenience function to get the first matching level
+ */
+static int isis_snmp_circuit_get_level_lo(struct isis_circuit *circuit)
+{
+ if (circuit->is_type == IS_LEVEL_2)
+ return IS_LEVEL_2;
+
+ return IS_LEVEL_1;
+}
+
+/* Check level match */
+static int isis_snmp_get_level_match(int is_type, int level)
+{
+ if (is_type != IS_LEVEL_1 && is_type != IS_LEVEL_2
+ && is_type != IS_LEVEL_1_AND_2)
+ return 0;
+
+ if (level != IS_LEVEL_1 && level != IS_LEVEL_2)
+ return 0;
+
+
+ if (is_type == IS_LEVEL_1) {
+ if (level == IS_LEVEL_1)
+ return 1;
+
+ return 0;
+ }
+
+ if (is_type == IS_LEVEL_2) {
+ if (level == IS_LEVEL_2)
+ return 1;
+
+ return 0;
+ }
+
+ return 1;
+}
+/*
+ * Helper function to convert oid index representing
+ * octet-string index (e.g. isis-sys-id) to byte string
+ * representing the same index.
+ *
+ * Also we do not fail if idx is longer than max_len,
+ * so we can use the same function to check compound
+ * indexes.
+ */
+static int isis_snmp_conv_exact(uint8_t *buf, size_t max_len, size_t *out_len,
+ const oid *idx, size_t idx_len)
+{
+ size_t off;
+ size_t len;
+
+ /* Oid representation: length followed by bytes */
+ if (idx == NULL || idx_len == 0)
+ return 0;
+
+ len = idx[0];
+
+ if (len > max_len)
+ return 0;
+
+ if (idx_len < len + 1)
+ return 0;
+
+ for (off = 0; off < len; off++) {
+ if (idx[off + 1] > 0xff)
+ return 0;
+
+ buf[off] = (uint8_t)(idx[off + 1] & 0xff);
+ }
+
+ *out_len = len;
+
+ return 1;
+}
+
+static int isis_snmp_conv_next(uint8_t *buf, size_t max_len, size_t *out_len,
+ int *try_exact, const oid *idx, size_t idx_len)
+{
+ size_t off;
+ size_t len;
+ size_t cmp_len;
+
+ if (idx == NULL || idx_len == 0) {
+ *out_len = 0;
+ *try_exact = 1;
+ return 1;
+ }
+
+ len = idx[0];
+
+ if (len > max_len)
+ return 0;
+
+ cmp_len = len;
+
+ if ((idx_len - 1) < cmp_len)
+ cmp_len = idx_len - 1;
+
+ for (off = 0; off < cmp_len; off++) {
+ if (idx[off + 1] > 0xff) {
+ memset(buf + off, 0xff, len - off);
+ *out_len = len;
+ *try_exact = 1;
+ return 1;
+ }
+
+ buf[off] = (uint8_t)(idx[off + 1] & 0xff);
+ }
+
+ if (cmp_len < len)
+ memset(buf + cmp_len, 0, len - cmp_len);
+
+ *out_len = len;
+ *try_exact = cmp_len < len ? 1 : 0;
+ return 1;
+}
+
+/*
+ * Helper functions to find area address from snmp index
+ */
+static int isis_snmp_area_addr_lookup_exact(oid *oid_idx, size_t oid_idx_len,
+ struct isis_area **ret_area,
+ struct iso_address **ret_addr)
+{
+ uint8_t cmp_buf[ISIS_SNMP_OSI_ADDR_LEN_MAX];
+ size_t addr_len;
+ struct isis_area *area = NULL;
+ struct iso_address *addr = NULL;
+ struct listnode *addr_node;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ if (isis == NULL)
+ return 0;
+
+ if (list_isempty(isis->area_list)) {
+ /* Area is not configured yet */
+ return 0;
+ }
+
+ area = listgetdata(listhead(isis->area_list));
+
+ int res = isis_snmp_conv_exact(cmp_buf, sizeof(cmp_buf), &addr_len,
+ oid_idx, oid_idx_len);
+
+
+ if (!res || addr_len == 0 || oid_idx_len != (addr_len + 1)) {
+ /* Bad conversion, empty address or extra oids at the end */
+ return 0;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(area->area_addrs, addr_node, addr)) {
+ if (addr->addr_len != addr_len)
+ continue;
+
+ if (memcmp(addr->area_addr, cmp_buf, addr_len) == 0) {
+ if (ret_area != 0)
+ *ret_area = area;
+
+ if (ret_addr != 0)
+ *ret_addr = addr;
+
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int isis_snmp_area_addr_lookup_next(oid *oid_idx, size_t oid_idx_len,
+ struct isis_area **ret_area,
+ struct iso_address **ret_addr)
+{
+ uint8_t cmp_buf[ISIS_SNMP_OSI_ADDR_LEN_MAX];
+ size_t addr_len;
+ int try_exact = 0;
+ struct isis_area *found_area = NULL;
+ struct isis_area *area = NULL;
+ struct iso_address *found_addr = NULL;
+ struct iso_address *addr = NULL;
+ struct listnode *addr_node;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ if (isis == NULL)
+ return 0;
+
+ if (list_isempty(isis->area_list)) {
+ /* Area is not configured yet */
+ return 0;
+ }
+
+ area = listgetdata(listhead(isis->area_list));
+
+ int res = isis_snmp_conv_next(cmp_buf, sizeof(cmp_buf), &addr_len,
+ &try_exact, oid_idx, oid_idx_len);
+
+ if (!res)
+ return 0;
+
+ for (ALL_LIST_ELEMENTS_RO(area->area_addrs, addr_node, addr)) {
+ if (addr->addr_len < addr_len)
+ continue;
+
+ if (addr->addr_len == addr_len) {
+ if (addr_len == 0)
+ continue;
+
+ res = memcmp(addr->area_addr, cmp_buf, addr_len);
+
+ if (res < 0)
+ continue;
+
+ if (res == 0 && addr->addr_len == addr_len) {
+ if (try_exact) {
+ /*
+ * This is the best match no point
+ * to look further
+ */
+ found_area = area;
+ found_addr = addr;
+ break;
+ }
+ continue;
+ }
+ }
+
+ if (found_addr == NULL || addr->addr_len < found_addr->addr_len
+ || (addr->addr_len == found_addr->addr_len
+ && memcmp(addr->area_addr, found_addr->area_addr,
+ addr->addr_len)
+ < 0)) {
+ found_area = area;
+ found_addr = addr;
+ }
+ }
+
+ if (found_area == NULL)
+ return 0;
+
+ if (ret_area != 0)
+ *ret_area = found_area;
+
+ if (ret_addr != 0)
+ *ret_addr = found_addr;
+
+ return 1;
+}
+
+/*
+ * Helper functions to find circuit from
+ * snmp index
+ */
+static int isis_snmp_circuit_lookup_exact(oid *oid_idx, size_t oid_idx_len,
+ struct isis_circuit **ret_circuit)
+{
+ struct isis_circuit *circuit;
+
+ if (oid_idx == NULL || oid_idx_len < 1
+ || oid_idx[0] > SNMP_CIRCUITS_MAX)
+ return 0;
+
+ circuit = snmp_circuits[oid_idx[0]];
+ if (circuit == NULL)
+ return 0;
+
+ if (ret_circuit != NULL)
+ *ret_circuit = circuit;
+
+ return 1;
+}
+
+static int isis_snmp_circuit_lookup_next(oid *oid_idx, size_t oid_idx_len,
+ struct isis_circuit **ret_circuit)
+{
+ oid off;
+ oid start;
+ struct isis_circuit *circuit;
+
+ start = 0;
+
+ if (oid_idx != NULL && oid_idx_len != 0) {
+ if (oid_idx[0] > SNMP_CIRCUITS_MAX)
+ return 0;
+
+ start = oid_idx[0];
+ }
+
+ for (off = start; off < SNMP_CIRCUITS_MAX; ++off) {
+ circuit = snmp_circuits[off];
+
+ if (circuit != NULL && off > start) {
+ if (ret_circuit != NULL)
+ *ret_circuit = circuit;
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Helper functions to find circuit level
+ * combination from snmp index
+ */
+static int isis_snmp_circuit_level_lookup_exact(
+ oid *oid_idx, size_t oid_idx_len, int check_match,
+ struct isis_circuit **ret_circuit, int *ret_level)
+{
+ int level;
+ int res;
+ struct isis_circuit *circuit;
+
+ /* Minor optimization: check level first */
+ if (oid_idx == NULL || oid_idx_len < 2)
+ return 0;
+
+ if (oid_idx[1] < IS_LEVEL_1 || oid_idx[1] > IS_LEVEL_2)
+ return 0;
+
+ level = (int)oid_idx[1];
+
+ res = isis_snmp_circuit_lookup_exact(oid_idx, oid_idx_len, &circuit);
+
+ if (!res)
+ return 0;
+
+ if (check_match && !isis_snmp_get_level_match(circuit->is_type, level))
+ return 0;
+
+ if (ret_circuit != NULL)
+ *ret_circuit = circuit;
+
+ if (ret_level != NULL)
+ *ret_level = level;
+
+ return 1;
+}
+
+static int isis_snmp_circuit_level_lookup_next(
+ oid *oid_idx, size_t oid_idx_len, int check_match,
+ struct isis_circuit **ret_circuit, int *ret_level)
+{
+ oid off;
+ oid start;
+ struct isis_circuit *circuit = NULL;
+ int level;
+
+ start = 0;
+
+ if (oid_idx != NULL && oid_idx_len != 0) {
+ if (oid_idx[0] > SNMP_CIRCUITS_MAX)
+ return 0;
+
+ start = oid_idx[0];
+ }
+
+ for (off = start; off < SNMP_CIRCUITS_MAX; off++) {
+ circuit = snmp_circuits[off];
+
+ if (circuit == NULL)
+ continue;
+
+ if (off > start || oid_idx_len < 2) {
+ /* Found and can use level 1 */
+ level = IS_LEVEL_1;
+ break;
+ }
+
+ assert(oid_idx != NULL);
+
+ /* We have to check level specified by index */
+ if (oid_idx[1] < IS_LEVEL_1) {
+ level = IS_LEVEL_1;
+ break;
+ }
+
+ if (oid_idx[1] < IS_LEVEL_2) {
+ level = IS_LEVEL_2;
+ break;
+ }
+
+ /* Try next */
+ circuit = NULL;
+ }
+
+ if (circuit == NULL)
+ return 0;
+
+ if (check_match
+ && !isis_snmp_get_level_match(circuit->is_type, level)) {
+ if (level == IS_LEVEL_1) {
+ /*
+ * We can simply advance level because
+ * at least one level should match
+ */
+ level = IS_LEVEL_2;
+ } else {
+ /* We have to move to the next circuit */
+ circuit = isis_snmp_circuit_next(circuit);
+ if (circuit == NULL)
+ return 0;
+
+ level = isis_snmp_circuit_get_level_lo(circuit);
+ }
+ }
+
+ if (ret_circuit != NULL)
+ *ret_circuit = circuit;
+
+ if (ret_level != NULL)
+ *ret_level = level;
+
+ return 1;
+}
+
+/*
+ * Helper functions to find adjacency
+ * from snmp index.
+ *
+ * We have 4 tables related to adjacency
+ * looking up adjacency is quite expensive
+ * in case of bcast interfaces.
+ *
+ * It is pain to have 4 very similar functions
+ * hence we pass in and out additional data
+ * we are looking for.
+ *
+ * Note: we use data-len value to distinguish
+ * between ipv4 and ipv6 addresses
+ */
+#define ISIS_SNMP_ADJ_DATA_NONE (1)
+#define ISIS_SNMP_ADJ_DATA_AREA_ADDR (2)
+#define ISIS_SNMP_ADJ_DATA_IP_ADDR (3)
+#define ISIS_SNMP_ADJ_DATA_PROTO (4)
+
+/*
+ * Helper function to process data associated
+ * with adjacency
+ */
+static int isis_snmp_adj_helper(struct isis_adjacency *adj, int data_id,
+ oid data_off, uint8_t **ret_data,
+ size_t *ret_data_len)
+{
+ uint8_t *data = NULL;
+ size_t data_len = 0;
+
+ switch (data_id) {
+ case ISIS_SNMP_ADJ_DATA_NONE:
+ break;
+
+ case ISIS_SNMP_ADJ_DATA_AREA_ADDR:
+ if (data_off >= adj->area_address_count)
+ return 0;
+
+ data = adj->area_addresses[data_off].area_addr;
+ data_len = adj->area_addresses[data_off].addr_len;
+ break;
+
+ case ISIS_SNMP_ADJ_DATA_IP_ADDR:
+ if (data_off >= (adj->ipv4_address_count + adj->ll_ipv6_count))
+ return 0;
+
+ if (data_off >= adj->ipv4_address_count) {
+ data = (uint8_t *)&adj->ll_ipv6_addrs
+ [data_off - adj->ipv4_address_count];
+ data_len = sizeof(adj->ll_ipv6_addrs[0]);
+ } else {
+ data = (uint8_t *)&adj->ipv4_addresses[data_off];
+ data_len = sizeof(adj->ipv4_addresses[0]);
+ }
+
+ break;
+
+
+ case ISIS_SNMP_ADJ_DATA_PROTO:
+ if (data_off >= adj->nlpids.count)
+ return 0;
+
+ data = &adj->nlpids.nlpids[data_off];
+ data_len = sizeof(adj->nlpids.nlpids[0]);
+ break;
+
+ default:
+ assert(0);
+ return 0;
+ }
+
+ if (ret_data != NULL)
+ *ret_data = data;
+
+ if (ret_data_len != NULL)
+ *ret_data_len = data_len;
+
+ return 1;
+}
+
+static int isis_snmp_adj_lookup_exact(oid *oid_idx, size_t oid_idx_len,
+ int data_id,
+ struct isis_adjacency **ret_adj,
+ oid *ret_data_idx, uint8_t **ret_data,
+ size_t *ret_data_len)
+{
+ int res;
+ struct listnode *node;
+ struct isis_circuit *circuit;
+ struct isis_adjacency *adj;
+ struct isis_adjacency *tmp_adj;
+ oid adj_idx;
+ oid data_off;
+ uint8_t *data;
+ size_t data_len;
+
+ res = isis_snmp_circuit_lookup_exact(oid_idx, oid_idx_len, &circuit);
+
+ if (!res)
+ return 0;
+
+ if (oid_idx == NULL || oid_idx_len < 2
+ || (data_id != ISIS_SNMP_ADJ_DATA_NONE && oid_idx_len < 3))
+ return 0;
+
+ adj_idx = oid_idx[1];
+
+ if (data_id != ISIS_SNMP_ADJ_DATA_NONE) {
+ if (oid_idx[2] == 0)
+ return 0;
+
+ data_off = oid_idx[2] - 1;
+ } else {
+ /*
+ * Data-off is not used if data-id is none
+ * but we set it just for consistency
+ */
+ data_off = 0;
+ }
+
+ adj = NULL;
+ data = NULL;
+ data_len = 0;
+
+ for (ALL_LIST_ELEMENTS_RO(circuit->snmp_adj_list, node, tmp_adj)) {
+ if (tmp_adj->snmp_idx > adj_idx) {
+ /*
+ * Adjacencies are ordered in the list
+ * no point to look further
+ */
+ break;
+ }
+
+ if (tmp_adj->snmp_idx == adj_idx) {
+ res = isis_snmp_adj_helper(tmp_adj, data_id, data_off,
+ &data, &data_len);
+ if (res)
+ adj = tmp_adj;
+
+ break;
+ }
+ }
+
+ if (adj == NULL)
+ return 0;
+
+ if (ret_adj != NULL)
+ *ret_adj = adj;
+
+ if (ret_data_idx != NULL)
+ *ret_data_idx = data_off + 1;
+
+ if (ret_data)
+ *ret_data = data;
+
+ if (ret_data_len)
+ *ret_data_len = data_len;
+
+ return 1;
+}
+
+static int isis_snmp_adj_lookup_next(oid *oid_idx, size_t oid_idx_len,
+ int data_id,
+ struct isis_adjacency **ret_adj,
+ oid *ret_data_idx, uint8_t **ret_data,
+ size_t *ret_data_len)
+{
+ struct listnode *node;
+ struct isis_circuit *circuit;
+ struct isis_adjacency *adj;
+ struct isis_adjacency *tmp_adj;
+ oid circ_idx;
+ oid adj_idx;
+ oid data_idx;
+ uint8_t *data;
+ size_t data_len;
+
+ adj = NULL;
+ data = NULL;
+ data_len = 0;
+
+ /*
+ * Note: we rely on the fact that data indexes are consequtive
+ * starting from 1
+ */
+
+ if (oid_idx == 0 || oid_idx_len == 0) {
+ circ_idx = 0;
+ adj_idx = 0;
+ data_idx = 0;
+ } else if (oid_idx_len == 1) {
+ circ_idx = oid_idx[0];
+ adj_idx = 0;
+ data_idx = 0;
+ } else if (oid_idx_len == 2) {
+ circ_idx = oid_idx[0];
+ adj_idx = oid_idx[1];
+ data_idx = 0;
+ } else {
+ circ_idx = oid_idx[0];
+ adj_idx = oid_idx[1];
+
+ if (data_id == ISIS_SNMP_ADJ_DATA_NONE)
+ data_idx = 0;
+ else
+ data_idx = oid_idx[2];
+ }
+
+ if (!isis_snmp_circuit_lookup_exact(&circ_idx, 1, &circuit)
+ && !isis_snmp_circuit_lookup_next(&circ_idx, 1, &circuit))
+ /* No circuit */
+ return 0;
+
+ if (circuit->snmp_id != circ_idx) {
+ /* Match is not exact */
+ circ_idx = 0;
+ adj_idx = 0;
+ data_idx = 0;
+ }
+
+ /*
+ * Note: the simple loop below will work in all cases
+ */
+ while (circuit != NULL) {
+ for (ALL_LIST_ELEMENTS_RO(circuit->snmp_adj_list, node,
+ tmp_adj)) {
+ if (tmp_adj->snmp_idx < adj_idx)
+ continue;
+
+ if (tmp_adj->snmp_idx == adj_idx
+ && data_id == ISIS_SNMP_ADJ_DATA_NONE)
+ continue;
+
+ if (adj_idx != 0 && tmp_adj->snmp_idx > adj_idx)
+ data_idx = 0;
+
+ if (isis_snmp_adj_helper(tmp_adj, data_id, data_idx,
+ &data, &data_len)) {
+ adj = tmp_adj;
+ break;
+ }
+ }
+
+ if (adj != NULL)
+ break;
+
+ circuit = isis_snmp_circuit_next(circuit);
+ circ_idx = 0;
+ adj_idx = 0;
+ data_idx = 0;
+ }
+
+ if (adj == NULL)
+ return 0;
+
+ if (ret_adj != NULL)
+ *ret_adj = adj;
+
+ if (ret_data_idx != 0) {
+ if (data_id == ISIS_SNMP_ADJ_DATA_NONE)
+ /*
+ * Value does not matter but let us set
+ * it to zero for consistency
+ */
+ *ret_data_idx = 0;
+ else
+ *ret_data_idx = data_idx + 1;
+ }
+
+ if (ret_data != 0)
+ *ret_data = data;
+
+ if (ret_data_len != 0)
+ *ret_data_len = data_len;
+
+ return 1;
+}
+
+static uint8_t *isis_snmp_find_sys_object(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ struct isis_area *area = NULL;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ if (isis == NULL)
+ return NULL;
+
+ if (!list_isempty(isis->area_list))
+ area = listgetdata(listhead(isis->area_list));
+
+ /* Check whether the instance identifier is valid */
+ if (smux_header_generic(v, name, length, exact, var_len, write_method)
+ == MATCH_FAILED)
+ return NULL;
+
+ switch (v->magic) {
+ case ISIS_SYS_VERSION:
+ return SNMP_INTEGER(ISIS_VERSION);
+
+ case ISIS_SYS_LEVELTYPE:
+ /*
+ * If we do not have areas use 1&2 otherwise use settings
+ * from the first area in the list
+ */
+ if (area == NULL)
+ return SNMP_INTEGER(IS_LEVEL_1_AND_2);
+
+ return SNMP_INTEGER(area->is_type);
+
+ case ISIS_SYS_ID:
+ if (!isis->sysid_set) {
+ *var_len = ISIS_SYS_ID_LEN;
+ return isis_null_sysid;
+ }
+
+ *var_len = ISIS_SYS_ID_LEN;
+ return isis->sysid;
+
+ case ISIS_SYS_MAXPATHSPLITS:
+ return SNMP_INTEGER(ISIS_SNMP_MAX_PATH_SPLITS);
+
+ case ISIS_SYS_MAXLSPGENINT:
+ return SNMP_INTEGER(DEFAULT_MAX_LSP_GEN_INTERVAL);
+
+ case ISIS_SYS_POLLESHELLORATE:
+ return SNMP_INTEGER(DEFAULT_HELLO_INTERVAL);
+
+ case ISIS_SYS_WAITTIME:
+ /* Note: it seems that we have same fixed delay time */
+ return SNMP_INTEGER(DEFAULT_MIN_LSP_GEN_INTERVAL);
+
+ case ISIS_SYS_ADMINSTATE:
+ /* If daemon is running it admin state is on */
+ return SNMP_INTEGER(ISIS_SNMP_ADMIN_STATE_ON);
+
+
+ case ISIS_SYS_L2TOL1LEAKING:
+ /* We do not allow l2-to-l1 leaking */
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE);
+
+ case ISIS_SYS_MAXAGE:
+ return SNMP_INTEGER(MAX_AGE);
+
+ case ISIS_SYS_RECEIVELSPBUFFERSIZE:
+ if (area == NULL)
+ return SNMP_INTEGER(DEFAULT_LSP_MTU);
+
+ return SNMP_INTEGER(area->lsp_mtu);
+
+ case ISIS_SYS_PROTSUPPORTED:
+ *var_len = 1;
+ return &isis_snmp_protocols_supported;
+
+ case ISIS_SYS_NOTIFICATIONENABLE:
+ if (isis->snmp_notifications)
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE);
+
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE);
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+
+static uint8_t *isis_snmp_find_man_area(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ int res;
+ struct iso_address *area_addr = NULL;
+ oid *oid_idx;
+ size_t oid_idx_len;
+ size_t off = 0;
+
+ *write_method = NULL;
+
+ if (*length <= v->namelen) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else {
+ oid_idx = name + v->namelen;
+ oid_idx_len = *length - v->namelen;
+ }
+
+ if (exact) {
+ res = isis_snmp_area_addr_lookup_exact(oid_idx, oid_idx_len,
+ NULL, &area_addr);
+
+ if (!res)
+ return NULL;
+
+ } else {
+ res = isis_snmp_area_addr_lookup_next(oid_idx, oid_idx_len,
+ NULL, &area_addr);
+
+ if (!res)
+ return NULL;
+
+ /* Copy the name out */
+ memcpy(name, v->name, v->namelen * sizeof(oid));
+
+ /* Append index */
+ name[v->namelen] = area_addr->addr_len;
+
+ for (off = 0; off < area_addr->addr_len; off++)
+ name[v->namelen + 1 + off] = area_addr->area_addr[off];
+
+ *length = v->namelen + 1 + area_addr->addr_len;
+ }
+
+ switch (v->magic) {
+ case ISIS_MANAREA_ADDREXISTSTATE:
+ return SNMP_INTEGER(ISIS_SNMP_ROW_STATUS_ACTIVE);
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static uint8_t *isis_snmp_find_area_addr(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ /*
+ * Area addresses in sense of addresses reported by L1 lsps
+ * are not supported yet.
+ */
+ (void)v;
+ (void)name;
+ (void)length;
+ (void)exact;
+ (void)var_len;
+
+
+ *write_method = NULL;
+
+ return NULL;
+}
+
+static uint8_t *isis_snmp_find_summ_addr(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ /*
+ * So far there is no way to set summary table values through cli
+ * and snmp operations are read-only, hence there are no entries
+ */
+ (void)v;
+ (void)name;
+ (void)length;
+ (void)exact;
+ (void)var_len;
+ *write_method = NULL;
+
+ return NULL;
+}
+
+static uint8_t *isis_snmp_find_redistribute_addr(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ /*
+ * It is not clear at the point whether redist code in isis is actually
+ * used for now we will consider that entries are not present
+ */
+ (void)v;
+ (void)name;
+ (void)length;
+ (void)exact;
+ (void)var_len;
+ *write_method = NULL;
+
+ return NULL;
+}
+
+static uint8_t *isis_snmp_find_router(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ uint8_t cmp_buf[ISIS_SYS_ID_LEN];
+ size_t cmp_len;
+ int try_exact;
+ int cmp_level;
+ int res;
+ struct isis_dynhn *dyn = NULL;
+ oid *oid_idx;
+ size_t oid_idx_len;
+ size_t off = 0;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ if (isis == NULL)
+ return NULL;
+
+ *write_method = NULL;
+
+ if (*length <= v->namelen) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else {
+ oid_idx = name + v->namelen;
+ oid_idx_len = *length - v->namelen;
+ }
+
+ if (exact) {
+ res = isis_snmp_conv_exact(cmp_buf, sizeof(cmp_buf), &cmp_len,
+ oid_idx, oid_idx_len);
+
+ if (!res || cmp_len != ISIS_SYS_ID_LEN
+ || oid_idx_len != (cmp_len + 2))
+ /*
+ * Bad conversion, or bad length,
+ * or extra oids at the end
+ */
+ return NULL;
+
+ if (oid_idx[ISIS_SYS_ID_LEN + 1] < IS_LEVEL_1
+ || oid_idx[ISIS_SYS_ID_LEN + 1] > IS_LEVEL_2)
+ /* Level part of the index is out of range */
+ return NULL;
+
+ cmp_level = (int)oid_idx[ISIS_SYS_ID_LEN + 1];
+
+ dyn = dynhn_find_by_id(isis, cmp_buf);
+
+ if (dyn == NULL || dyn->level != cmp_level)
+ return NULL;
+
+ switch (v->magic) {
+ case ISIS_ROUTER_HOSTNAME:
+ *var_len = strlen(dyn->hostname);
+ return (uint8_t *)dyn->hostname;
+
+ case ISIS_ROUTER_ID:
+ /* It seems that we do no know router-id in lsps */
+ return SNMP_INTEGER(0);
+
+ default:
+ break;
+ }
+
+ return NULL;
+ }
+
+ res = isis_snmp_conv_next(cmp_buf, sizeof(cmp_buf), &cmp_len,
+ &try_exact, oid_idx, oid_idx_len);
+
+
+ if (!res)
+ /* Bad conversion */
+ return NULL;
+
+ if (cmp_len != ISIS_SYS_ID_LEN) {
+ /* We do not have valid index oids */
+ memset(cmp_buf, 0, sizeof(cmp_buf));
+ cmp_level = 0;
+ } else if (try_exact)
+ /*
+ * We have no valid level index.
+ * Let start from non-existing level 0 and
+ * hence not need to do exact match
+ */
+ cmp_level = 0;
+ else if (oid_idx_len < (ISIS_SYS_ID_LEN + 2))
+ cmp_level = 0;
+ else if (oid_idx[ISIS_SYS_ID_LEN + 1] <= IS_LEVEL_2)
+ cmp_level = (int)oid_idx[ISIS_SYS_ID_LEN + 1];
+ else
+ /*
+ * Any value greater than 2 will have the same result
+ * but we can have integer overflows, hence 3 is a reasonable
+ * choice
+ */
+ cmp_level = (int)(IS_LEVEL_2 + 1);
+
+ dyn = dynhn_snmp_next(isis, cmp_buf, cmp_level);
+
+ if (dyn == NULL)
+ return NULL;
+
+ /* Copy the name out */
+ memcpy(name, v->name, v->namelen * sizeof(oid));
+
+ /* Append index */
+ name[v->namelen] = ISIS_SYS_ID_LEN;
+
+ for (off = 0; off < ISIS_SYS_ID_LEN; off++)
+ name[v->namelen + 1 + off] = dyn->id[off];
+
+ name[v->namelen + 1 + ISIS_SYS_ID_LEN] = (oid)dyn->level;
+
+ /* Set length */
+ *length = v->namelen + 1 + ISIS_SYS_ID_LEN + 1;
+
+ switch (v->magic) {
+ case ISIS_ROUTER_HOSTNAME:
+ *var_len = strlen(dyn->hostname);
+ return (uint8_t *)dyn->hostname;
+
+ case ISIS_ROUTER_ID:
+ /* It seems that we do no know router-id in lsps */
+ return SNMP_INTEGER(0);
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static uint8_t *isis_snmp_find_sys_level(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ oid *oid_idx;
+ size_t oid_idx_len;
+ int level;
+ int level_match;
+ struct isis_area *area = NULL;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ if (isis == NULL)
+ return NULL;
+
+ *write_method = NULL;
+
+ if (*length <= v->namelen) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else {
+ oid_idx = name + v->namelen;
+ oid_idx_len = *length - v->namelen;
+ }
+
+ if (exact) {
+ if (oid_idx == NULL || oid_idx_len != 1)
+ return NULL;
+
+ if (oid_idx[0] == IS_LEVEL_1)
+ level = IS_LEVEL_1;
+ else if (oid_idx[0] == IS_LEVEL_2)
+ level = IS_LEVEL_2;
+ else
+ return NULL;
+
+ } else {
+ if (oid_idx == NULL)
+ level = IS_LEVEL_1;
+ else if (oid_idx_len == 0)
+ level = IS_LEVEL_1;
+ else if (oid_idx[0] < IS_LEVEL_1)
+ level = IS_LEVEL_1;
+ else if (oid_idx[0] < IS_LEVEL_2)
+ level = IS_LEVEL_2;
+ else
+ return NULL;
+
+ /* Copy the name out */
+ memcpy(name, v->name, v->namelen * sizeof(oid));
+
+ /* Append index */
+ name[v->namelen] = level;
+
+ /* Set length */
+ *length = v->namelen + 1;
+ }
+
+ area = NULL;
+
+ if (!list_isempty(isis->area_list))
+ area = listgetdata(listhead(isis->area_list));
+
+ level_match = 0;
+
+ if (area != NULL)
+ level_match = isis_snmp_get_level_match(area->is_type, level);
+
+ switch (v->magic) {
+ case ISIS_SYSLEVEL_ORIGLSPBUFFSIZE:
+ if (level_match)
+ return SNMP_INTEGER(area->lsp_mtu);
+
+ return SNMP_INTEGER(DEFAULT_LSP_MTU);
+
+ case ISIS_SYSLEVEL_MINLSPGENINT:
+ if (level_match)
+ return SNMP_INTEGER(area->lsp_gen_interval[level - 1]);
+ else
+ return SNMP_INTEGER(DEFAULT_MIN_LSP_GEN_INTERVAL);
+
+ case ISIS_SYSLEVEL_STATE:
+ if (level_match) {
+ if (area->overload_bit)
+ return SNMP_INTEGER(
+ ISIS_SNMP_LEVEL_STATE_OVERLOADED);
+
+ return SNMP_INTEGER(ISIS_SNMP_LEVEL_STATE_ON);
+ }
+ return SNMP_INTEGER(ISIS_SNMP_LEVEL_STATE_OFF);
+
+ case ISIS_SYSLEVEL_SETOVERLOAD:
+ if (level_match && area->overload_bit)
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE);
+
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE);
+
+ case ISIS_SYSLEVEL_SETOVERLOADUNTIL:
+ /* We do not have automatic cleanup of overload bit */
+ return SNMP_INTEGER(0);
+
+ case ISIS_SYSLEVEL_METRICSTYLE:
+ if (level_match) {
+ if (area->newmetric && area->oldmetric)
+ return SNMP_INTEGER(
+ ISIS_SNMP_METRIC_STYLE_BOTH);
+
+ if (area->newmetric)
+ return SNMP_INTEGER(
+ ISIS_SNMP_METRIC_STYLE_WIDE);
+
+ return SNMP_INTEGER(ISIS_SNMP_METRIC_STYLE_NARROW);
+ }
+ return SNMP_INTEGER(ISIS_SNMP_METRIC_STYLE_NARROW);
+
+ case ISIS_SYSLEVEL_SPFCONSIDERS:
+ return SNMP_INTEGER(ISIS_SNMP_METRIC_STYLE_BOTH);
+
+ case ISIS_SYSLEVEL_TEENABLED:
+ if (level_match && IS_MPLS_TE(area->mta))
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE);
+
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE);
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static uint8_t *isis_snmp_find_system_counter(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ oid *oid_idx;
+ size_t oid_idx_len;
+ int level;
+ int level_match;
+ struct isis_area *area = NULL;
+ uint32_t val;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ if (isis == NULL)
+ return NULL;
+
+ *write_method = NULL;
+
+ if (*length <= v->namelen) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else {
+ oid_idx = name + v->namelen;
+ oid_idx_len = *length - v->namelen;
+ }
+
+ if (exact) {
+ if (oid_idx == NULL || oid_idx_len != 1)
+ return 0;
+
+ if (oid_idx[0] == IS_LEVEL_1)
+ level = IS_LEVEL_1;
+ else if (oid_idx[0] == IS_LEVEL_2)
+ level = IS_LEVEL_2;
+ else
+ return NULL;
+
+ } else {
+ if (oid_idx == NULL)
+ level = IS_LEVEL_1;
+ else if (oid_idx_len == 0)
+ level = IS_LEVEL_1;
+ else if (oid_idx[0] < IS_LEVEL_1)
+ level = IS_LEVEL_1;
+ else if (oid_idx[0] < IS_LEVEL_2)
+ level = IS_LEVEL_2;
+ else
+ return NULL;
+
+ /* Copy the name out */
+ memcpy(name, v->name, v->namelen * sizeof(oid));
+
+ /* Append index */
+ name[v->namelen] = level;
+
+ /* Set length */
+ *length = v->namelen + 1;
+ }
+
+ area = NULL;
+
+ if (!list_isempty(isis->area_list))
+ area = listgetdata(listhead(isis->area_list));
+
+ level_match = 0;
+
+ if (area != NULL)
+ level_match = isis_snmp_get_level_match(area->is_type, level);
+
+ if (!level_match)
+ /* If level does not match all counters are zeros */
+ return SNMP_INTEGER(0);
+
+ switch (v->magic) {
+ case ISIS_SYSSTAT_CORRLSPS:
+ val = 0;
+ break;
+
+ case ISIS_SYSSTAT_AUTHTYPEFAILS:
+ val = (uint32_t)area->auth_type_failures[level - 1];
+ break;
+
+ case ISIS_SYSSTAT_AUTHFAILS:
+ val = (uint32_t)area->auth_failures[level - 1];
+ break;
+
+ case ISIS_SYSSTAT_LSPDBASEOLOADS:
+ val = area->overload_counter;
+ break;
+
+ case ISIS_SYSSTAT_MANADDRDROPFROMAREAS:
+ /* We do not support manual addresses */
+ val = 0;
+ break;
+
+ case ISIS_SYSSTAT_ATTMPTTOEXMAXSEQNUMS:
+ val = area->lsp_exceeded_max_counter;
+ break;
+
+ case ISIS_SYSSTAT_SEQNUMSKIPS:
+ val = area->lsp_seqno_skipped_counter;
+ break;
+
+ case ISIS_SYSSTAT_OWNLSPPURGES:
+ if (!area->purge_originator)
+ val = 0;
+ else
+ val = area->lsp_purge_count[level - 1];
+ break;
+
+ case ISIS_SYSSTAT_IDFIELDLENMISMATCHES:
+ val = (uint32_t)area->id_len_mismatches[level - 1];
+ break;
+
+ case ISIS_SYSSTAT_PARTCHANGES:
+ /* Not supported */
+ val = 0;
+ break;
+
+ case ISIS_SYSSTAT_SPFRUNS:
+ val = (uint32_t)area->spf_run_count[level - 1];
+ break;
+
+ case ISIS_SYSSTAT_LSPERRORS:
+ val = (uint32_t)area->lsp_error_counter[level - 1];
+ break;
+
+ default:
+ return NULL;
+ }
+
+ return SNMP_INTEGER(val);
+}
+
+static uint8_t *isis_snmp_find_next_circ_index(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ /* Check whether the instance identifier is valid */
+ if (smux_header_generic(v, name, length, exact, var_len, write_method)
+ == MATCH_FAILED)
+ return NULL;
+
+ switch (v->magic) {
+ case ISIS_NEXTCIRC_INDEX:
+ /*
+ * We do not support circuit creation through snmp
+ */
+ return SNMP_INTEGER(0);
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static uint8_t *isis_snmp_find_circ(struct variable *v, oid *name,
+ size_t *length, int exact, size_t *var_len,
+ WriteMethod **write_method)
+{
+ /* Index is circuit-id: 1-255 */
+ oid *oid_idx;
+ size_t oid_idx_len;
+ struct isis_circuit *circuit;
+ uint32_t up_ticks;
+ uint32_t delta_ticks;
+ time_t now_time;
+ int res;
+
+ *write_method = NULL;
+
+ if (*length <= v->namelen) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else {
+ oid_idx = name + v->namelen;
+ oid_idx_len = *length - v->namelen;
+ }
+ if (exact) {
+ res = isis_snmp_circuit_lookup_exact(oid_idx, oid_idx_len,
+ &circuit);
+
+ if (!res || oid_idx_len != 1)
+ return NULL;
+
+ } else {
+ res = isis_snmp_circuit_lookup_next(oid_idx, oid_idx_len,
+ &circuit);
+
+ if (!res)
+ return NULL;
+
+ /* Copy the name out */
+ memcpy(name, v->name, v->namelen * sizeof(oid));
+
+ /* Append index */
+ name[v->namelen] = circuit->snmp_id;
+
+ /* Set length */
+ *length = v->namelen + 1;
+ }
+
+ switch (v->magic) {
+ case ISIS_CIRC_IFINDEX:
+ if (circuit->interface == NULL)
+ return SNMP_INTEGER(0);
+
+ return SNMP_INTEGER(circuit->interface->ifindex);
+
+ case ISIS_CIRC_ADMINSTATE:
+ return SNMP_INTEGER(ISIS_SNMP_ADMIN_STATE_ON);
+
+ case ISIS_CIRC_EXISTSTATE:
+ return SNMP_INTEGER(ISIS_SNMP_ROW_STATUS_ACTIVE);
+
+ case ISIS_CIRC_TYPE:
+ /*
+ * Note: values do not match 100%:
+ *
+ * 1. From isis_circuit.h:
+ * CIRCUIT_T_UNKNOWN 0
+ * CIRCUIT_T_BROADCAST 1
+ * CIRCUIT_T_P2P 2
+ * CIRCUIT_T_LOOPBACK 3
+ *
+ * 2. From rfc:
+ * broadcast(1),
+ * ptToPt(2),
+ * staticIn(3),
+ * staticOut(4),
+ */
+
+ return SNMP_INTEGER(circuit->circ_type);
+
+ case ISIS_CIRC_EXTDOMAIN:
+ if (circuit->ext_domain)
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE);
+
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE);
+
+ case ISIS_CIRC_LEVELTYPE:
+ return SNMP_INTEGER(circuit->is_type);
+
+ case ISIS_CIRC_PASSIVECIRCUIT:
+ if (circuit->is_passive)
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE);
+
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE);
+
+ case ISIS_CIRC_MESHGROUPENABLED:
+ /* Not supported */
+ return SNMP_INTEGER(ISIS_SNMP_MESH_GROUP_INACTIVE);
+
+ case ISIS_CIRC_MESHGROUP:
+ /* Not supported */
+ return SNMP_INTEGER(0);
+
+ case ISIS_CIRC_SMALLHELLOS:
+ /*
+ * return false if lan hellos must be padded
+ */
+ if (circuit->pad_hellos == ISIS_HELLO_PADDING_ALWAYS ||
+ (circuit->pad_hellos ==
+ ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION &&
+ circuit->upadjcount[0] + circuit->upadjcount[1] == 0))
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE);
+
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE);
+
+ case ISIS_CIRC_LASTUPTIME:
+ if (circuit->last_uptime == 0)
+ return SNMP_INTEGER(0);
+
+ up_ticks = (uint32_t)netsnmp_get_agent_uptime();
+ now_time = time(NULL);
+
+ if (circuit->last_uptime >= now_time)
+ return SNMP_INTEGER(up_ticks);
+
+ delta_ticks = (now_time - circuit->last_uptime) * 10;
+
+ if (up_ticks < delta_ticks)
+ return SNMP_INTEGER(up_ticks);
+
+ return SNMP_INTEGER(up_ticks - delta_ticks);
+
+ case ISIS_CIRC_3WAYENABLED:
+ /* Not supported */
+ return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE);
+
+ case ISIS_CIRC_EXTENDEDCIRCID:
+ /* Used for 3-way hand shake only */
+ return SNMP_INTEGER(0);
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static uint8_t *isis_snmp_find_circ_level(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ static uint8_t circuit_id_val[ISIS_SYS_ID_LEN + 1];
+ /* Index is circuit-id: 1-255 + level: 1-2 */
+ oid *oid_idx;
+ size_t oid_idx_len;
+ int res;
+ struct isis_circuit *circuit;
+ int level;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ if (isis == NULL)
+ return NULL;
+
+ *write_method = NULL;
+
+ if (*length <= v->namelen) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else {
+ oid_idx = name + v->namelen;
+ oid_idx_len = *length - v->namelen;
+ }
+ if (exact) {
+ res = isis_snmp_circuit_level_lookup_exact(oid_idx, oid_idx_len,
+ 1, &circuit, &level);
+
+ if (!res || oid_idx_len != 2)
+ return NULL;
+
+ } else {
+ res = isis_snmp_circuit_level_lookup_next(oid_idx, oid_idx_len,
+ 1, &circuit, &level);
+
+ if (!res)
+ return NULL;
+
+ /* Copy the name out */
+ memcpy(name, v->name, v->namelen * sizeof(oid));
+
+ /* Append index */
+ name[v->namelen] = circuit->snmp_id;
+ name[v->namelen + 1] = level;
+
+ /* Set length */
+ *length = v->namelen + 2;
+ }
+
+ switch (v->magic) {
+ case ISIS_CIRCLEVEL_METRIC:
+ return SNMP_INTEGER(circuit->metric[level - 1]);
+
+ case ISIS_CIRCLEVEL_WIDEMETRIC:
+ if (circuit->area == NULL || !circuit->area->newmetric) {
+ /* What should we do if wide metric is not supported? */
+ return SNMP_INTEGER(0);
+ }
+ return SNMP_INTEGER(circuit->te_metric[level - 1]);
+
+ case ISIS_CIRCLEVEL_ISPRIORITY:
+ return SNMP_INTEGER(circuit->priority[level - 1]);
+
+ case ISIS_CIRCLEVEL_IDOCTET:
+ return SNMP_INTEGER(circuit->circuit_id);
+
+ case ISIS_CIRCLEVEL_ID:
+ if (circuit->circ_type != CIRCUIT_T_P2P) {
+ /*
+ * Unless it is point-to-point circuit, the value is and
+ * empty octet string
+ */
+ *var_len = 0;
+ return circuit_id_val;
+ }
+
+ /* !!!!!! Circuit-id is zero for p2p links */
+ if (circuit->u.p2p.neighbor == NULL
+ || circuit->u.p2p.neighbor->adj_state != ISIS_ADJ_UP) {
+ /* No adjacency or adjacency not fully up yet */
+ memcpy(circuit_id_val, isis->sysid, ISIS_SYS_ID_LEN);
+ circuit_id_val[ISIS_SYS_ID_LEN] = circuit->circuit_id;
+ *var_len = ISIS_SYS_ID_LEN + 1;
+ return circuit_id_val;
+ }
+
+ /* Adjacency fully-up */
+ memcpy(circuit_id_val, circuit->u.p2p.neighbor->sysid,
+ ISIS_SYS_ID_LEN);
+ circuit_id_val[ISIS_SYS_ID_LEN] = 0;
+ *var_len = ISIS_SYS_ID_LEN + 1;
+ return circuit_id_val;
+
+ case ISIS_CIRCLEVEL_DESIS:
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST
+ || !circuit->u.bc.is_dr[level - 1]) {
+ /*
+ * Unless it is lan circuit participating in dis process
+ * the value is an empty octet string
+ */
+ *var_len = 0;
+ return circuit_id_val;
+ }
+
+ *var_len = ISIS_SYS_ID_LEN + 1;
+
+ if (level == IS_LEVEL_1)
+ return circuit->u.bc.l1_desig_is;
+
+ return circuit->u.bc.l2_desig_is;
+
+ case ISIS_CIRCLEVEL_HELLOMULTIPLIER:
+ return SNMP_INTEGER(circuit->hello_multiplier[level - 1]);
+
+ case ISIS_CIRCLEVEL_HELLOTIMER:
+ return SNMP_INTEGER(circuit->hello_interval[level - 1] * 1000);
+
+ case ISIS_CIRCLEVEL_DRHELLOTIMER:
+ return SNMP_INTEGER(circuit->hello_interval[level - 1] * 1000);
+
+ case ISIS_CIRCLEVEL_LSPTHROTTLE:
+ if (circuit->area)
+ return SNMP_INTEGER(
+ circuit->area->min_spf_interval[level - 1]
+ * 1000);
+ else
+ return SNMP_INTEGER(0);
+
+ case ISIS_CIRCLEVEL_MINLSPRETRANSINT:
+ if (circuit->area)
+ return SNMP_INTEGER(
+ circuit->area->min_spf_interval[level - 1]);
+ else
+ return SNMP_INTEGER(0);
+
+ case ISIS_CIRCLEVEL_CSNPINTERVAL:
+ return SNMP_INTEGER(circuit->csnp_interval[level - 1]);
+
+ case ISIS_CIRCLEVEL_PARTSNPINTERVAL:
+ return SNMP_INTEGER(circuit->psnp_interval[level - 1]);
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static uint8_t *isis_snmp_find_circ_counter(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ /* Index circuit-id 1-255 + level */
+ oid *oid_idx;
+ size_t oid_idx_len;
+ int res;
+ struct isis_circuit *circuit;
+ int level;
+ uint32_t val = 0;
+
+ *write_method = NULL;
+
+ if (*length <= v->namelen) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else {
+ oid_idx = name + v->namelen;
+ oid_idx_len = *length - v->namelen;
+ }
+ if (exact) {
+ res = isis_snmp_circuit_level_lookup_exact(oid_idx, oid_idx_len,
+ 1, &circuit, &level);
+
+ if (!res || oid_idx_len != 2)
+ return NULL;
+
+ } else {
+ res = isis_snmp_circuit_level_lookup_next(oid_idx, oid_idx_len,
+ 1, &circuit, &level);
+
+ if (!res)
+ return NULL;
+
+ /* Copy the name out */
+ memcpy(name, v->name, v->namelen * sizeof(oid));
+
+ /* Append index */
+ name[v->namelen] = circuit->snmp_id;
+ if (circuit->circ_type == CIRCUIT_T_P2P)
+ name[v->namelen + 1] = ISIS_SNMP_P2P_CIRCUIT;
+ else
+ name[v->namelen + 1] = level;
+
+ /* Set length */
+ *length = v->namelen + 2;
+ }
+
+ switch (v->magic) {
+ case ISIS_CIRC_ADJCHANGES:
+ val = circuit->adj_state_changes;
+ break;
+
+ case ISIS_CIRC_NUMADJ:
+ if (circuit->circ_type == CIRCUIT_T_P2P) {
+ val = circuit->u.p2p.neighbor == NULL ? 0 : 1;
+ break;
+ }
+
+ if (circuit->circ_type != CIRCUIT_T_BROADCAST) {
+ val = 0;
+ break;
+ }
+
+ if (level == IS_LEVEL_1) {
+ if (circuit->u.bc.adjdb[0] == NULL)
+ val = 0;
+ else
+ val = listcount(circuit->u.bc.adjdb[0]);
+ break;
+ }
+
+ if (circuit->u.bc.adjdb[1] == NULL)
+ val = 0;
+ else
+ val = listcount(circuit->u.bc.adjdb[1]);
+
+ break;
+
+ case ISIS_CIRC_INITFAILS:
+ val = circuit->init_failures; /* counter never incremented */
+ break;
+
+ case ISIS_CIRC_REJADJS:
+ val = circuit->rej_adjacencies;
+ break;
+
+ case ISIS_CIRC_IDFIELDLENMISMATCHES:
+ val = circuit->id_len_mismatches;
+ break;
+
+ case ISIS_CIRC_MAXAREAADDRMISMATCHES:
+ val = circuit->max_area_addr_mismatches;
+ break;
+
+ case ISIS_CIRC_AUTHTYPEFAILS:
+ val = circuit->auth_type_failures;
+ break;
+
+ case ISIS_CIRC_AUTHFAILS:
+ val = circuit->auth_failures;
+ break;
+
+ case ISIS_CIRC_LANDESISCHANGES:
+ if (circuit->circ_type == CIRCUIT_T_P2P)
+ val = 0;
+ else
+ val = circuit->desig_changes[level - 1];
+ break;
+
+ default:
+ return NULL;
+ }
+
+ return SNMP_INTEGER(val);
+}
+
+static uint8_t *isis_snmp_find_isadj(struct variable *v, oid *name,
+ size_t *length, int exact, size_t *var_len,
+ WriteMethod **write_method)
+{
+ /* Index is circuit-id: 1-255 + adj-id: 1-... */
+ oid *oid_idx;
+ size_t oid_idx_len;
+ int res;
+ time_t val;
+ struct isis_adjacency *adj;
+ uint32_t up_ticks;
+ uint32_t delta_ticks;
+ time_t now_time;
+
+ /* Ring buffer to print SNPA */
+#define FORMAT_BUF_COUNT 4
+ static char snpa[FORMAT_BUF_COUNT][ISO_SYSID_STRLEN];
+ static size_t cur_buf = 0;
+
+ *write_method = NULL;
+
+ if (*length <= v->namelen) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else {
+ oid_idx = name + v->namelen;
+ oid_idx_len = *length - v->namelen;
+ }
+ if (exact) {
+ res = isis_snmp_adj_lookup_exact(oid_idx, oid_idx_len,
+ ISIS_SNMP_ADJ_DATA_NONE, &adj,
+ NULL, NULL, NULL);
+
+ if (!res || oid_idx_len != 2)
+ return NULL;
+
+ } else {
+ res = isis_snmp_adj_lookup_next(oid_idx, oid_idx_len,
+ ISIS_SNMP_ADJ_DATA_NONE, &adj,
+ NULL, NULL, NULL);
+ if (!res)
+ return NULL;
+
+ /* Copy the name out */
+ memcpy(name, v->name, v->namelen * sizeof(oid));
+
+ /* Append index */
+ name[v->namelen] = adj->circuit->snmp_id;
+ name[v->namelen + 1] = adj->snmp_idx;
+
+ /* Set length */
+ *length = v->namelen + 2;
+ }
+
+ switch (v->magic) {
+ case ISIS_ISADJ_STATE:
+ return SNMP_INTEGER(isis_snmp_adj_state(adj->adj_state));
+
+ case ISIS_ISADJ_3WAYSTATE:
+ return SNMP_INTEGER(adj->threeway_state);
+
+ case ISIS_ISADJ_NEIGHSNPAADDRESS: {
+ cur_buf = (cur_buf + 1) % FORMAT_BUF_COUNT;
+ snprintfrr(snpa[cur_buf], ISO_SYSID_STRLEN, "%pSY", adj->snpa);
+ *var_len = strlen(snpa[cur_buf]);
+ return (uint8_t *)snpa[cur_buf];
+ }
+
+ case ISIS_ISADJ_NEIGHSYSTYPE:
+ return SNMP_INTEGER(isis_snmp_adj_neightype(adj->sys_type));
+
+ case ISIS_ISADJ_NEIGHSYSID:
+ *var_len = sizeof(adj->sysid);
+ return adj->sysid;
+
+ case ISIS_ISADJ_NBREXTENDEDCIRCID:
+ return SNMP_INTEGER(adj->ext_circuit_id != 0 ? 1 : 0);
+
+ case ISIS_ISADJ_USAGE:
+ /* It seems that no value conversion is required */
+ return SNMP_INTEGER(adj->adj_usage);
+
+ case ISIS_ISADJ_HOLDTIMER:
+ /*
+ * It seems that we want remaining timer
+ */
+ if (adj->last_upd != 0) {
+ val = time(NULL);
+ if (val < (adj->last_upd + adj->hold_time))
+ return SNMP_INTEGER(adj->last_upd
+ + adj->hold_time - val);
+ }
+ /* Not running or just expired */
+ return SNMP_INTEGER(0);
+
+ case ISIS_ISADJ_NEIGHPRIORITY:
+ return SNMP_INTEGER(adj->prio[adj->level - 1]);
+
+ case ISIS_ISADJ_LASTUPTIME:
+ if (adj->flaps == 0)
+ return SNMP_INTEGER(0);
+
+ up_ticks = (uint32_t)netsnmp_get_agent_uptime();
+
+ now_time = time(NULL);
+
+ if (adj->last_flap >= now_time)
+ return SNMP_INTEGER(up_ticks);
+
+ delta_ticks = (now_time - adj->last_flap) * 10;
+
+ if (up_ticks < delta_ticks)
+ return SNMP_INTEGER(up_ticks);
+
+ return SNMP_INTEGER(up_ticks - delta_ticks);
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static uint8_t *isis_snmp_find_isadj_area(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ /* Index circuit-id: 1-255 + adj-id: 1-... */
+ oid *oid_idx;
+ size_t oid_idx_len;
+ int res;
+ struct isis_adjacency *adj;
+ oid data_idx;
+ uint8_t *data;
+ size_t data_len;
+
+ *write_method = NULL;
+
+ if (*length <= v->namelen) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else {
+ oid_idx = name + v->namelen;
+ oid_idx_len = *length - v->namelen;
+ }
+ if (exact) {
+ res = isis_snmp_adj_lookup_exact(oid_idx, oid_idx_len,
+ ISIS_SNMP_ADJ_DATA_AREA_ADDR,
+ &adj, NULL, &data, &data_len);
+
+ if (!res || oid_idx_len != 3)
+ return NULL;
+
+ } else {
+ res = isis_snmp_adj_lookup_next(
+ oid_idx, oid_idx_len, ISIS_SNMP_ADJ_DATA_AREA_ADDR,
+ &adj, &data_idx, &data, &data_len);
+ if (!res)
+ return NULL;
+
+ /* Copy the name out */
+ memcpy(name, v->name, v->namelen * sizeof(oid));
+
+ /* Append index */
+ name[v->namelen] = adj->circuit->snmp_id;
+ name[v->namelen + 1] = adj->snmp_idx;
+ name[v->namelen + 2] = data_idx;
+
+ /* Set length */
+ *length = v->namelen + 3;
+ }
+
+ switch (v->magic) {
+ case ISIS_ISADJAREA_ADDRESS:
+ *var_len = data_len;
+ return data;
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static uint8_t *isis_snmp_find_isadj_ipaddr(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ /* Index circuit-id 1-255 + adj-id 1-... */
+ oid *oid_idx;
+ size_t oid_idx_len;
+ int res;
+ struct isis_adjacency *adj;
+ oid data_idx;
+ uint8_t *data;
+ size_t data_len;
+
+ *write_method = NULL;
+
+ if (*length <= v->namelen) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else {
+ oid_idx = name + v->namelen;
+ oid_idx_len = *length - v->namelen;
+ }
+ if (exact) {
+ res = isis_snmp_adj_lookup_exact(oid_idx, oid_idx_len,
+ ISIS_SNMP_ADJ_DATA_IP_ADDR,
+ &adj, NULL, &data, &data_len);
+
+ if (!res || oid_idx_len != 3)
+ return NULL;
+ } else {
+ res = isis_snmp_adj_lookup_next(
+ oid_idx, oid_idx_len, ISIS_SNMP_ADJ_DATA_IP_ADDR, &adj,
+ &data_idx, &data, &data_len);
+ if (!res)
+ return NULL;
+
+ /* Copy the name out */
+ memcpy(name, v->name, v->namelen * sizeof(oid));
+
+ /* Append index */
+ name[v->namelen] = adj->circuit->snmp_id;
+ name[v->namelen + 1] = adj->snmp_idx;
+ name[v->namelen + 2] = data_idx;
+
+ /* Set length */
+ *length = v->namelen + 3;
+ }
+
+ switch (v->magic) {
+ case ISIS_ISADJIPADDR_TYPE:
+ if (data_len == 4)
+ return SNMP_INTEGER(ISIS_SNMP_INET_TYPE_V4);
+
+ return SNMP_INTEGER(ISIS_SNMP_INET_TYPE_V6);
+
+ case ISIS_ISADJIPADDR_ADDRESS:
+ *var_len = data_len;
+ return data;
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static uint8_t *isis_snmp_find_isadj_prot_supp(struct variable *v, oid *name,
+ size_t *length, int exact,
+ size_t *var_len,
+ WriteMethod **write_method)
+{
+ /* Index circuit-id 1-255 + adj-id 1-... */
+ oid *oid_idx;
+ size_t oid_idx_len;
+ int res;
+ struct isis_adjacency *adj;
+ oid data_idx;
+ uint8_t *data;
+ size_t data_len;
+
+ *write_method = NULL;
+
+ if (*length <= v->namelen) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) {
+ oid_idx = NULL;
+ oid_idx_len = 0;
+ } else {
+ oid_idx = name + v->namelen;
+ oid_idx_len = *length - v->namelen;
+ }
+ if (exact) {
+ res = isis_snmp_adj_lookup_exact(oid_idx, oid_idx_len,
+ ISIS_SNMP_ADJ_DATA_PROTO, &adj,
+ NULL, &data, &data_len);
+
+ if (!res || oid_idx_len != 3)
+ return NULL;
+
+ } else {
+ res = isis_snmp_adj_lookup_next(oid_idx, oid_idx_len,
+ ISIS_SNMP_ADJ_DATA_PROTO, &adj,
+ &data_idx, &data, &data_len);
+ if (!res)
+ return NULL;
+
+ /* Copy the name out */
+ memcpy(name, v->name, v->namelen * sizeof(oid));
+
+ /* Append index */
+ name[v->namelen] = adj->circuit->snmp_id;
+ name[v->namelen + 1] = adj->snmp_idx;
+ name[v->namelen + 2] = data_idx;
+
+ /* Set length */
+ *length = v->namelen + 3;
+ }
+
+ switch (v->magic) {
+ case ISIS_ISADJPROTSUPP_PROTOCOL:
+ return SNMP_INTEGER(*data);
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+
+/* Register ISIS-MIB. */
+static int isis_snmp_init(struct event_loop *tm)
+{
+ struct isis_func_to_prefix *h2f = isis_func_to_prefix_arr;
+ struct variable *v;
+
+ for (size_t off = 0; off < isis_var_count; off++) {
+ v = &isis_var_arr[off];
+
+ if (v->findVar != h2f->ihtp_func) {
+ /* Next table */
+ h2f++;
+ assert(h2f < (isis_func_to_prefix_arr
+ + isis_func_to_prefix_count));
+ assert(v->findVar == h2f->ihtp_func);
+ }
+
+ v->namelen = h2f->ihtp_pref_len + 1;
+ memcpy(v->name, h2f->ihtp_pref_oid,
+ h2f->ihtp_pref_len * sizeof(oid));
+ v->name[h2f->ihtp_pref_len] = v->magic;
+ }
+
+
+ smux_init(tm);
+ REGISTER_MIB("mibII/isis", isis_var_arr, variable, isis_oid);
+ return 0;
+}
+
+/*
+ * ISIS notification functions: we have one function per notification
+ */
+static int isis_snmp_trap_throttle(oid trap_id)
+{
+ time_t time_now;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ if (isis == NULL || !isis->snmp_notifications || !smux_enabled())
+ return 0;
+
+ time_now = time(NULL);
+
+ if ((isis_snmp_trap_timestamp[trap_id] + 5) > time_now)
+ /* Throttle trap rate at 1 in 5 secs */
+ return 0;
+
+ isis_snmp_trap_timestamp[trap_id] = time_now;
+ return 1;
+}
+
+static int isis_snmp_db_overload_update(const struct isis_area *area)
+{
+ netsnmp_variable_list *notification_vars;
+ long val;
+ uint32_t off;
+
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_DB_OVERLOAD))
+ return 0;
+
+ notification_vars = NULL;
+
+ /* Put in trap value */
+ snmp_varlist_add_variable(&notification_vars, isis_snmp_trap_var,
+ array_size(isis_snmp_trap_var), ASN_OBJECT_ID,
+ (uint8_t *)&isis_snmp_trap_val_db_overload,
+ sizeof(isis_snmp_trap_val_db_overload));
+
+ /* Prepare data */
+ val = area->is_type;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_sys_level_index,
+ array_size(isis_snmp_trap_data_var_sys_level_index), INTEGER,
+ (uint8_t *)&val, sizeof(val));
+
+ /* Patch sys_level_state with proper index */
+ off = array_size(isis_snmp_trap_data_var_sys_level_state) - 1;
+ isis_snmp_trap_data_var_sys_level_state[off] = val;
+
+ /* Prepare data */
+ if (area->overload_bit)
+ val = ISIS_SNMP_LEVEL_STATE_OVERLOADED;
+ else
+ val = ISIS_SNMP_LEVEL_STATE_ON;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_sys_level_state,
+ array_size(isis_snmp_trap_data_var_sys_level_state), INTEGER,
+ (uint8_t *)&val, sizeof(val));
+
+ send_v2trap(notification_vars);
+ snmp_free_varbind(notification_vars);
+ smux_events_update();
+ return 0;
+}
+
+static int isis_snmp_lsp_exceed_max_update(const struct isis_area *area,
+ const uint8_t *lsp_id)
+{
+ netsnmp_variable_list *notification_vars;
+ long val;
+
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_LSP_EXCEED_MAX))
+ return 0;
+
+ notification_vars = NULL;
+
+ /* Put in trap value */
+ snmp_varlist_add_variable(&notification_vars, isis_snmp_trap_var,
+ array_size(isis_snmp_trap_var), ASN_OBJECT_ID,
+ (uint8_t *)&isis_snmp_trap_val_lsp_exceed_max,
+ sizeof(isis_snmp_trap_val_lsp_exceed_max));
+
+ /* Prepare data */
+ val = area->is_type;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_sys_level_index,
+ array_size(isis_snmp_trap_data_var_sys_level_index), INTEGER,
+ (uint8_t *)&val, sizeof(val));
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_pdu_lsp_id,
+ array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id,
+ ISIS_SYS_ID_LEN + 2);
+
+ send_v2trap(notification_vars);
+ snmp_free_varbind(notification_vars);
+ smux_events_update();
+ return 0;
+}
+
+
+/*
+ * A common function to handle popular combination of trap objects
+ * isisNotificationSysLevelIndex,
+ * optional-object-a
+ * isisNotificationCircIfIndex,
+ * optional-object-b
+ */
+static void isis_snmp_update_worker_a(const struct isis_circuit *circuit,
+ oid trap_id, const oid *oid_a,
+ size_t oid_a_len, uint8_t type_a,
+ const void *data_a, size_t data_a_len,
+ const oid *oid_b, size_t oid_b_len,
+ uint8_t type_b, const void *data_b,
+ size_t data_b_len)
+{
+ netsnmp_variable_list *notification_vars = NULL;
+ oid var_name[MAX_OID_LEN];
+ size_t var_count;
+ long val;
+
+ /* Sanity */
+ if (trap_id != ISIS_TRAP_ID_LEN_MISMATCH
+ && trap_id != ISIS_TRAP_MAX_AREA_ADDR_MISMATCH
+ && trap_id != ISIS_TRAP_OWN_LSP_PURGE
+ && trap_id != ISIS_TRAP_SEQNO_SKIPPED
+ && trap_id != ISIS_TRAP_AUTHEN_TYPE_FAILURE
+ && trap_id != ISIS_TRAP_AUTHEN_FAILURE
+ && trap_id != ISIS_TRAP_REJ_ADJACENCY)
+ return;
+
+ /* Put in trap value */
+ memcpy(var_name, isis_snmp_notifications,
+ sizeof(isis_snmp_notifications));
+ var_count = array_size(isis_snmp_notifications);
+ var_name[var_count++] = trap_id;
+
+ /* Put in trap value */
+ snmp_varlist_add_variable(&notification_vars, isis_snmp_trap_var,
+ array_size(isis_snmp_trap_var), ASN_OBJECT_ID,
+ (uint8_t *)var_name, var_count * sizeof(oid));
+
+ val = circuit->is_type;
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_sys_level_index,
+ array_size(isis_snmp_trap_data_var_sys_level_index), INTEGER,
+ (uint8_t *)&val, sizeof(val));
+
+ if (oid_a_len != 0) {
+ if (oid_a == NULL || data_a == NULL || data_a_len == 0)
+ return;
+
+ snmp_varlist_add_variable(&notification_vars, oid_a, oid_a_len,
+ type_a, (uint8_t *)data_a,
+ data_a_len);
+ }
+
+ if (circuit->interface == NULL)
+ val = 0;
+ else
+ val = circuit->interface->ifindex;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_circ_if_index,
+ array_size(isis_snmp_trap_data_var_circ_if_index), UNSIGNED32,
+ (uint8_t *)&val, sizeof(val));
+
+
+ if (oid_b_len != 0) {
+ if (oid_b == NULL || data_b == NULL || data_b_len == 0)
+ return;
+
+ snmp_varlist_add_variable(&notification_vars, oid_b, oid_b_len,
+ type_b, (uint8_t *)data_b,
+ data_b_len);
+ }
+
+ send_v2trap(notification_vars);
+ snmp_free_varbind(notification_vars);
+ smux_events_update();
+}
+
+/*
+ * A common function to handle popular combination of trap objects
+ * isisNotificationSysLevelIndex,
+ * isisNotificationCircIfIndex,
+ * optional-var-a
+ * optional-var-b
+ *
+ * Note: the only difference with worker_a is order of circ-if-index vs
+ * optional-var-a
+ */
+static void isis_snmp_update_worker_b(const struct isis_circuit *circuit,
+ oid trap_id, const oid *oid_a,
+ size_t oid_a_len, uint8_t type_a,
+ const void *data_a, size_t data_a_len,
+ const oid *oid_b, size_t oid_b_len,
+ uint8_t type_b, const void *data_b,
+ size_t data_b_len)
+{
+ netsnmp_variable_list *notification_vars = NULL;
+ oid var_name[MAX_OID_LEN];
+ size_t var_count;
+ long val;
+
+ /* Sanity */
+ if (trap_id != ISIS_TRAP_VERSION_SKEW
+ && trap_id != ISIS_TRAP_LSP_TOO_LARGE
+ && trap_id != ISIS_TRAP_ADJ_STATE_CHANGE)
+ return;
+
+ /* Put in trap value */
+ memcpy(var_name, isis_snmp_notifications,
+ sizeof(isis_snmp_notifications));
+ var_count = array_size(isis_snmp_notifications);
+ var_name[var_count++] = trap_id;
+
+ /* Put in trap value */
+ snmp_varlist_add_variable(&notification_vars, isis_snmp_trap_var,
+ array_size(isis_snmp_trap_var), ASN_OBJECT_ID,
+ (uint8_t *)var_name, var_count * sizeof(oid));
+
+ val = circuit->is_type;
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_sys_level_index,
+ array_size(isis_snmp_trap_data_var_sys_level_index), INTEGER,
+ (uint8_t *)&val, sizeof(val));
+
+ if (circuit->interface == NULL)
+ val = 0;
+ else
+ val = circuit->interface->ifindex;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_circ_if_index,
+ array_size(isis_snmp_trap_data_var_circ_if_index), UNSIGNED32,
+ (uint8_t *)&val, sizeof(val));
+
+
+ if (oid_a_len != 0) {
+ if (oid_a == NULL || data_a == NULL || data_a_len == 0)
+ return;
+
+ snmp_varlist_add_variable(&notification_vars, oid_a, oid_a_len,
+ type_a, (uint8_t *)data_a,
+ data_a_len);
+ }
+
+ if (oid_b_len != 0) {
+ if (oid_b == NULL || data_b == NULL || data_b_len == 0)
+ return;
+
+ snmp_varlist_add_variable(&notification_vars, oid_b, oid_b_len,
+ type_b, (uint8_t *)data_b,
+ data_b_len);
+ }
+
+ send_v2trap(notification_vars);
+ snmp_free_varbind(notification_vars);
+ smux_events_update();
+}
+
+
+static int isis_snmp_id_len_mismatch_update(const struct isis_circuit *circuit,
+ uint8_t rcv_id, const char *raw_pdu,
+ size_t raw_pdu_len)
+{
+ long val;
+
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_ID_LEN_MISMATCH))
+ return 0;
+
+ val = rcv_id;
+
+ if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN)
+ raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN;
+
+ isis_snmp_update_worker_a(
+ circuit, ISIS_TRAP_ID_LEN_MISMATCH,
+ isis_snmp_trap_data_var_pdu_field_len,
+ array_size(isis_snmp_trap_data_var_pdu_field_len), UNSIGNED32,
+ &val, sizeof(val), isis_snmp_trap_data_var_pdu_fragment,
+ array_size(isis_snmp_trap_data_var_pdu_fragment), STRING,
+ raw_pdu, raw_pdu_len);
+ return 0;
+}
+
+static int
+isis_snmp_max_area_addr_mismatch_update(const struct isis_circuit *circuit,
+ uint8_t max_addrs, const char *raw_pdu,
+ size_t raw_pdu_len)
+{
+ long val;
+
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_MAX_AREA_ADDR_MISMATCH))
+ return 0;
+
+ val = max_addrs;
+
+ if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN)
+ raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN;
+
+ isis_snmp_update_worker_a(
+ circuit, ISIS_TRAP_MAX_AREA_ADDR_MISMATCH,
+ isis_snmp_trap_data_var_pdu_max_area_addr,
+ array_size(isis_snmp_trap_data_var_pdu_max_area_addr),
+ UNSIGNED32, &val, sizeof(val),
+ isis_snmp_trap_data_var_pdu_fragment,
+ array_size(isis_snmp_trap_data_var_pdu_fragment), STRING,
+ raw_pdu, raw_pdu_len);
+ return 0;
+}
+
+static int isis_snmp_own_lsp_purge_update(const struct isis_circuit *circuit,
+ const uint8_t *lsp_id)
+{
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_OWN_LSP_PURGE))
+ return 0;
+
+ isis_snmp_update_worker_a(
+ circuit, ISIS_TRAP_OWN_LSP_PURGE, NULL, 0, STRING, NULL, 0,
+ isis_snmp_trap_data_var_pdu_lsp_id,
+ array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id,
+ ISIS_SYS_ID_LEN + 2);
+ return 0;
+}
+
+static int isis_snmp_seqno_skipped_update(const struct isis_circuit *circuit,
+ const uint8_t *lsp_id)
+{
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_SEQNO_SKIPPED))
+ return 0;
+
+ isis_snmp_update_worker_a(
+ circuit, ISIS_TRAP_SEQNO_SKIPPED, NULL, 0, STRING, NULL, 0,
+ isis_snmp_trap_data_var_pdu_lsp_id,
+ array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id,
+ ISIS_SYS_ID_LEN + 2);
+ return 0;
+}
+
+static int
+isis_snmp_authentication_type_failure_update(const struct isis_circuit *circuit,
+ const char *raw_pdu,
+ size_t raw_pdu_len)
+{
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_AUTHEN_TYPE_FAILURE))
+ return 0;
+
+ if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN)
+ raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN;
+
+ isis_snmp_update_worker_a(
+ circuit, ISIS_TRAP_AUTHEN_TYPE_FAILURE, NULL, 0, STRING, NULL,
+ 0, isis_snmp_trap_data_var_pdu_fragment,
+ array_size(isis_snmp_trap_data_var_pdu_fragment), STRING,
+ raw_pdu, raw_pdu_len);
+ return 0;
+}
+
+static int
+isis_snmp_authentication_failure_update(const struct isis_circuit *circuit,
+ char const *raw_pdu, size_t raw_pdu_len)
+{
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_AUTHEN_FAILURE))
+ return 0;
+
+ if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN)
+ raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN;
+
+ isis_snmp_update_worker_a(
+ circuit, ISIS_TRAP_AUTHEN_FAILURE, NULL, 0, STRING, NULL, 0,
+ isis_snmp_trap_data_var_pdu_fragment,
+ array_size(isis_snmp_trap_data_var_pdu_fragment), STRING,
+ raw_pdu, raw_pdu_len);
+ return 0;
+}
+
+static int isis_snmp_version_skew_update(const struct isis_circuit *circuit,
+ uint8_t version, const char *raw_pdu,
+ size_t raw_pdu_len)
+{
+ long val;
+
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_VERSION_SKEW))
+ return 0;
+
+ val = version;
+
+ if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN)
+ raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN;
+
+ isis_snmp_update_worker_b(
+ circuit, ISIS_TRAP_VERSION_SKEW,
+ isis_snmp_trap_data_var_pdu_proto_ver,
+ array_size(isis_snmp_trap_data_var_pdu_proto_ver), UNSIGNED32,
+ &val, sizeof(val), isis_snmp_trap_data_var_pdu_fragment,
+ array_size(isis_snmp_trap_data_var_pdu_fragment), STRING,
+ raw_pdu, raw_pdu_len);
+ return 0;
+}
+
+static int isis_snmp_area_mismatch_update(const struct isis_circuit *circuit,
+ const char *raw_pdu,
+ size_t raw_pdu_len)
+{
+ /*
+ * This is a special case because
+ * it does not include isisNotificationSysLevelIndex
+ */
+ netsnmp_variable_list *notification_vars;
+ long val;
+
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_AREA_MISMATCH))
+ return 0;
+
+ notification_vars = NULL;
+
+ /* Put in trap value */
+ snmp_varlist_add_variable(&notification_vars, isis_snmp_trap_var,
+ array_size(isis_snmp_trap_var), ASN_OBJECT_ID,
+ (uint8_t *)&isis_snmp_trap_val_area_mismatch,
+ sizeof(isis_snmp_trap_val_area_mismatch));
+
+
+ if (circuit->interface == NULL)
+ val = 0;
+ else
+ val = circuit->interface->ifindex;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_circ_if_index,
+ array_size(isis_snmp_trap_data_var_circ_if_index), UNSIGNED32,
+ (uint8_t *)&val, sizeof(val));
+
+
+ if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN)
+ raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_pdu_fragment,
+ array_size(isis_snmp_trap_data_var_pdu_fragment), STRING,
+ raw_pdu, raw_pdu_len);
+
+ send_v2trap(notification_vars);
+ snmp_free_varbind(notification_vars);
+ smux_events_update();
+
+ return 0;
+}
+
+static int isis_snmp_reject_adjacency_update(const struct isis_circuit *circuit,
+ const char *raw_pdu,
+ size_t raw_pdu_len)
+{
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_REJ_ADJACENCY))
+ return 0;
+
+ if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN)
+ raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN;
+
+ isis_snmp_update_worker_a(
+ circuit, ISIS_TRAP_REJ_ADJACENCY, NULL, 0, STRING, NULL, 0,
+ isis_snmp_trap_data_var_pdu_fragment,
+ array_size(isis_snmp_trap_data_var_pdu_fragment), STRING,
+ raw_pdu, raw_pdu_len);
+ return 0;
+}
+
+static int isis_snmp_lsp_too_large_update(const struct isis_circuit *circuit,
+ uint32_t pdu_size,
+ const uint8_t *lsp_id)
+{
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_LSP_TOO_LARGE))
+ return 0;
+
+ isis_snmp_update_worker_b(
+ circuit, ISIS_TRAP_LSP_TOO_LARGE,
+ isis_snmp_trap_data_var_pdu_lsp_size,
+ array_size(isis_snmp_trap_data_var_pdu_lsp_size), UNSIGNED32,
+ &pdu_size, sizeof(pdu_size), isis_snmp_trap_data_var_pdu_lsp_id,
+ array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id,
+ ISIS_SYS_ID_LEN + 2);
+ return 0;
+}
+
+
+static int isis_snmp_adj_state_change_update(const struct isis_adjacency *adj)
+{
+ uint8_t lsp_id[ISIS_SYS_ID_LEN + 2];
+ long val;
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ if (isis == NULL || !isis->snmp_notifications || !smux_enabled())
+ return 0;
+
+ /* Prepare data */
+ memcpy(lsp_id, adj->sysid, ISIS_SYS_ID_LEN);
+ lsp_id[ISIS_SYS_ID_LEN] = 0;
+ lsp_id[ISIS_SYS_ID_LEN + 1] = 0;
+
+ val = isis_snmp_adj_state(adj->adj_state);
+
+ isis_snmp_update_worker_b(
+ adj->circuit, ISIS_TRAP_ADJ_STATE_CHANGE,
+ isis_snmp_trap_data_var_pdu_lsp_id,
+ array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id,
+ ISIS_SYS_ID_LEN + 2, isis_snmp_trap_data_var_adj_state,
+ array_size(isis_snmp_trap_data_var_adj_state), INTEGER, &val,
+ sizeof(val));
+ return 0;
+}
+
+static int isis_snmp_lsp_error_update(const struct isis_circuit *circuit,
+ const uint8_t *lsp_id,
+ char const *raw_pdu, size_t raw_pdu_len)
+{
+ /*
+ * This is a special case because
+ * it have more variables
+ */
+ netsnmp_variable_list *notification_vars;
+ long val;
+
+ if (!isis_snmp_trap_throttle(ISIS_TRAP_LSP_ERROR))
+ return 0;
+
+ notification_vars = NULL;
+
+ /* Put in trap value */
+ snmp_varlist_add_variable(&notification_vars, isis_snmp_trap_var,
+ array_size(isis_snmp_trap_var), ASN_OBJECT_ID,
+ (uint8_t *)&isis_snmp_trap_val_lsp_error,
+ sizeof(isis_snmp_trap_val_lsp_error));
+
+ /* Prepare data */
+ val = circuit->is_type;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_sys_level_index,
+ array_size(isis_snmp_trap_data_var_sys_level_index), INTEGER,
+ (uint8_t *)&val, sizeof(val));
+
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_pdu_lsp_id,
+ array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id,
+ ISIS_SYS_ID_LEN + 2);
+
+ /* Prepare data */
+ if (circuit->interface == NULL)
+ val = 0;
+ else
+ val = circuit->interface->ifindex;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_circ_if_index,
+ array_size(isis_snmp_trap_data_var_circ_if_index), UNSIGNED32,
+ (uint8_t *)&val, sizeof(val));
+
+ /* Prepare data */
+ if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN)
+ raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_pdu_fragment,
+ array_size(isis_snmp_trap_data_var_pdu_fragment), STRING,
+ raw_pdu, raw_pdu_len);
+
+ /* Prepare data */
+ val = 0;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_error_offset,
+ array_size(isis_snmp_trap_data_var_error_offset), UNSIGNED32,
+ (uint8_t *)&val, sizeof(val));
+
+ /* Prepare data */
+ val = 0;
+
+ snmp_varlist_add_variable(
+ &notification_vars, isis_snmp_trap_data_var_error_tlv_type,
+ array_size(isis_snmp_trap_data_var_error_tlv_type), UNSIGNED32,
+ (uint8_t *)&val, sizeof(val));
+
+ send_v2trap(notification_vars);
+ snmp_free_varbind(notification_vars);
+ smux_events_update();
+ return 0;
+}
+
+
+static int isis_snmp_module_init(void)
+{
+ hook_register(isis_hook_db_overload, isis_snmp_db_overload_update);
+ hook_register(isis_hook_lsp_exceed_max,
+ isis_snmp_lsp_exceed_max_update);
+ hook_register(isis_hook_id_len_mismatch,
+ isis_snmp_id_len_mismatch_update);
+ hook_register(isis_hook_max_area_addr_mismatch,
+ isis_snmp_max_area_addr_mismatch_update);
+ hook_register(isis_hook_own_lsp_purge, isis_snmp_own_lsp_purge_update);
+ hook_register(isis_hook_seqno_skipped, isis_snmp_seqno_skipped_update);
+ hook_register(isis_hook_authentication_type_failure,
+ isis_snmp_authentication_type_failure_update);
+ hook_register(isis_hook_authentication_failure,
+ isis_snmp_authentication_failure_update);
+ hook_register(isis_hook_version_skew, isis_snmp_version_skew_update);
+ hook_register(isis_hook_area_mismatch, isis_snmp_area_mismatch_update);
+ hook_register(isis_hook_reject_adjacency,
+ isis_snmp_reject_adjacency_update);
+ hook_register(isis_hook_lsp_too_large, isis_snmp_lsp_too_large_update);
+ hook_register(isis_hook_adj_state_change,
+ isis_snmp_adj_state_change_update);
+ hook_register(isis_hook_lsp_error, isis_snmp_lsp_error_update);
+ hook_register(isis_circuit_new_hook, isis_circuit_snmp_id_gen);
+ hook_register(isis_circuit_del_hook, isis_circuit_snmp_id_free);
+
+ hook_register(frr_late_init, isis_snmp_init);
+ return 0;
+}
+
+FRR_MODULE_SETUP(
+ .name = "isis_snmp",
+ .version = FRR_VERSION,
+ .description = "isis AgentX SNMP module",
+ .init = isis_snmp_module_init,
+);
diff --git a/isisd/isis_spf.c b/isisd/isis_spf.c
new file mode 100644
index 0000000..a2230cd
--- /dev/null
+++ b/isisd/isis_spf.c
@@ -0,0 +1,3363 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_spf.c
+ * The SPT algorithm
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ * Copyright (C) 2017 Christian Franke <chris@opensourcerouting.org>
+ */
+
+#include <zebra.h>
+
+#include "frrevent.h"
+#include "linklist.h"
+#include "vty.h"
+#include "log.h"
+#include "command.h"
+#include "termtable.h"
+#include "memory.h"
+#include "prefix.h"
+#include "filter.h"
+#include "if.h"
+#include "hash.h"
+#include "table.h"
+#include "spf_backoff.h"
+#include "srcdest_table.h"
+#include "vrf.h"
+#include "lib/json.h"
+
+#include "isis_errors.h"
+#include "isis_constants.h"
+#include "isis_common.h"
+#include "isis_flags.h"
+#include "isisd.h"
+#include "isis_misc.h"
+#include "isis_adjacency.h"
+#include "isis_circuit.h"
+#include "isis_pdu.h"
+#include "isis_lsp.h"
+#include "isis_dynhn.h"
+#include "isis_spf.h"
+#include "isis_route.h"
+#include "isis_csm.h"
+#include "isis_mt.h"
+#include "isis_tlvs.h"
+#include "isis_flex_algo.h"
+#include "isis_zebra.h"
+#include "fabricd.h"
+#include "isis_spf_private.h"
+
+DEFINE_MTYPE_STATIC(ISISD, ISIS_SPFTREE, "ISIS SPFtree");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_SPF_RUN, "ISIS SPF Run Info");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_SPF_ADJ, "ISIS SPF Adjacency");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_VERTEX, "ISIS vertex");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_VERTEX_ADJ, "ISIS SPF Vertex Adjacency");
+
+static void spf_adj_list_parse_lsp(struct isis_spftree *spftree,
+ struct list *adj_list, struct isis_lsp *lsp,
+ const uint8_t *pseudo_nodeid,
+ uint32_t pseudo_metric);
+
+/*
+ * supports the given af ?
+ */
+static bool speaks(uint8_t *protocols, uint8_t count, int family)
+{
+ for (uint8_t i = 0; i < count; i++) {
+ if (family == AF_INET && protocols[i] == NLPID_IP)
+ return true;
+ if (family == AF_INET6 && protocols[i] == NLPID_IPV6)
+ return true;
+ }
+ return false;
+}
+
+struct isis_spf_run {
+ struct isis_area *area;
+ int level;
+};
+
+/* 7.2.7 */
+static void remove_excess_adjs(struct list *adjs)
+{
+ struct listnode *node, *excess = NULL;
+ struct isis_vertex_adj *vadj, *candidate = NULL;
+ int comp;
+
+ for (ALL_LIST_ELEMENTS_RO(adjs, node, vadj)) {
+ struct isis_adjacency *adj, *candidate_adj;
+
+ adj = vadj->sadj->adj;
+ assert(adj);
+
+ if (excess == NULL)
+ excess = node;
+ candidate = listgetdata(excess);
+ candidate_adj = candidate->sadj->adj;
+
+ if (candidate_adj->sys_type < adj->sys_type) {
+ excess = node;
+ continue;
+ }
+ if (candidate_adj->sys_type > adj->sys_type)
+ continue;
+
+ comp = memcmp(candidate_adj->sysid, adj->sysid,
+ ISIS_SYS_ID_LEN);
+ if (comp > 0) {
+ excess = node;
+ continue;
+ }
+ if (comp < 0)
+ continue;
+
+ if (candidate_adj->circuit->idx > adj->circuit->idx) {
+ excess = node;
+ continue;
+ }
+
+ if (candidate_adj->circuit->idx < adj->circuit->idx)
+ continue;
+
+ comp = memcmp(candidate_adj->snpa, adj->snpa, ETH_ALEN);
+ if (comp > 0) {
+ excess = node;
+ continue;
+ }
+ }
+
+ list_delete_node(adjs, excess);
+
+ return;
+}
+
+const char *vtype2string(enum vertextype vtype)
+{
+ switch (vtype) {
+ case VTYPE_PSEUDO_IS:
+ return "pseudo_IS";
+ case VTYPE_PSEUDO_TE_IS:
+ return "pseudo_TE-IS";
+ case VTYPE_NONPSEUDO_IS:
+ return "IS";
+ case VTYPE_NONPSEUDO_TE_IS:
+ return "TE-IS";
+ case VTYPE_ES:
+ return "ES";
+ case VTYPE_IPREACH_INTERNAL:
+ return "IP internal";
+ case VTYPE_IPREACH_EXTERNAL:
+ return "IP external";
+ case VTYPE_IPREACH_TE:
+ return "IP TE";
+ case VTYPE_IP6REACH_INTERNAL:
+ return "IP6 internal";
+ case VTYPE_IP6REACH_EXTERNAL:
+ return "IP6 external";
+ default:
+ return "UNKNOWN";
+ }
+ return NULL; /* Not reached */
+}
+
+const char *vid2string(const struct isis_vertex *vertex, char *buff, int size)
+{
+ if (VTYPE_IS(vertex->type) || VTYPE_ES(vertex->type)) {
+ const char *hostname = print_sys_hostname(vertex->N.id);
+ strlcpy(buff, hostname, size);
+ return buff;
+ }
+
+ if (VTYPE_IP(vertex->type)) {
+ srcdest2str(&vertex->N.ip.p.dest, &vertex->N.ip.p.src, buff,
+ size);
+ return buff;
+ }
+
+ return "UNKNOWN";
+}
+
+static bool prefix_sid_cmp(const void *value1, const void *value2)
+{
+ const struct isis_vertex *c1 = value1;
+ const struct isis_vertex *c2 = value2;
+
+ if (CHECK_FLAG(c1->N.ip.sr.sid.flags,
+ ISIS_PREFIX_SID_VALUE | ISIS_PREFIX_SID_LOCAL)
+ != CHECK_FLAG(c2->N.ip.sr.sid.flags,
+ ISIS_PREFIX_SID_VALUE | ISIS_PREFIX_SID_LOCAL))
+ return false;
+
+ return c1->N.ip.sr.sid.value == c2->N.ip.sr.sid.value;
+}
+
+static unsigned int prefix_sid_key_make(const void *value)
+{
+ const struct isis_vertex *vertex = value;
+
+ return jhash_1word(vertex->N.ip.sr.sid.value, 0);
+}
+
+struct isis_vertex *isis_spf_prefix_sid_lookup(struct isis_spftree *spftree,
+ struct isis_prefix_sid *psid)
+{
+ struct isis_vertex lookup = {};
+
+ lookup.N.ip.sr.sid = *psid;
+ return hash_lookup(spftree->prefix_sids, &lookup);
+}
+
+void isis_vertex_adj_free(void *arg)
+{
+ struct isis_vertex_adj *vadj = arg;
+
+ XFREE(MTYPE_ISIS_VERTEX_ADJ, vadj);
+}
+
+static struct isis_vertex *isis_vertex_new(struct isis_spftree *spftree,
+ void *id,
+ enum vertextype vtype)
+{
+ struct isis_vertex *vertex;
+
+ vertex = XCALLOC(MTYPE_ISIS_VERTEX, sizeof(struct isis_vertex));
+
+ isis_vertex_id_init(vertex, id, vtype);
+
+ vertex->Adj_N = list_new();
+ vertex->Adj_N->del = isis_vertex_adj_free;
+ vertex->parents = list_new();
+
+ if (CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC)) {
+ vertex->firsthops = hash_create(isis_vertex_queue_hash_key,
+ isis_vertex_queue_hash_cmp,
+ NULL);
+ }
+
+ return vertex;
+}
+
+void isis_vertex_del(struct isis_vertex *vertex)
+{
+ list_delete(&vertex->Adj_N);
+ list_delete(&vertex->parents);
+ hash_clean_and_free(&vertex->firsthops, NULL);
+
+ memset(vertex, 0, sizeof(struct isis_vertex));
+ XFREE(MTYPE_ISIS_VERTEX, vertex);
+}
+
+struct isis_vertex_adj *
+isis_vertex_adj_add(struct isis_spftree *spftree, struct isis_vertex *vertex,
+ struct list *vadj_list, struct isis_spf_adj *sadj,
+ struct isis_prefix_sid *psid, bool last_hop)
+{
+ struct isis_vertex_adj *vadj;
+
+ vadj = XCALLOC(MTYPE_ISIS_VERTEX_ADJ, sizeof(*vadj));
+ vadj->sadj = sadj;
+ if (spftree->area->srdb.enabled && psid) {
+ if (vertex->N.ip.sr.present
+ && vertex->N.ip.sr.sid.value != psid->value)
+ zlog_warn(
+ "ISIS-SPF: ignoring different Prefix-SID for route %pFX",
+ &vertex->N.ip.p.dest);
+ else {
+ vadj->sr.sid = *psid;
+ vadj->sr.label = sr_prefix_out_label(
+ spftree->lspdb, vertex->N.ip.p.dest.family,
+ psid, sadj->id, last_hop);
+ if (vadj->sr.label != MPLS_INVALID_LABEL)
+ vadj->sr.present = true;
+ }
+ }
+ listnode_add(vadj_list, vadj);
+
+ return vadj;
+}
+
+static void isis_vertex_adj_del(struct isis_vertex *vertex,
+ struct isis_adjacency *adj)
+{
+ struct isis_vertex_adj *vadj;
+ struct listnode *node, *nextnode;
+
+ if (!vertex)
+ return;
+
+ for (ALL_LIST_ELEMENTS(vertex->Adj_N, node, nextnode, vadj)) {
+ if (vadj->sadj->adj == adj) {
+ listnode_delete(vertex->Adj_N, vadj);
+ isis_vertex_adj_free(vadj);
+ }
+ }
+ return;
+}
+
+bool isis_vertex_adj_exists(const struct isis_spftree *spftree,
+ const struct isis_vertex *vertex,
+ const struct isis_spf_adj *sadj)
+{
+ struct isis_vertex_adj *tmp;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(vertex->Adj_N, node, tmp)) {
+ if (CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)) {
+ if (memcmp(sadj->id, tmp->sadj->id, sizeof(sadj->id))
+ == 0)
+ return true;
+ } else {
+ if (sadj->adj == tmp->sadj->adj)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void isis_spf_adj_free(void *arg)
+{
+ struct isis_spf_adj *sadj = arg;
+
+ XFREE(MTYPE_ISIS_SPF_ADJ, sadj);
+}
+
+static void _isis_spftree_init(struct isis_spftree *tree)
+{
+ isis_vertex_queue_init(&tree->tents, "IS-IS SPF tents", true);
+ isis_vertex_queue_init(&tree->paths, "IS-IS SPF paths", false);
+ tree->route_table = srcdest_table_init();
+ tree->route_table->cleanup = isis_route_node_cleanup;
+ tree->route_table->info = isis_route_table_info_alloc(tree->algorithm);
+ tree->route_table_backup = srcdest_table_init();
+ tree->route_table_backup->info =
+ isis_route_table_info_alloc(tree->algorithm);
+ tree->route_table_backup->cleanup = isis_route_node_cleanup;
+ tree->prefix_sids = hash_create(prefix_sid_key_make, prefix_sid_cmp,
+ "SR Prefix-SID Entries");
+ tree->sadj_list = list_new();
+ tree->sadj_list->del = isis_spf_adj_free;
+ isis_rlfa_list_init(tree);
+ tree->lfa.remote.pc_spftrees = list_new();
+ tree->lfa.remote.pc_spftrees->del = (void (*)(void *))isis_spftree_del;
+ if (tree->type == SPF_TYPE_RLFA || tree->type == SPF_TYPE_TI_LFA) {
+ isis_spf_node_list_init(&tree->lfa.p_space);
+ isis_spf_node_list_init(&tree->lfa.q_space);
+ }
+}
+
+struct isis_spftree *
+isis_spftree_new(struct isis_area *area, struct lspdb_head *lspdb,
+ const uint8_t *sysid, int level, enum spf_tree_id tree_id,
+ enum spf_type type, uint8_t flags, uint8_t algorithm)
+{
+ struct isis_spftree *tree;
+
+ tree = XCALLOC(MTYPE_ISIS_SPFTREE, sizeof(struct isis_spftree));
+
+ tree->area = area;
+ tree->lspdb = lspdb;
+ tree->last_run_timestamp = 0;
+ tree->last_run_monotime = 0;
+ tree->last_run_duration = 0;
+ tree->runcount = 0;
+ tree->type = type;
+ memcpy(tree->sysid, sysid, ISIS_SYS_ID_LEN);
+ tree->level = level;
+ tree->tree_id = tree_id;
+ tree->family = (tree->tree_id == SPFTREE_IPV4) ? AF_INET : AF_INET6;
+ tree->flags = flags;
+ tree->algorithm = algorithm;
+
+ _isis_spftree_init(tree);
+
+ return tree;
+}
+
+static void _isis_spftree_del(struct isis_spftree *spftree)
+{
+ hash_clean_and_free(&spftree->prefix_sids, NULL);
+ isis_zebra_rlfa_unregister_all(spftree);
+ isis_rlfa_list_clear(spftree);
+ list_delete(&spftree->lfa.remote.pc_spftrees);
+ if (spftree->type == SPF_TYPE_RLFA
+ || spftree->type == SPF_TYPE_TI_LFA) {
+ isis_spf_node_list_clear(&spftree->lfa.q_space);
+ isis_spf_node_list_clear(&spftree->lfa.p_space);
+ }
+ isis_spf_node_list_clear(&spftree->adj_nodes);
+ list_delete(&spftree->sadj_list);
+ isis_vertex_queue_free(&spftree->tents);
+ isis_vertex_queue_free(&spftree->paths);
+ isis_route_table_info_free(spftree->route_table->info);
+ isis_route_table_info_free(spftree->route_table_backup->info);
+ route_table_finish(spftree->route_table);
+ route_table_finish(spftree->route_table_backup);
+}
+
+void isis_spftree_del(struct isis_spftree *spftree)
+{
+ _isis_spftree_del(spftree);
+
+ spftree->route_table = NULL;
+
+ XFREE(MTYPE_ISIS_SPFTREE, spftree);
+ return;
+}
+
+#ifndef FABRICD
+static void isis_spftree_clear(struct isis_spftree *spftree)
+{
+ _isis_spftree_del(spftree);
+ _isis_spftree_init(spftree);
+}
+#endif /* ifndef FABRICD */
+
+static void isis_spftree_adj_del(struct isis_spftree *spftree,
+ struct isis_adjacency *adj)
+{
+ struct listnode *node;
+ struct isis_vertex *v;
+ if (!adj)
+ return;
+ assert(!isis_vertex_queue_count(&spftree->tents));
+ for (ALL_QUEUE_ELEMENTS_RO(&spftree->paths, node, v))
+ isis_vertex_adj_del(v, adj);
+ return;
+}
+
+void spftree_area_init(struct isis_area *area)
+{
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+ if (!(area->is_type & level))
+ continue;
+ if (area->spftree[tree][level - 1])
+ continue;
+
+ area->spftree[tree][level - 1] = isis_spftree_new(
+ area, &area->lspdb[level - 1],
+ area->isis->sysid, level, tree,
+ SPF_TYPE_FORWARD, 0, SR_ALGORITHM_SPF);
+ }
+ }
+}
+
+void spftree_area_del(struct isis_area *area)
+{
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+ if (!(area->is_type & level))
+ continue;
+ if (!area->spftree[tree][level - 1])
+ continue;
+
+ isis_spftree_del(area->spftree[tree][level - 1]);
+ }
+ }
+}
+
+static int spf_adj_state_change(struct isis_adjacency *adj)
+{
+ struct isis_area *area = adj->circuit->area;
+
+ if (adj->adj_state == ISIS_ADJ_UP)
+ return 0;
+
+ /* Remove adjacency from all SPF trees. */
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+ if (!(area->is_type & level))
+ continue;
+ if (!area->spftree[tree][level - 1])
+ continue;
+ isis_spftree_adj_del(area->spftree[tree][level - 1],
+ adj);
+ }
+ }
+
+ if (fabricd_spftree(area) != NULL)
+ isis_spftree_adj_del(fabricd_spftree(area), adj);
+
+ return 0;
+}
+
+/*
+ * Find the system LSP: returns the LSP in our LSP database
+ * associated with the given system ID.
+ */
+struct isis_lsp *isis_root_system_lsp(struct lspdb_head *lspdb,
+ const uint8_t *sysid)
+{
+ struct isis_lsp *lsp;
+ uint8_t lspid[ISIS_SYS_ID_LEN + 2];
+
+ memcpy(lspid, sysid, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(lspid) = 0;
+ LSP_FRAGMENT(lspid) = 0;
+ lsp = lsp_search(lspdb, lspid);
+ if (lsp && lsp->hdr.rem_lifetime != 0)
+ return lsp;
+ return NULL;
+}
+
+/*
+ * Add this IS to the root of SPT
+ */
+static struct isis_vertex *isis_spf_add_root(struct isis_spftree *spftree)
+{
+ struct isis_vertex *vertex;
+#ifdef EXTREME_DEBUG
+ char buff[VID2STR_BUFFER];
+#endif /* EXTREME_DEBUG */
+
+ vertex = isis_vertex_new(spftree, spftree->sysid,
+ spftree->area->oldmetric
+ ? VTYPE_NONPSEUDO_IS
+ : VTYPE_NONPSEUDO_TE_IS);
+ isis_vertex_queue_append(&spftree->paths, vertex);
+
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_SPF_EVENTS)
+ zlog_debug(
+ "ISIS-SPF: A:%hhu added this IS %s %s depth %d dist %d to PATHS",
+ spftree->algorithm, vtype2string(vertex->type),
+ vid2string(vertex, buff, sizeof(buff)), vertex->depth,
+ vertex->d_N);
+#endif /* EXTREME_DEBUG */
+
+ return vertex;
+}
+
+static void vertex_add_parent_firsthop(struct hash_bucket *bucket, void *arg)
+{
+ struct isis_vertex *vertex = arg;
+ struct isis_vertex *hop = bucket->data;
+
+ (void)hash_get(vertex->firsthops, hop, hash_alloc_intern);
+}
+
+static void vertex_update_firsthops(struct isis_vertex *vertex,
+ struct isis_vertex *parent)
+{
+ if (vertex->d_N <= 2)
+ (void)hash_get(vertex->firsthops, vertex, hash_alloc_intern);
+
+ if (vertex->d_N < 2 || !parent)
+ return;
+
+ hash_iterate(parent->firsthops, vertex_add_parent_firsthop, vertex);
+}
+
+/*
+ * Add a vertex to TENT sorted by cost and by vertextype on tie break situation
+ */
+static struct isis_vertex *
+isis_spf_add2tent(struct isis_spftree *spftree, enum vertextype vtype, void *id,
+ uint32_t cost, int depth, struct isis_spf_adj *sadj,
+ struct isis_prefix_sid *psid, struct isis_vertex *parent)
+{
+ struct isis_vertex *vertex;
+ struct listnode *node;
+ bool last_hop;
+ char buff[VID2STR_BUFFER];
+
+ vertex = isis_find_vertex(&spftree->paths, id, vtype);
+ if (vertex != NULL) {
+ zlog_err(
+ "%s: vertex %s of type %s already in PATH; check for sysId collisions with established neighbors",
+ __func__, vid2string(vertex, buff, sizeof(buff)),
+ vtype2string(vertex->type));
+ return NULL;
+ }
+ vertex = isis_find_vertex(&spftree->tents, id, vtype);
+ if (vertex != NULL) {
+ zlog_err(
+ "%s: vertex %s of type %s already in TENT; check for sysId collisions with established neighbors",
+ __func__, vid2string(vertex, buff, sizeof(buff)),
+ vtype2string(vertex->type));
+ return NULL;
+ }
+
+ vertex = isis_vertex_new(spftree, id, vtype);
+ vertex->d_N = cost;
+ vertex->depth = depth;
+ if (VTYPE_IP(vtype) && spftree->area->srdb.enabled && psid) {
+ struct isis_area *area = spftree->area;
+ struct isis_vertex *vertex_psid;
+
+ /*
+ * Check if the Prefix-SID is already in use by another prefix.
+ */
+ vertex_psid = isis_spf_prefix_sid_lookup(spftree, psid);
+ if (vertex_psid
+ && !prefix_same(&vertex_psid->N.ip.p.dest,
+ &vertex->N.ip.p.dest)) {
+ flog_warn(
+ EC_ISIS_SID_COLLISION,
+ "ISIS-Sr (%s): collision detected, prefixes %pFX and %pFX share the same SID %s (%u)",
+ area->area_tag, &vertex->N.ip.p.dest,
+ &vertex_psid->N.ip.p.dest,
+ CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_VALUE)
+ ? "label"
+ : "index",
+ psid->value);
+ psid = NULL;
+ } else {
+ bool local;
+
+ local = (vertex->depth == 1);
+ vertex->N.ip.sr.sid = *psid;
+ vertex->N.ip.sr.label =
+ sr_prefix_in_label(area, psid, local);
+ vertex->N.ip.sr.algorithm = psid->algorithm;
+
+ if (vertex->N.ip.sr.label != MPLS_INVALID_LABEL)
+ vertex->N.ip.sr.present = true;
+
+#ifndef FABRICD
+ if (flex_algo_id_valid(spftree->algorithm) &&
+ !isis_flex_algo_elected_supported(
+ spftree->algorithm, spftree->area)) {
+ vertex->N.ip.sr.present = false;
+ vertex->N.ip.sr.label = MPLS_INVALID_LABEL;
+ }
+#endif /* ifndef FABRICD */
+
+ (void)hash_get(spftree->prefix_sids, vertex,
+ hash_alloc_intern);
+ }
+ }
+
+ if (parent) {
+ listnode_add(vertex->parents, parent);
+ }
+
+ if (CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC))
+ vertex_update_firsthops(vertex, parent);
+
+ last_hop = (vertex->depth == 2);
+ if (parent && parent->Adj_N && listcount(parent->Adj_N) > 0) {
+ struct isis_vertex_adj *parent_vadj;
+
+ for (ALL_LIST_ELEMENTS_RO(parent->Adj_N, node, parent_vadj))
+ isis_vertex_adj_add(spftree, vertex, vertex->Adj_N,
+ parent_vadj->sadj, psid, last_hop);
+ } else if (sadj) {
+ isis_vertex_adj_add(spftree, vertex, vertex->Adj_N, sadj, psid,
+ last_hop);
+ }
+
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_SPF_EVENTS)
+ zlog_debug(
+ "ISIS-SPF: A:%hhu add to TENT %s %s %s depth %d dist %d adjcount %d",
+ spftree->algorithm, print_sys_hostname(vertex->N.id),
+ vtype2string(vertex->type),
+ vid2string(vertex, buff, sizeof(buff)), vertex->depth,
+ vertex->d_N, listcount(vertex->Adj_N));
+#endif /* EXTREME_DEBUG */
+
+ isis_vertex_queue_insert(&spftree->tents, vertex);
+ return vertex;
+}
+
+static void isis_spf_add_local(struct isis_spftree *spftree,
+ enum vertextype vtype, void *id,
+ struct isis_spf_adj *sadj, uint32_t cost,
+ struct isis_prefix_sid *psid,
+ struct isis_vertex *parent)
+{
+ struct isis_vertex *vertex;
+
+ vertex = isis_find_vertex(&spftree->tents, id, vtype);
+
+ if (vertex) {
+ /* C.2.5 c) */
+ if (vertex->d_N == cost) {
+ if (sadj) {
+ bool last_hop = (vertex->depth == 2);
+
+ isis_vertex_adj_add(spftree, vertex,
+ vertex->Adj_N, sadj, psid,
+ last_hop);
+ }
+ /* d) */
+ if (!CHECK_FLAG(spftree->flags,
+ F_SPFTREE_NO_ADJACENCIES)
+ && listcount(vertex->Adj_N) > ISIS_MAX_PATH_SPLITS)
+ remove_excess_adjs(vertex->Adj_N);
+ if (parent && (listnode_lookup(vertex->parents, parent)
+ == NULL))
+ listnode_add(vertex->parents, parent);
+ return;
+ } else if (vertex->d_N < cost) {
+ /* e) do nothing */
+ return;
+ } else { /* vertex->d_N > cost */
+ /* f) */
+ isis_vertex_queue_delete(&spftree->tents, vertex);
+ isis_vertex_del(vertex);
+ }
+ }
+
+ isis_spf_add2tent(spftree, vtype, id, cost, 1, sadj, psid, parent);
+ return;
+}
+
+static void process_N(struct isis_spftree *spftree, enum vertextype vtype,
+ void *id, uint32_t dist, uint16_t depth,
+ struct isis_prefix_sid *psid, struct isis_vertex *parent)
+{
+ struct isis_vertex *vertex;
+#ifdef EXTREME_DEBUG
+ char buff[VID2STR_BUFFER];
+#endif
+
+ assert(spftree && parent);
+
+ if (CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC)
+ && !VTYPE_IS(vtype))
+ return;
+
+ struct prefix_pair p;
+ if (vtype >= VTYPE_IPREACH_INTERNAL) {
+ memcpy(&p, id, sizeof(p));
+ apply_mask(&p.dest);
+ apply_mask(&p.src);
+ id = &p;
+ }
+
+ /* RFC3787 section 5.1 */
+ if (spftree->area->newmetric == 1) {
+ if (dist > MAX_WIDE_PATH_METRIC)
+ return;
+ }
+ /* C.2.6 b) */
+ else if (spftree->area->oldmetric == 1) {
+ if (dist > MAX_NARROW_PATH_METRIC)
+ return;
+ }
+
+ /* c) */
+ vertex = isis_find_vertex(&spftree->paths, id, vtype);
+ if (vertex) {
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_SPF_EVENTS)
+ zlog_debug(
+ "ISIS-SPF: A:%hhu process_N %s %s %s dist %d already found from PATH",
+ spftree->algorithm,
+ print_sys_hostname(vertex->N.id),
+ vtype2string(vtype),
+ vid2string(vertex, buff, sizeof(buff)), dist);
+#endif /* EXTREME_DEBUG */
+ assert(dist >= vertex->d_N);
+ return;
+ }
+
+ vertex = isis_find_vertex(&spftree->tents, id, vtype);
+ /* d) */
+ if (vertex) {
+/* 1) */
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_SPF_EVENTS)
+ zlog_debug(
+ "ISIS-SPF: A:%hhu process_N %s %s %s dist %d parent %s adjcount %d",
+ spftree->algorithm,
+ print_sys_hostname(vertex->N.id),
+ vtype2string(vtype),
+ vid2string(vertex, buff, sizeof(buff)), dist,
+ (parent ? print_sys_hostname(parent->N.id)
+ : "null"),
+ (parent ? listcount(parent->Adj_N) : 0));
+#endif /* EXTREME_DEBUG */
+ if (vertex->d_N == dist) {
+ struct listnode *node;
+ struct isis_vertex_adj *parent_vadj;
+ for (ALL_LIST_ELEMENTS_RO(parent->Adj_N, node,
+ parent_vadj))
+ if (!isis_vertex_adj_exists(
+ spftree, vertex,
+ parent_vadj->sadj)) {
+ bool last_hop = (vertex->depth == 2);
+
+ isis_vertex_adj_add(spftree, vertex,
+ vertex->Adj_N,
+ parent_vadj->sadj,
+ psid, last_hop);
+ }
+ if (CHECK_FLAG(spftree->flags,
+ F_SPFTREE_HOPCOUNT_METRIC))
+ vertex_update_firsthops(vertex, parent);
+ /* 2) */
+ if (!CHECK_FLAG(spftree->flags,
+ F_SPFTREE_NO_ADJACENCIES)
+ && listcount(vertex->Adj_N) > ISIS_MAX_PATH_SPLITS)
+ remove_excess_adjs(vertex->Adj_N);
+ if (listnode_lookup(vertex->parents, parent) == NULL)
+ listnode_add(vertex->parents, parent);
+ return;
+ } else if (vertex->d_N < dist) {
+ return;
+ /* 4) */
+ } else {
+ isis_vertex_queue_delete(&spftree->tents, vertex);
+ isis_vertex_del(vertex);
+ }
+ }
+
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_SPF_EVENTS)
+ zlog_debug(
+ "ISIS-SPF: A:%hhu process_N add2tent %s %s dist %d parent %s",
+ spftree->algorithm, print_sys_hostname(id),
+ vtype2string(vtype), dist,
+ (parent ? print_sys_hostname(parent->N.id) : "null"));
+#endif /* EXTREME_DEBUG */
+
+ isis_spf_add2tent(spftree, vtype, id, dist, depth, NULL, psid, parent);
+ return;
+}
+
+/*
+ * C.2.6 Step 1
+ */
+static int isis_spf_process_lsp(struct isis_spftree *spftree,
+ struct isis_lsp *lsp, uint32_t cost,
+ uint16_t depth, uint8_t *root_sysid,
+ struct isis_vertex *parent)
+{
+ bool pseudo_lsp = LSP_PSEUDO_ID(lsp->hdr.lsp_id);
+ struct listnode *fragnode = NULL;
+ uint32_t dist;
+ enum vertextype vtype;
+ static const uint8_t null_sysid[ISIS_SYS_ID_LEN];
+ struct isis_mt_router_info *mt_router_info = NULL;
+ struct prefix_pair ip_info;
+ bool has_valid_psid;
+ bool loc_is_in_ipv6_reach = false;
+
+ if (isis_lfa_excise_node_check(spftree, lsp->hdr.lsp_id)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: excising node %s",
+ print_sys_hostname(lsp->hdr.lsp_id));
+ return ISIS_OK;
+ }
+
+ if (!lsp->tlvs)
+ return ISIS_OK;
+
+ if (spftree->mtid != ISIS_MT_IPV4_UNICAST)
+ mt_router_info = isis_tlvs_lookup_mt_router_info(lsp->tlvs,
+ spftree->mtid);
+
+ if (!pseudo_lsp && (spftree->mtid == ISIS_MT_IPV4_UNICAST
+ && !speaks(lsp->tlvs->protocols_supported.protocols,
+ lsp->tlvs->protocols_supported.count,
+ spftree->family))
+ && !mt_router_info)
+ return ISIS_OK;
+
+ /* RFC3787 section 4 SHOULD ignore overload bit in pseudo LSPs */
+ bool no_overload = (pseudo_lsp
+ || (spftree->mtid == ISIS_MT_IPV4_UNICAST
+ && !ISIS_MASK_LSP_OL_BIT(lsp->hdr.lsp_bits))
+ || (mt_router_info && !mt_router_info->overload));
+
+lspfragloop:
+ if (lsp->hdr.seqno == 0) {
+ zlog_warn("%s: lsp with 0 seq_num - ignore", __func__);
+ return ISIS_WARNING;
+ }
+
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_SPF_EVENTS)
+ zlog_debug("ISIS-SPF: A:%hhu process_lsp %s",
+ spftree->algorithm,
+ print_sys_hostname(lsp->hdr.lsp_id));
+#endif /* EXTREME_DEBUG */
+
+ if (no_overload) {
+ if ((pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST)
+ && spftree->area->oldmetric) {
+ struct isis_oldstyle_reach *r;
+ for (r = (struct isis_oldstyle_reach *)
+ lsp->tlvs->oldstyle_reach.head;
+ r; r = r->next) {
+ if (fabricd)
+ continue;
+
+ /* C.2.6 a) */
+ /* Two way connectivity */
+ if (!LSP_PSEUDO_ID(r->id)
+ && !memcmp(r->id, root_sysid,
+ ISIS_SYS_ID_LEN))
+ continue;
+ if (!pseudo_lsp
+ && !memcmp(r->id, null_sysid,
+ ISIS_SYS_ID_LEN))
+ continue;
+ dist = cost + r->metric;
+ process_N(spftree,
+ LSP_PSEUDO_ID(r->id)
+ ? VTYPE_PSEUDO_IS
+ : VTYPE_NONPSEUDO_IS,
+ (void *)r->id, dist, depth + 1, NULL,
+ parent);
+ }
+ }
+
+ if (spftree->area->newmetric) {
+ struct isis_item_list *te_neighs = NULL;
+ if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST)
+ te_neighs = &lsp->tlvs->extended_reach;
+ else
+ te_neighs = isis_lookup_mt_items(
+ &lsp->tlvs->mt_reach, spftree->mtid);
+
+ struct isis_extended_reach *er;
+ for (er = te_neighs ? (struct isis_extended_reach *)
+ te_neighs->head
+ : NULL;
+ er; er = er->next) {
+ /* C.2.6 a) */
+ /* Two way connectivity */
+ if (!LSP_PSEUDO_ID(er->id)
+ && !memcmp(er->id, root_sysid,
+ ISIS_SYS_ID_LEN))
+ continue;
+ if (!pseudo_lsp
+ && !memcmp(er->id, null_sysid,
+ ISIS_SYS_ID_LEN))
+ continue;
+#ifndef FABRICD
+
+ if (flex_algo_id_valid(spftree->algorithm) &&
+ (!sr_algorithm_participated(
+ lsp, spftree->algorithm) ||
+ isis_flex_algo_constraint_drop(spftree,
+ lsp, er)))
+ continue;
+#endif /* ifndef FABRICD */
+
+ dist = cost
+ + (CHECK_FLAG(spftree->flags,
+ F_SPFTREE_HOPCOUNT_METRIC)
+ ? 1
+ : er->metric);
+ process_N(spftree,
+ LSP_PSEUDO_ID(er->id)
+ ? VTYPE_PSEUDO_TE_IS
+ : VTYPE_NONPSEUDO_TE_IS,
+ (void *)er->id, dist, depth + 1, NULL,
+ parent);
+ }
+ }
+ }
+
+ if (!fabricd && !pseudo_lsp && spftree->family == AF_INET
+ && spftree->mtid == ISIS_MT_IPV4_UNICAST
+ && spftree->area->oldmetric) {
+ struct isis_item_list *reachs[] = {
+ &lsp->tlvs->oldstyle_ip_reach,
+ &lsp->tlvs->oldstyle_ip_reach_ext};
+
+ for (unsigned int i = 0; i < array_size(reachs); i++) {
+ vtype = i ? VTYPE_IPREACH_EXTERNAL
+ : VTYPE_IPREACH_INTERNAL;
+
+ memset(&ip_info, 0, sizeof(ip_info));
+ ip_info.dest.family = AF_INET;
+
+ struct isis_oldstyle_ip_reach *r;
+ for (r = (struct isis_oldstyle_ip_reach *)reachs[i]
+ ->head;
+ r; r = r->next) {
+ dist = cost + r->metric;
+ ip_info.dest.u.prefix4 = r->prefix.prefix;
+ ip_info.dest.prefixlen = r->prefix.prefixlen;
+ process_N(spftree, vtype, &ip_info,
+ dist, depth + 1, NULL, parent);
+ }
+ }
+ }
+
+ /* we can skip all the rest if we're using metric style narrow */
+ if (!spftree->area->newmetric)
+ goto end;
+
+ if (!pseudo_lsp && spftree->family == AF_INET) {
+ struct isis_item_list *ipv4_reachs;
+ if (spftree->mtid == ISIS_MT_IPV4_UNICAST)
+ ipv4_reachs = &lsp->tlvs->extended_ip_reach;
+ else
+ ipv4_reachs = isis_lookup_mt_items(
+ &lsp->tlvs->mt_ip_reach, spftree->mtid);
+
+ memset(&ip_info, 0, sizeof(ip_info));
+ ip_info.dest.family = AF_INET;
+
+ struct isis_extended_ip_reach *r;
+ for (r = ipv4_reachs
+ ? (struct isis_extended_ip_reach *)
+ ipv4_reachs->head
+ : NULL;
+ r; r = r->next) {
+ dist = cost + r->metric;
+ ip_info.dest.u.prefix4 = r->prefix.prefix;
+ ip_info.dest.prefixlen = r->prefix.prefixlen;
+
+ /* Parse list of Prefix-SID subTLVs if SR is enabled */
+ has_valid_psid = false;
+ if (spftree->area->srdb.enabled && r->subtlvs) {
+ for (struct isis_item *i =
+ r->subtlvs->prefix_sids.head;
+ i; i = i->next) {
+ struct isis_prefix_sid *psid =
+ (struct isis_prefix_sid *)i;
+
+ if (psid->algorithm !=
+ spftree->algorithm)
+ continue;
+
+#ifndef FABRICD
+ if (flex_algo_id_valid(
+ spftree->algorithm) &&
+ (!sr_algorithm_participated(
+ lsp, spftree->algorithm) ||
+ !isis_flex_algo_elected_supported(
+ spftree->algorithm,
+ spftree->area)))
+ continue;
+#endif /* ifndef FABRICD */
+
+ has_valid_psid = true;
+ process_N(spftree, VTYPE_IPREACH_TE,
+ &ip_info, dist, depth + 1,
+ psid, parent);
+ /*
+ * Stop the Prefix-SID iteration since
+ * we only support the SPF algorithm for
+ * now.
+ */
+ break;
+ }
+ }
+ if (!has_valid_psid)
+ process_N(spftree, VTYPE_IPREACH_TE, &ip_info,
+ dist, depth + 1, NULL, parent);
+ }
+ }
+
+ if (!pseudo_lsp && spftree->family == AF_INET6) {
+ struct isis_item_list *ipv6_reachs;
+ if (spftree->mtid == ISIS_MT_IPV4_UNICAST)
+ ipv6_reachs = &lsp->tlvs->ipv6_reach;
+ else
+ ipv6_reachs = isis_lookup_mt_items(
+ &lsp->tlvs->mt_ipv6_reach, spftree->mtid);
+
+ struct isis_ipv6_reach *r;
+ for (r = ipv6_reachs
+ ? (struct isis_ipv6_reach *)ipv6_reachs->head
+ : NULL;
+ r; r = r->next) {
+ dist = cost + r->metric;
+ vtype = r->external ? VTYPE_IP6REACH_EXTERNAL
+ : VTYPE_IP6REACH_INTERNAL;
+ memset(&ip_info, 0, sizeof(ip_info));
+ ip_info.dest.family = AF_INET6;
+ ip_info.dest.u.prefix6 = r->prefix.prefix;
+ ip_info.dest.prefixlen = r->prefix.prefixlen;
+
+ if (spftree->area->srdb.enabled && r->subtlvs &&
+ r->subtlvs->source_prefix &&
+ r->subtlvs->source_prefix->prefixlen) {
+ if (spftree->tree_id != SPFTREE_DSTSRC) {
+ char buff[VID2STR_BUFFER];
+ zlog_warn("Ignoring dest-src route %s in non dest-src topology",
+ srcdest2str(
+ &ip_info.dest,
+ r->subtlvs->source_prefix,
+ buff, sizeof(buff)
+ )
+ );
+ continue;
+ }
+ ip_info.src = *r->subtlvs->source_prefix;
+ }
+
+ /* Parse list of Prefix-SID subTLVs */
+ has_valid_psid = false;
+ if (r->subtlvs) {
+ for (struct isis_item *i =
+ r->subtlvs->prefix_sids.head;
+ i; i = i->next) {
+ struct isis_prefix_sid *psid =
+ (struct isis_prefix_sid *)i;
+
+ if (psid->algorithm !=
+ spftree->algorithm)
+ continue;
+
+#ifndef FABRICD
+ if (flex_algo_id_valid(
+ spftree->algorithm) &&
+ (!sr_algorithm_participated(
+ lsp, spftree->algorithm) ||
+ !isis_flex_algo_elected_supported(
+ spftree->algorithm,
+ spftree->area)))
+ continue;
+#endif /* ifndef FABRICD */
+
+ has_valid_psid = true;
+ process_N(spftree, vtype, &ip_info,
+ dist, depth + 1, psid,
+ parent);
+ /*
+ * Stop the Prefix-SID iteration since
+ * we only support the SPF algorithm for
+ * now.
+ */
+ break;
+ }
+ }
+ if (!has_valid_psid)
+ process_N(spftree, vtype, &ip_info, dist,
+ depth + 1, NULL, parent);
+ }
+
+ /* Process SRv6 Locator TLVs */
+
+ struct isis_item_list *srv6_locators = isis_lookup_mt_items(
+ &lsp->tlvs->srv6_locator, spftree->mtid);
+
+ struct isis_srv6_locator_tlv *loc;
+ for (loc = srv6_locators ? (struct isis_srv6_locator_tlv *)
+ srv6_locators->head
+ : NULL;
+ loc; loc = loc->next) {
+
+ if (loc->algorithm != SR_ALGORITHM_SPF)
+ continue;
+
+ dist = cost + loc->metric;
+ vtype = VTYPE_IP6REACH_INTERNAL;
+ memset(&ip_info, 0, sizeof(ip_info));
+ ip_info.dest.family = AF_INET6;
+ ip_info.dest.u.prefix6 = loc->prefix.prefix;
+ ip_info.dest.prefixlen = loc->prefix.prefixlen;
+
+ /* An SRv6 Locator can be received in both a Prefix
+ Reachability TLV and an SRv6 Locator TLV (as per RFC
+ 9352 section #5). We go through the Prefix Reachability
+ TLVs and check if the SRv6 Locator is present in some of
+ them. If we find the SRv6 Locator in some Prefix
+ Reachbility TLV then it means that we have already
+ processed it before and we can skip it. */
+ for (r = ipv6_reachs ? (struct isis_ipv6_reach *)
+ ipv6_reachs->head
+ : NULL;
+ r; r = r->next) {
+ if (prefix_same((struct prefix *)&r->prefix,
+ (struct prefix *)&loc->prefix))
+ loc_is_in_ipv6_reach = true;
+ }
+
+ /* SRv6 locator not present in Prefix Reachability TLV,
+ * let's process it */
+ if (!loc_is_in_ipv6_reach)
+ process_N(spftree, vtype, &ip_info, dist,
+ depth + 1, NULL, parent);
+ }
+ }
+
+end:
+
+ /* if attach bit set in LSP, attached-bit receive ignore is
+ * not configured, we are a level-1 area and we have no other
+ * level-2 | level1-2 areas then add a default route toward
+ * this neighbor
+ */
+ if ((lsp->hdr.lsp_bits & LSPBIT_ATT) == LSPBIT_ATT
+ && !spftree->area->attached_bit_rcv_ignore
+ && (spftree->area->is_type & IS_LEVEL_1)
+ && !isis_level2_adj_up(spftree->area)) {
+ struct prefix_pair ip_info = { {0} };
+ if (IS_DEBUG_RTE_EVENTS)
+ zlog_debug("ISIS-Spf (%pLS): add default %s route",
+ lsp->hdr.lsp_id,
+ spftree->family == AF_INET ? "ipv4"
+ : "ipv6");
+
+ if (spftree->family == AF_INET) {
+ ip_info.dest.family = AF_INET;
+ vtype = VTYPE_IPREACH_INTERNAL;
+ } else {
+ ip_info.dest.family = AF_INET6;
+ vtype = VTYPE_IP6REACH_INTERNAL;
+ }
+ process_N(spftree, vtype, &ip_info, cost, depth + 1, NULL,
+ parent);
+ }
+
+ if (fragnode == NULL)
+ fragnode = listhead(lsp->lspu.frags);
+ else
+ fragnode = listnextnode(fragnode);
+
+ if (fragnode) {
+ lsp = listgetdata(fragnode);
+ goto lspfragloop;
+ }
+
+ return ISIS_OK;
+}
+
+static struct isis_adjacency *adj_find(struct list *adj_list, const uint8_t *id,
+ int level, uint16_t mtid, int family)
+{
+ struct isis_adjacency *adj;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(adj_list, node, adj)) {
+ if (!(adj->level & level))
+ continue;
+ if (memcmp(adj->sysid, id, ISIS_SYS_ID_LEN) != 0)
+ continue;
+ if (adj->adj_state != ISIS_ADJ_UP)
+ continue;
+ if (!adj_has_mt(adj, mtid))
+ continue;
+ if (mtid == ISIS_MT_IPV4_UNICAST
+ && !speaks(adj->nlpids.nlpids, adj->nlpids.count, family))
+ continue;
+ return adj;
+ }
+
+ return NULL;
+}
+
+struct spf_preload_tent_ip_reach_args {
+ struct isis_spftree *spftree;
+ struct isis_vertex *parent;
+};
+
+static int isis_spf_preload_tent_ip_reach_cb(const struct prefix *prefix,
+ uint32_t metric, bool external,
+ struct isis_subtlvs *subtlvs,
+ void *arg)
+{
+ struct spf_preload_tent_ip_reach_args *args = arg;
+ struct isis_spftree *spftree = args->spftree;
+ struct isis_vertex *parent = args->parent;
+ struct prefix_pair ip_info;
+ enum vertextype vtype;
+ bool has_valid_psid = false;
+
+ if (external)
+ return LSP_ITER_CONTINUE;
+
+ assert(spftree->family == prefix->family);
+ memset(&ip_info, 0, sizeof(ip_info));
+ prefix_copy(&ip_info.dest, prefix);
+ apply_mask(&ip_info.dest);
+
+ if (prefix->family == AF_INET)
+ vtype = VTYPE_IPREACH_INTERNAL;
+ else
+ vtype = VTYPE_IP6REACH_INTERNAL;
+
+ /* Parse list of Prefix-SID subTLVs if SR is enabled */
+ if (spftree->area->srdb.enabled && subtlvs) {
+ for (struct isis_item *i = subtlvs->prefix_sids.head; i;
+ i = i->next) {
+ struct isis_prefix_sid *psid =
+ (struct isis_prefix_sid *)i;
+
+ if (psid->algorithm != spftree->algorithm)
+ continue;
+
+ has_valid_psid = true;
+ isis_spf_add_local(spftree, vtype, &ip_info, NULL, 0,
+ psid, parent);
+
+ /*
+ * Stop the Prefix-SID iteration since we only support
+ * the SPF algorithm for now.
+ */
+ break;
+ }
+ }
+ if (!has_valid_psid)
+ isis_spf_add_local(spftree, vtype, &ip_info, NULL, 0, NULL,
+ parent);
+
+ return LSP_ITER_CONTINUE;
+}
+
+static void isis_spf_preload_tent(struct isis_spftree *spftree,
+ uint8_t *root_sysid,
+ struct isis_lsp *root_lsp,
+ struct isis_vertex *parent)
+{
+ struct spf_preload_tent_ip_reach_args ip_reach_args;
+ struct isis_spf_adj *sadj;
+ struct listnode *node;
+
+ if (!CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC)) {
+ ip_reach_args.spftree = spftree;
+ ip_reach_args.parent = parent;
+ isis_lsp_iterate_ip_reach(
+ root_lsp, spftree->family, spftree->mtid,
+ isis_spf_preload_tent_ip_reach_cb, &ip_reach_args);
+ }
+
+ /* Iterate over adjacencies. */
+ for (ALL_LIST_ELEMENTS_RO(spftree->sadj_list, node, sadj)) {
+ const uint8_t *adj_id;
+ uint32_t metric;
+
+ if (CHECK_FLAG(sadj->flags, F_ISIS_SPF_ADJ_BROADCAST))
+ adj_id = sadj->lan.desig_is_id;
+ else
+ adj_id = sadj->id;
+
+ if (isis_lfa_excise_adj_check(spftree, adj_id)) {
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-SPF: excising adjacency %pPN",
+ sadj->id);
+ continue;
+ }
+
+ metric = CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC)
+ ? 1
+ : sadj->metric;
+ if (!LSP_PSEUDO_ID(sadj->id)) {
+ isis_spf_add_local(spftree,
+ CHECK_FLAG(sadj->flags,
+ F_ISIS_SPF_ADJ_OLDMETRIC)
+ ? VTYPE_NONPSEUDO_IS
+ : VTYPE_NONPSEUDO_TE_IS,
+ sadj->id, sadj, metric, NULL,
+ parent);
+ } else if (sadj->lsp) {
+ isis_spf_process_lsp(spftree, sadj->lsp, metric, 0,
+ spftree->sysid, parent);
+ }
+ }
+}
+
+struct spf_adj_find_reverse_metric_args {
+ const uint8_t *id_self;
+ uint32_t reverse_metric;
+};
+
+static int spf_adj_find_reverse_metric_cb(const uint8_t *id, uint32_t metric,
+ bool oldmetric,
+ struct isis_ext_subtlvs *subtlvs,
+ void *arg)
+{
+ struct spf_adj_find_reverse_metric_args *args = arg;
+
+ if (memcmp(id, args->id_self, ISIS_SYS_ID_LEN))
+ return LSP_ITER_CONTINUE;
+
+ args->reverse_metric = metric;
+
+ return LSP_ITER_STOP;
+}
+
+/*
+ * Change all SPF adjacencies to use the link cost in the direction from the
+ * next hop back towards root in place of the link cost in the direction away
+ * from root towards the next hop.
+ */
+static void spf_adj_get_reverse_metrics(struct isis_spftree *spftree)
+{
+ struct isis_spf_adj *sadj;
+ struct listnode *node, *nnode;
+
+ for (ALL_LIST_ELEMENTS(spftree->sadj_list, node, nnode, sadj)) {
+ uint8_t lspid[ISIS_SYS_ID_LEN + 2];
+ struct isis_lsp *lsp_adj;
+ const uint8_t *id_self;
+ struct spf_adj_find_reverse_metric_args args;
+
+ /* Skip pseudonodes. */
+ if (LSP_PSEUDO_ID(sadj->id))
+ continue;
+
+ /* Find LSP of the corresponding adjacency. */
+ memcpy(lspid, sadj->id, ISIS_SYS_ID_LEN);
+ LSP_PSEUDO_ID(lspid) = 0;
+ LSP_FRAGMENT(lspid) = 0;
+ lsp_adj = lsp_search(spftree->lspdb, lspid);
+ if (lsp_adj == NULL || lsp_adj->hdr.rem_lifetime == 0) {
+ /* Delete one-way adjacency. */
+ listnode_delete(spftree->sadj_list, sadj);
+ isis_spf_adj_free(sadj);
+ continue;
+ }
+
+ /* Find root node in the LSP of the adjacent router. */
+ if (CHECK_FLAG(sadj->flags, F_ISIS_SPF_ADJ_BROADCAST))
+ id_self = sadj->lan.desig_is_id;
+ else
+ id_self = spftree->sysid;
+ args.id_self = id_self;
+ args.reverse_metric = UINT32_MAX;
+ isis_lsp_iterate_is_reach(lsp_adj, spftree->mtid,
+ spf_adj_find_reverse_metric_cb,
+ &args);
+ if (args.reverse_metric == UINT32_MAX) {
+ /* Delete one-way adjacency. */
+ listnode_delete(spftree->sadj_list, sadj);
+ isis_spf_adj_free(sadj);
+ continue;
+ }
+ sadj->metric = args.reverse_metric;
+ }
+}
+
+static void spf_adj_list_parse_tlv(struct isis_spftree *spftree,
+ struct list *adj_list, const uint8_t *id,
+ const uint8_t *desig_is_id,
+ uint32_t pseudo_metric, uint32_t metric,
+ bool oldmetric,
+ struct isis_ext_subtlvs *subtlvs)
+{
+ struct isis_spf_adj *sadj;
+ uint8_t lspid[ISIS_SYS_ID_LEN + 2];
+ struct isis_lsp *lsp;
+ uint8_t flags = 0;
+
+ /* Skip self in the pseudonode. */
+ if (desig_is_id && !memcmp(id, spftree->sysid, ISIS_SYS_ID_LEN))
+ return;
+
+ /* Find LSP from the adjacency. */
+ memcpy(lspid, id, ISIS_SYS_ID_LEN + 1);
+ LSP_FRAGMENT(lspid) = 0;
+ lsp = lsp_search(spftree->lspdb, lspid);
+ if (lsp == NULL || lsp->hdr.rem_lifetime == 0) {
+ zlog_warn("ISIS-SPF: No LSP found from root to L%d %pLS",
+ spftree->level, lspid);
+ return;
+ }
+
+ sadj = XCALLOC(MTYPE_ISIS_SPF_ADJ, sizeof(*sadj));
+ memcpy(sadj->id, id, sizeof(sadj->id));
+ if (desig_is_id) {
+ memcpy(sadj->lan.desig_is_id, desig_is_id,
+ sizeof(sadj->lan.desig_is_id));
+ SET_FLAG(flags, F_ISIS_SPF_ADJ_BROADCAST);
+ sadj->metric = pseudo_metric;
+ } else
+ sadj->metric = metric;
+ if (oldmetric)
+ SET_FLAG(flags, F_ISIS_SPF_ADJ_OLDMETRIC);
+ sadj->lsp = lsp;
+ sadj->subtlvs = subtlvs;
+ sadj->flags = flags;
+
+ if ((oldmetric && metric == ISIS_NARROW_METRIC_INFINITY)
+ || (!oldmetric && metric == ISIS_WIDE_METRIC_INFINITY))
+ SET_FLAG(flags, F_ISIS_SPF_ADJ_METRIC_INFINITY);
+
+ /* Set real adjacency. */
+ if (!CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)
+ && !LSP_PSEUDO_ID(id)) {
+ struct isis_adjacency *adj;
+
+ adj = adj_find(adj_list, id, spftree->level, spftree->mtid,
+ spftree->family);
+ if (!adj) {
+ XFREE(MTYPE_ISIS_SPF_ADJ, sadj);
+ return;
+ }
+
+ listnode_delete(adj_list, adj);
+ sadj->adj = adj;
+ }
+
+ /* Add adjacency to the list. */
+ listnode_add(spftree->sadj_list, sadj);
+
+ if (!LSP_PSEUDO_ID(id)) {
+ struct isis_spf_node *node;
+
+ node = isis_spf_node_find(&spftree->adj_nodes, id);
+ if (!node)
+ node = isis_spf_node_new(&spftree->adj_nodes, id);
+ if (node->best_metric == 0 || sadj->metric < node->best_metric)
+ node->best_metric = sadj->metric;
+ listnode_add(node->adjacencies, sadj);
+ }
+
+ /* Parse pseudonode LSP too. */
+ if (LSP_PSEUDO_ID(id))
+ spf_adj_list_parse_lsp(spftree, adj_list, lsp, id, metric);
+}
+
+static void spf_adj_list_parse_lsp(struct isis_spftree *spftree,
+ struct list *adj_list, struct isis_lsp *lsp,
+ const uint8_t *pseudo_nodeid,
+ uint32_t pseudo_metric)
+{
+ bool pseudo_lsp = LSP_PSEUDO_ID(lsp->hdr.lsp_id);
+ struct isis_lsp *frag;
+ struct listnode *node;
+ struct isis_item *head;
+ struct isis_item_list *te_neighs;
+
+ if (lsp->hdr.seqno == 0 || lsp->hdr.rem_lifetime == 0)
+ return;
+
+ /* Parse LSP. */
+ if (lsp->tlvs) {
+ if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST) {
+ head = lsp->tlvs->oldstyle_reach.head;
+ for (struct isis_oldstyle_reach *reach =
+ (struct isis_oldstyle_reach *)head;
+ reach; reach = reach->next) {
+ spf_adj_list_parse_tlv(
+ spftree, adj_list, reach->id,
+ pseudo_nodeid, pseudo_metric,
+ reach->metric, true, NULL);
+ }
+ }
+
+ if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST)
+ te_neighs = &lsp->tlvs->extended_reach;
+ else
+ te_neighs = isis_get_mt_items(&lsp->tlvs->mt_reach,
+ spftree->mtid);
+ if (te_neighs) {
+ head = te_neighs->head;
+ for (struct isis_extended_reach *reach =
+ (struct isis_extended_reach *)head;
+ reach; reach = reach->next) {
+#ifndef FABRICD
+ /*
+ * cutting out adjacency by flex-algo link
+ * affinity attribute
+ */
+ if (flex_algo_id_valid(spftree->algorithm) &&
+ (!sr_algorithm_participated(
+ lsp, spftree->algorithm) ||
+ isis_flex_algo_constraint_drop(
+ spftree, lsp, reach)))
+ continue;
+#endif /* ifndef FABRICD */
+
+ spf_adj_list_parse_tlv(
+ spftree, adj_list, reach->id,
+ pseudo_nodeid, pseudo_metric,
+ reach->metric, false, reach->subtlvs);
+ }
+ }
+ }
+
+ if (LSP_FRAGMENT(lsp->hdr.lsp_id))
+ return;
+
+ /* Parse LSP fragments. */
+ for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) {
+ if (!frag->tlvs)
+ continue;
+
+ spf_adj_list_parse_lsp(spftree, adj_list, frag, pseudo_nodeid,
+ pseudo_metric);
+ }
+}
+
+static void isis_spf_build_adj_list(struct isis_spftree *spftree,
+ struct isis_lsp *lsp)
+{
+ struct list *adj_list = NULL;
+
+ if (!CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES))
+ adj_list = list_dup(spftree->area->adjacency_list);
+
+ spf_adj_list_parse_lsp(spftree, adj_list, lsp, NULL, 0);
+
+ if (!CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES))
+ list_delete(&adj_list);
+
+ if (spftree->type == SPF_TYPE_REVERSE)
+ spf_adj_get_reverse_metrics(spftree);
+}
+
+/*
+ * The parent(s) for vertex is set when added to TENT list
+ * now we just put the child pointer(s) in place
+ */
+static void add_to_paths(struct isis_spftree *spftree,
+ struct isis_vertex *vertex)
+{
+#ifdef EXTREME_DEBUG
+ char buff[VID2STR_BUFFER];
+#endif /* EXTREME_DEBUG */
+
+ if (isis_find_vertex(&spftree->paths, &vertex->N, vertex->type))
+ return;
+ isis_vertex_queue_append(&spftree->paths, vertex);
+
+#ifdef EXTREME_DEBUG
+ if (IS_DEBUG_SPF_EVENTS)
+ zlog_debug(
+ "ISIS-SPF: A:%hhu S:%p added %s %s %s depth %d dist %d to PATHS",
+ spftree->algorithm, spftree,
+ print_sys_hostname(vertex->N.id),
+ vtype2string(vertex->type),
+ vid2string(vertex, buff, sizeof(buff)), vertex->depth,
+ vertex->d_N);
+#endif /* EXTREME_DEBUG */
+}
+
+static void init_spt(struct isis_spftree *spftree, int mtid)
+{
+ /* Clear data from previous run. */
+ hash_clean(spftree->prefix_sids, NULL);
+ isis_spf_node_list_clear(&spftree->adj_nodes);
+ list_delete_all_node(spftree->sadj_list);
+ isis_vertex_queue_clear(&spftree->tents);
+ isis_vertex_queue_clear(&spftree->paths);
+ isis_zebra_rlfa_unregister_all(spftree);
+ isis_rlfa_list_clear(spftree);
+ list_delete_all_node(spftree->lfa.remote.pc_spftrees);
+ memset(&spftree->lfa.protection_counters, 0,
+ sizeof(spftree->lfa.protection_counters));
+
+ spftree->mtid = mtid;
+}
+
+static enum spf_prefix_priority
+spf_prefix_priority(struct isis_spftree *spftree, struct isis_vertex *vertex)
+{
+ struct isis_area *area = spftree->area;
+ struct prefix *prefix = &vertex->N.ip.p.dest;
+
+ for (int priority = SPF_PREFIX_PRIO_CRITICAL;
+ priority <= SPF_PREFIX_PRIO_MEDIUM; priority++) {
+ struct spf_prefix_priority_acl *ppa;
+ enum filter_type ret = FILTER_PERMIT;
+
+ ppa = &area->spf_prefix_priorities[priority];
+ switch (spftree->family) {
+ case AF_INET:
+ ret = access_list_apply(ppa->list_v4, prefix);
+ break;
+ case AF_INET6:
+ ret = access_list_apply(ppa->list_v6, prefix);
+ break;
+ default:
+ break;
+ }
+
+ if (ret == FILTER_PERMIT)
+ return priority;
+ }
+
+ /* Assign medium priority to loopback prefixes by default. */
+ if (is_host_route(prefix))
+ return SPF_PREFIX_PRIO_MEDIUM;
+
+ return SPF_PREFIX_PRIO_LOW;
+}
+
+static void spf_path_process(struct isis_spftree *spftree,
+ struct isis_vertex *vertex)
+{
+ struct isis_area *area = spftree->area;
+ int level = spftree->level;
+ char buff[VID2STR_BUFFER];
+
+ if (spftree->type == SPF_TYPE_TI_LFA && VTYPE_IS(vertex->type)
+ && !CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)) {
+ if (listcount(vertex->Adj_N) > 0) {
+ struct isis_adjacency *adj;
+
+ if (isis_tilfa_check(spftree, vertex) != 0)
+ return;
+
+ adj = isis_adj_find(area, level, vertex->N.id);
+ if (adj)
+ sr_adj_sid_add_single(adj, spftree->family,
+ true, vertex->Adj_N);
+ } else if (IS_DEBUG_SPF_EVENTS)
+ zlog_debug(
+ "ISIS-SPF: no adjacencies, do not install backup Adj-SID for %s depth %d dist %d",
+ vid2string(vertex, buff, sizeof(buff)),
+ vertex->depth, vertex->d_N);
+ }
+
+ if (VTYPE_IP(vertex->type)
+ && !CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ROUTES)) {
+ enum spf_prefix_priority priority;
+
+ priority = spf_prefix_priority(spftree, vertex);
+ vertex->N.ip.priority = priority;
+ if (vertex->depth == 1 || listcount(vertex->Adj_N) > 0) {
+ struct isis_spftree *pre_spftree;
+ struct route_table *route_table = NULL;
+ bool allow_ecmp = false;
+
+ switch (spftree->type) {
+ case SPF_TYPE_RLFA:
+ case SPF_TYPE_TI_LFA:
+ if (priority
+ > area->lfa_priority_limit[level - 1]) {
+ if (IS_DEBUG_LFA)
+ zlog_debug(
+ "ISIS-LFA: skipping %s %s (low prefix priority)",
+ vtype2string(
+ vertex->type),
+ vid2string(
+ vertex, buff,
+ sizeof(buff)));
+ return;
+ }
+ break;
+ case SPF_TYPE_FORWARD:
+ case SPF_TYPE_REVERSE:
+ break;
+ }
+
+ switch (spftree->type) {
+ case SPF_TYPE_RLFA:
+ isis_rlfa_check(spftree, vertex);
+ return;
+ case SPF_TYPE_TI_LFA:
+ if (isis_tilfa_check(spftree, vertex) != 0)
+ return;
+
+ pre_spftree = spftree->lfa.old.spftree;
+ route_table = pre_spftree->route_table_backup;
+ allow_ecmp = area->lfa_load_sharing[level - 1];
+ pre_spftree->lfa.protection_counters
+ .tilfa[vertex->N.ip.priority] += 1;
+ break;
+ case SPF_TYPE_FORWARD:
+ case SPF_TYPE_REVERSE:
+ route_table = spftree->route_table;
+ allow_ecmp = true;
+
+ /*
+ * Update LFA protection counters (ignore local
+ * routes).
+ */
+ if (vertex->depth > 1) {
+ spftree->lfa.protection_counters
+ .total[priority] += 1;
+ if (listcount(vertex->Adj_N) > 1)
+ spftree->lfa.protection_counters
+ .ecmp[priority] += 1;
+ }
+ break;
+ }
+
+#ifdef EXTREME_DEBUG
+ struct isis_route_info *ri =
+#endif /* EXTREME_DEBUG */
+ isis_route_create(&vertex->N.ip.p.dest,
+ &vertex->N.ip.p.src,
+ vertex->d_N, vertex->depth,
+ &vertex->N.ip.sr,
+ vertex->Adj_N, allow_ecmp,
+ area, route_table);
+
+#ifdef EXTREME_DEBUG
+ zlog_debug(
+ "ISIS-SPF: A:%hhu create route pfx %pFX dist %d, sr.algo %d, table %p, rv %p",
+ spftree->algorithm, &vertex->N.ip.p.dest,
+ vertex->d_N, vertex->N.ip.sr.algorithm,
+ route_table, ri);
+#endif /* EXTREME_DEBUG */
+ } else if (IS_DEBUG_SPF_EVENTS)
+ zlog_debug(
+ "ISIS-SPF: no adjacencies, do not install route for %s depth %d dist %d",
+ vid2string(vertex, buff, sizeof(buff)),
+ vertex->depth, vertex->d_N);
+ }
+}
+
+static void isis_spf_loop(struct isis_spftree *spftree,
+ uint8_t *root_sysid)
+{
+ struct isis_vertex *vertex;
+ struct isis_lsp *lsp;
+ struct listnode *node;
+
+ while (isis_vertex_queue_count(&spftree->tents)) {
+ vertex = isis_vertex_queue_pop(&spftree->tents);
+
+#ifdef EXTREME_DEBUG
+ zlog_debug(
+ "ISIS-SPF: A:%hhu get TENT node %s %s depth %d dist %d to PATHS",
+ spftree->algorithm, print_sys_hostname(vertex->N.id),
+ vtype2string(vertex->type), vertex->depth, vertex->d_N);
+#endif /* EXTREME_DEBUG */
+
+ add_to_paths(spftree, vertex);
+ if (!VTYPE_IS(vertex->type))
+ continue;
+
+ lsp = lsp_for_vertex(spftree, vertex);
+ if (!lsp) {
+ zlog_warn("ISIS-SPF: No LSP found for %pPN",
+ vertex->N.id);
+ continue;
+ }
+
+ isis_spf_process_lsp(spftree, lsp, vertex->d_N, vertex->depth,
+ root_sysid, vertex);
+ }
+
+ /* Generate routes once the SPT is formed. */
+ for (ALL_QUEUE_ELEMENTS_RO(&spftree->paths, node, vertex)) {
+ /* New-style TLVs take precedence over the old-style TLVs. */
+ switch (vertex->type) {
+ case VTYPE_IPREACH_INTERNAL:
+ case VTYPE_IPREACH_EXTERNAL:
+ if (isis_find_vertex(&spftree->paths, &vertex->N,
+ VTYPE_IPREACH_TE))
+ continue;
+ break;
+ case VTYPE_PSEUDO_IS:
+ case VTYPE_PSEUDO_TE_IS:
+ case VTYPE_NONPSEUDO_IS:
+ case VTYPE_NONPSEUDO_TE_IS:
+ case VTYPE_ES:
+ case VTYPE_IPREACH_TE:
+ case VTYPE_IP6REACH_INTERNAL:
+ case VTYPE_IP6REACH_EXTERNAL:
+ break;
+ }
+
+ spf_path_process(spftree, vertex);
+ }
+}
+
+struct isis_spftree *isis_run_hopcount_spf(struct isis_area *area,
+ uint8_t *sysid,
+ struct isis_spftree *spftree)
+{
+ if (!spftree)
+ spftree = isis_spftree_new(
+ area, &area->lspdb[IS_LEVEL_2 - 1], sysid, ISIS_LEVEL2,
+ SPFTREE_IPV4, SPF_TYPE_FORWARD,
+ F_SPFTREE_HOPCOUNT_METRIC, SR_ALGORITHM_SPF);
+
+ init_spt(spftree, ISIS_MT_IPV4_UNICAST);
+ if (!memcmp(sysid, area->isis->sysid, ISIS_SYS_ID_LEN)) {
+ struct isis_lsp *root_lsp;
+ struct isis_vertex *root_vertex;
+
+ root_lsp = isis_root_system_lsp(spftree->lspdb, spftree->sysid);
+ if (root_lsp) {
+ /*
+ * If we are running locally, initialize with
+ * information from adjacencies
+ */
+ root_vertex = isis_spf_add_root(spftree);
+
+ isis_spf_preload_tent(spftree, sysid, root_lsp,
+ root_vertex);
+ }
+ } else {
+ isis_vertex_queue_insert(
+ &spftree->tents,
+ isis_vertex_new(spftree, sysid, VTYPE_NONPSEUDO_TE_IS));
+ }
+
+ isis_spf_loop(spftree, sysid);
+
+ return spftree;
+}
+
+void isis_run_spf(struct isis_spftree *spftree)
+{
+ struct isis_lsp *root_lsp;
+ struct isis_vertex *root_vertex;
+ struct timeval time_start;
+ struct timeval time_end;
+ struct isis_mt_router_info *mt_router_info;
+ uint16_t mtid = 0;
+#ifndef FABRICD
+ bool flex_algo_enabled;
+#endif /* ifndef FABRICD */
+
+ /* Get time that can't roll backwards. */
+ monotime(&time_start);
+
+ root_lsp = isis_root_system_lsp(spftree->lspdb, spftree->sysid);
+ if (root_lsp == NULL) {
+ zlog_err("ISIS-SPF: could not find own l%d LSP!",
+ spftree->level);
+ return;
+ }
+
+ /* Get Multi-Topology ID. */
+ switch (spftree->tree_id) {
+ case SPFTREE_IPV4:
+ mtid = ISIS_MT_IPV4_UNICAST;
+ break;
+ case SPFTREE_IPV6:
+ mt_router_info = isis_tlvs_lookup_mt_router_info(
+ root_lsp->tlvs, ISIS_MT_IPV6_UNICAST);
+ if (mt_router_info)
+ mtid = ISIS_MT_IPV6_UNICAST;
+ else
+ mtid = ISIS_MT_IPV4_UNICAST;
+ break;
+ case SPFTREE_DSTSRC:
+ mtid = ISIS_MT_IPV6_DSTSRC;
+ break;
+ case SPFTREE_COUNT:
+ zlog_err(
+ "%s should never be called with SPFTREE_COUNT as argument!",
+ __func__);
+ exit(1);
+ }
+
+#ifndef FABRICD
+ /* If a node is configured to participate in a particular Flexible-
+ * Algorithm, but there is no valid Flex-Algorithm definition available
+ * for it, or the selected Flex-Algorithm definition includes
+ * calculation-type, metric-type, constraint, flag, or Sub-TLV that is
+ * not supported by the node, it MUST stop participating in such
+ * Flexible-Algorithm.
+ */
+ if (flex_algo_id_valid(spftree->algorithm)) {
+ flex_algo_enabled = isis_flex_algo_elected_supported(
+ spftree->algorithm, spftree->area);
+ if (flex_algo_enabled !=
+ flex_algo_get_state(spftree->area->flex_algos,
+ spftree->algorithm)) {
+ /* actual state is inconsistent with local LSP */
+ lsp_regenerate_schedule(spftree->area,
+ spftree->area->is_type, 0);
+ goto out;
+ }
+ if (!flex_algo_enabled) {
+ if (!CHECK_FLAG(spftree->flags, F_SPFTREE_DISABLED)) {
+ isis_spftree_clear(spftree);
+ SET_FLAG(spftree->flags, F_SPFTREE_DISABLED);
+ lsp_regenerate_schedule(spftree->area,
+ spftree->area->is_type,
+ 0);
+ }
+ goto out;
+ }
+ }
+#endif /* ifndef FABRICD */
+
+ /*
+ * C.2.5 Step 0
+ */
+ init_spt(spftree, mtid);
+ /* a) */
+ root_vertex = isis_spf_add_root(spftree);
+ /* b) */
+ isis_spf_build_adj_list(spftree, root_lsp);
+ isis_spf_preload_tent(spftree, spftree->sysid, root_lsp, root_vertex);
+
+ /*
+ * C.2.7 Step 2
+ */
+ if (!isis_vertex_queue_count(&spftree->tents)
+ && (IS_DEBUG_SPF_EVENTS)) {
+ zlog_warn("ISIS-SPF: TENT is empty SPF-root:%s",
+ print_sys_hostname(spftree->sysid));
+ }
+
+ isis_spf_loop(spftree, spftree->sysid);
+
+
+#ifndef FABRICD
+ /* flex-algo */
+ if (CHECK_FLAG(spftree->flags, F_SPFTREE_DISABLED)) {
+ UNSET_FLAG(spftree->flags, F_SPFTREE_DISABLED);
+ lsp_regenerate_schedule(spftree->area, spftree->area->is_type,
+ 0);
+ }
+
+out:
+#endif /* ifndef FABRICD */
+ spftree->runcount++;
+ spftree->last_run_timestamp = time(NULL);
+ spftree->last_run_monotime = monotime(&time_end);
+ spftree->last_run_duration =
+ ((time_end.tv_sec - time_start.tv_sec) * 1000000)
+ + (time_end.tv_usec - time_start.tv_usec);
+}
+
+static void isis_run_spf_with_protection(struct isis_area *area,
+ struct isis_spftree *spftree)
+{
+ /* Run forward SPF locally. */
+ memcpy(spftree->sysid, area->isis->sysid, ISIS_SYS_ID_LEN);
+ isis_run_spf(spftree);
+
+ /* Run LFA protection if configured. */
+ if (area->lfa_protected_links[spftree->level - 1] > 0
+ || area->tilfa_protected_links[spftree->level - 1] > 0)
+ isis_spf_run_lfa(area, spftree);
+}
+
+void isis_spf_verify_routes(struct isis_area *area, struct isis_spftree **trees,
+ int tree)
+{
+ if (area->is_type == IS_LEVEL_1) {
+ isis_route_verify_table(area, trees[0]->route_table,
+ trees[0]->route_table_backup, tree);
+ } else if (area->is_type == IS_LEVEL_2) {
+ isis_route_verify_table(area, trees[1]->route_table,
+ trees[1]->route_table_backup, tree);
+ } else {
+ isis_route_verify_merge(area, trees[0]->route_table,
+ trees[0]->route_table_backup,
+ trees[1]->route_table,
+ trees[1]->route_table_backup, tree);
+ }
+}
+
+void isis_spf_invalidate_routes(struct isis_spftree *tree)
+{
+ struct isis_route_table_info *backup_info;
+
+ isis_route_invalidate_table(tree->area, tree->route_table);
+
+ /* Delete backup routes. */
+
+ backup_info = tree->route_table_backup->info;
+ route_table_finish(tree->route_table_backup);
+ isis_route_table_info_free(backup_info);
+ tree->route_table_backup = srcdest_table_init();
+ tree->route_table_backup->info =
+ isis_route_table_info_alloc(tree->algorithm);
+ tree->route_table_backup->cleanup = isis_route_node_cleanup;
+}
+
+void isis_spf_switchover_routes(struct isis_area *area,
+ struct isis_spftree **trees, int family,
+ union g_addr *nexthop_ip, ifindex_t ifindex,
+ int level)
+{
+ isis_route_switchover_nexthop(area, trees[level - 1]->route_table,
+ family, nexthop_ip, ifindex);
+}
+
+static void isis_run_spf_cb(struct event *thread)
+{
+ struct isis_spf_run *run = EVENT_ARG(thread);
+ struct isis_area *area = run->area;
+ int level = run->level;
+ int have_run = 0;
+ struct listnode *node;
+ struct isis_circuit *circuit;
+#ifndef FABRICD
+ struct flex_algo *fa;
+ struct isis_flex_algo_data *data;
+#endif /* ifndef FABRICD */
+
+ XFREE(MTYPE_ISIS_SPF_RUN, run);
+
+ if (!(area->is_type & level)) {
+ if (IS_DEBUG_SPF_EVENTS)
+ zlog_warn("ISIS-SPF (%s) area does not share level",
+ area->area_tag);
+ return;
+ }
+
+ isis_area_delete_backup_adj_sids(area, level);
+ isis_area_invalidate_routes(area, level);
+
+ if (IS_DEBUG_SPF_EVENTS)
+ zlog_debug("ISIS-SPF (%s) L%d SPF needed, periodic SPF",
+ area->area_tag, level);
+
+ if (area->ip_circuits) {
+ isis_run_spf_with_protection(
+ area, area->spftree[SPFTREE_IPV4][level - 1]);
+#ifndef FABRICD
+ for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node,
+ fa)) {
+ data = fa->data;
+ isis_run_spf_with_protection(
+ area, data->spftree[SPFTREE_IPV4][level - 1]);
+ }
+#endif /* ifndef FABRICD */
+ have_run = 1;
+ }
+ if (area->ipv6_circuits) {
+ isis_run_spf_with_protection(
+ area, area->spftree[SPFTREE_IPV6][level - 1]);
+#ifndef FABRICD
+ for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node,
+ fa)) {
+ data = fa->data;
+ isis_run_spf_with_protection(
+ area, data->spftree[SPFTREE_IPV6][level - 1]);
+ }
+#endif /* ifndef FABRICD */
+ have_run = 1;
+ }
+ if (area->ipv6_circuits && isis_area_ipv6_dstsrc_enabled(area)) {
+ isis_run_spf_with_protection(
+ area, area->spftree[SPFTREE_DSTSRC][level - 1]);
+ have_run = 1;
+ }
+
+ if (have_run)
+ area->spf_run_count[level]++;
+
+ isis_area_verify_routes(area);
+
+ /* walk all circuits and reset any spf specific flags */
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit))
+ UNSET_FLAG(circuit->flags, ISIS_CIRCUIT_FLAPPED_AFTER_SPF);
+
+ fabricd_run_spf(area);
+}
+
+static struct isis_spf_run *isis_run_spf_arg(struct isis_area *area, int level)
+{
+ struct isis_spf_run *run = XMALLOC(MTYPE_ISIS_SPF_RUN, sizeof(*run));
+
+ run->area = area;
+ run->level = level;
+
+ return run;
+}
+
+void isis_spf_timer_free(void *run)
+{
+ XFREE(MTYPE_ISIS_SPF_RUN, run);
+}
+
+int _isis_spf_schedule(struct isis_area *area, int level,
+ const char *func, const char *file, int line)
+{
+ struct isis_spftree *spftree;
+ time_t now;
+ long tree_diff, diff;
+ int tree;
+
+ now = monotime(NULL);
+ diff = 0;
+ for (tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
+ spftree = area->spftree[tree][level - 1];
+ tree_diff = difftime(now - spftree->last_run_monotime, 0);
+ if (tree_diff != now && (diff == 0 || tree_diff < diff))
+ diff = tree_diff;
+ }
+
+ if (CHECK_FLAG(im->options, F_ISIS_UNIT_TEST))
+ return 0;
+
+ assert(diff >= 0);
+ assert(area->is_type & level);
+
+ if (IS_DEBUG_SPF_EVENTS) {
+ zlog_debug(
+ "ISIS-SPF (%s) L%d SPF schedule called, lastrun %ld sec ago Caller: %s %s:%d",
+ area->area_tag, level, diff, func, file, line);
+ }
+
+ EVENT_OFF(area->t_rlfa_rib_update);
+ if (area->spf_delay_ietf[level - 1]) {
+ /* Need to call schedule function also if spf delay is running
+ * to
+ * restart holdoff timer - compare
+ * draft-ietf-rtgwg-backoff-algo-04 */
+ long delay =
+ spf_backoff_schedule(area->spf_delay_ietf[level - 1]);
+ if (area->spf_timer[level - 1])
+ return ISIS_OK;
+
+ event_add_timer_msec(master, isis_run_spf_cb,
+ isis_run_spf_arg(area, level), delay,
+ &area->spf_timer[level - 1]);
+ return ISIS_OK;
+ }
+
+ if (area->spf_timer[level - 1])
+ return ISIS_OK;
+
+ /* wait configured min_spf_interval before doing the SPF */
+ long timer;
+ if (diff >= area->min_spf_interval[level - 1]
+ || area->bfd_force_spf_refresh) {
+ /*
+ * Last run is more than min interval ago or BFD signalled a
+ * 'down' message, schedule immediate run
+ */
+ timer = 0;
+
+ if (area->bfd_force_spf_refresh) {
+ zlog_debug(
+ "ISIS-SPF (%s) L%d SPF scheduled immediately due to BFD 'down' message",
+ area->area_tag, level);
+ area->bfd_force_spf_refresh = false;
+ }
+ } else {
+ timer = area->min_spf_interval[level - 1] - diff;
+ }
+
+ event_add_timer(master, isis_run_spf_cb, isis_run_spf_arg(area, level),
+ timer, &area->spf_timer[level - 1]);
+
+ if (IS_DEBUG_SPF_EVENTS)
+ zlog_debug("ISIS-SPF (%s) L%d SPF scheduled %ld sec from now",
+ area->area_tag, level, timer);
+
+ return ISIS_OK;
+}
+
+static void isis_print_paths(struct vty *vty, struct isis_vertex_queue *queue,
+ uint8_t *root_sysid)
+{
+ struct listnode *node;
+ struct isis_vertex *vertex;
+ char buff[VID2STR_BUFFER];
+
+ vty_out(vty,
+ "Vertex Type Metric Next-Hop Interface Parent\n");
+
+ for (ALL_QUEUE_ELEMENTS_RO(queue, node, vertex)) {
+ if (VTYPE_IS(vertex->type)
+ && memcmp(vertex->N.id, root_sysid, ISIS_SYS_ID_LEN) == 0) {
+ vty_out(vty, "%-20s %-12s %-6s",
+ print_sys_hostname(root_sysid), "", "");
+ vty_out(vty, "%-30s\n", "");
+ continue;
+ }
+
+ int rows = 0;
+ struct listnode *anode = listhead(vertex->Adj_N);
+ struct listnode *pnode = listhead(vertex->parents);
+ struct isis_vertex_adj *vadj;
+ struct isis_vertex *pvertex;
+
+ vty_out(vty, "%-20s %-12s %-6u ",
+ vid2string(vertex, buff, sizeof(buff)),
+ vtype2string(vertex->type), vertex->d_N);
+ for (unsigned int i = 0;
+ i < MAX(vertex->Adj_N ? listcount(vertex->Adj_N) : 0,
+ vertex->parents ? listcount(vertex->parents) : 0);
+ i++) {
+ if (anode) {
+ vadj = listgetdata(anode);
+ anode = anode->next;
+ } else {
+ vadj = NULL;
+ }
+
+ if (pnode) {
+ pvertex = listgetdata(pnode);
+ pnode = pnode->next;
+ } else {
+ pvertex = NULL;
+ }
+
+ if (rows) {
+ vty_out(vty, "\n");
+ vty_out(vty, "%-20s %-12s %-6s ", "", "", "");
+ }
+
+ if (vadj) {
+ struct isis_spf_adj *sadj = vadj->sadj;
+
+ vty_out(vty, "%-20s %-9s ",
+ print_sys_hostname(sadj->id),
+ sadj->adj ? sadj->adj->circuit
+ ->interface->name
+ : "-");
+ }
+
+ if (pvertex) {
+ if (!vadj)
+ vty_out(vty, "%-20s %-9s ", "", "");
+
+ vty_out(vty, "%s(%d)",
+ vid2string(pvertex, buff, sizeof(buff)),
+ pvertex->type);
+ }
+
+ ++rows;
+ }
+ vty_out(vty, "\n");
+ }
+}
+
+void isis_print_spftree(struct vty *vty, struct isis_spftree *spftree)
+{
+ const char *tree_id_text = NULL;
+
+ if (!spftree || !isis_vertex_queue_count(&spftree->paths))
+ return;
+
+ switch (spftree->tree_id) {
+ case SPFTREE_IPV4:
+ tree_id_text = "that speak IP";
+ break;
+ case SPFTREE_IPV6:
+ tree_id_text = "that speak IPv6";
+ break;
+ case SPFTREE_DSTSRC:
+ tree_id_text = "that support IPv6 dst-src routing";
+ break;
+ case SPFTREE_COUNT:
+ assert(!"isis_print_spftree shouldn't be called with SPFTREE_COUNT as type");
+ return;
+ }
+
+ vty_out(vty, "IS-IS paths to level-%d routers %s\n", spftree->level,
+ tree_id_text);
+ isis_print_paths(vty, &spftree->paths, spftree->sysid);
+ vty_out(vty, "\n");
+}
+
+static void show_isis_topology_common(struct vty *vty, int levels,
+ struct isis *isis, uint8_t algo)
+{
+#ifndef FABRICD
+ struct isis_flex_algo_data *fa_data;
+ struct flex_algo *fa;
+#endif /* ifndef FABRICD */
+ struct isis_spftree *spftree;
+ struct listnode *node;
+ struct isis_area *area;
+
+ if (!isis->area_list || isis->area_list->count == 0)
+ return;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ vty_out(vty,
+ "Area %s:", area->area_tag ? area->area_tag : "null");
+
+#ifndef FABRICD
+ /*
+ * The shapes of the flex algo spftree 2-dimensional array
+ * and the area spftree 2-dimensional array are not guaranteed
+ * to be identical.
+ */
+ fa = NULL;
+ if (flex_algo_id_valid(algo)) {
+ fa = flex_algo_lookup(area->flex_algos, algo);
+ if (!fa)
+ continue;
+ fa_data = (struct isis_flex_algo_data *)fa->data;
+ } else
+ fa_data = NULL;
+
+ if (algo != SR_ALGORITHM_SPF)
+ vty_out(vty, " Algorithm %hhu\n", algo);
+ else
+#endif /* ifndef FABRICD */
+ vty_out(vty, "\n");
+
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) {
+ if ((level & levels) == 0)
+ continue;
+
+ if (area->ip_circuits > 0) {
+#ifndef FABRICD
+ if (fa_data)
+ spftree = fa_data->spftree[SPFTREE_IPV4]
+ [level - 1];
+ else
+#endif /* ifndef FABRICD */
+ spftree = area->spftree[SPFTREE_IPV4]
+ [level - 1];
+
+ isis_print_spftree(vty, spftree);
+ }
+ if (area->ipv6_circuits > 0) {
+#ifndef FABRICD
+ if (fa_data)
+ spftree = fa_data->spftree[SPFTREE_IPV6]
+ [level - 1];
+ else
+#endif /* ifndef FABRICD */
+ spftree = area->spftree[SPFTREE_IPV6]
+ [level - 1];
+ isis_print_spftree(vty, spftree);
+ }
+ if (isis_area_ipv6_dstsrc_enabled(area)) {
+#ifndef FABRICD
+ if (fa_data)
+ spftree =
+ fa_data->spftree[SPFTREE_DSTSRC]
+ [level - 1];
+ else
+#endif /* ifndef FABRICD */
+ spftree = area->spftree[SPFTREE_DSTSRC]
+ [level - 1];
+ isis_print_spftree(vty, spftree);
+ }
+ }
+
+ if (fabricd_spftree(area)) {
+ vty_out(vty,
+ "IS-IS paths to level-2 routers with hop-by-hop metric\n");
+ isis_print_paths(vty, &fabricd_spftree(area)->paths, isis->sysid);
+ vty_out(vty, "\n");
+ }
+
+ vty_out(vty, "\n");
+ }
+}
+
+DEFUN(show_isis_topology, show_isis_topology_cmd,
+ "show " PROTO_NAME
+ " [vrf <NAME|all>] topology"
+#ifndef FABRICD
+ " [<level-1|level-2>]"
+ " [algorithm (128-255)]"
+#endif /* ifndef FABRICD */
+ ,
+ SHOW_STR PROTO_HELP VRF_CMD_HELP_STR
+ "All VRFs\n"
+ "IS-IS paths to Intermediate Systems\n"
+#ifndef FABRICD
+ "Paths to all level-1 routers in the area\n"
+ "Paths to all level-2 routers in the domain\n"
+ "Show Flex-algo routes\n"
+ "Algorithm number\n"
+#endif /* ifndef FABRICD */
+)
+{
+ int levels = ISIS_LEVELS;
+ struct listnode *node;
+ struct isis *isis = NULL;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx_vrf = 0;
+ uint8_t algorithm = SR_ALGORITHM_SPF;
+#ifndef FABRICD
+ int idx = 0;
+
+ levels = ISIS_LEVEL1 | ISIS_LEVEL2;
+ if (argv_find(argv, argc, "level-1", &idx))
+ levels = ISIS_LEVEL1;
+ if (argv_find(argv, argc, "level-2", &idx))
+ levels = ISIS_LEVEL2;
+ if (argv_find(argv, argc, "algorithm", &idx))
+ algorithm = (uint8_t)strtoul(argv[idx + 1]->arg, NULL, 10);
+#endif /* ifndef FABRICD */
+
+ if (!im) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
+ show_isis_topology_common(vty, levels, isis,
+ algorithm);
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL)
+ show_isis_topology_common(vty, levels, isis, algorithm);
+ }
+
+ return CMD_SUCCESS;
+}
+
+#ifndef FABRICD
+static void show_isis_flex_algo_display_eag(struct vty *vty, char *buf,
+ int indent,
+ struct admin_group *admin_group)
+{
+ if (admin_group_zero(admin_group))
+ vty_out(vty, "not-set\n");
+ else {
+ vty_out(vty, "%s\n",
+ admin_group_string(buf, ADMIN_GROUP_PRINT_MAX_SIZE,
+ indent, admin_group));
+ admin_group_print(buf, indent, admin_group);
+ if (buf[0] != '\0')
+ vty_out(vty, " Bit positions: %s\n", buf);
+ }
+}
+
+static void show_isis_flex_algo_common(struct vty *vty, struct isis *isis,
+ uint8_t algorithm)
+{
+ struct isis_router_cap_fad *router_fad;
+ char buf[ADMIN_GROUP_PRINT_MAX_SIZE];
+ struct admin_group *admin_group;
+ struct isis_area *area;
+ struct listnode *node;
+ struct flex_algo *fa;
+ int indent, algo;
+ bool fad_identical, fad_supported;
+
+ if (!isis->area_list || isis->area_list->count == 0)
+ return;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ /*
+ * The shapes of the flex algo spftree 2-dimensional array
+ * and the area spftree 2-dimensional array are not guaranteed
+ * to be identical.
+ */
+
+ for (algo = 0; algo < SR_ALGORITHM_COUNT; algo++) {
+ if (algorithm != SR_ALGORITHM_UNSET &&
+ algorithm != algo)
+ continue;
+
+ fa = flex_algo_lookup(area->flex_algos, algo);
+ if (!fa)
+ continue;
+
+ vty_out(vty, "Area %s:",
+ area->area_tag ? area->area_tag : "null");
+
+ vty_out(vty, " Algorithm %d\n", algo);
+ vty_out(vty, "\n");
+
+ vty_out(vty, " Enabled Data-Planes:");
+ if (fa->dataplanes == 0) {
+ vty_out(vty, " None\n\n");
+ continue;
+ }
+ if (CHECK_FLAG(fa->dataplanes, FLEX_ALGO_SR_MPLS))
+ vty_out(vty, " SR-MPLS");
+ if (CHECK_FLAG(fa->dataplanes, FLEX_ALGO_SRV6))
+ vty_out(vty, " SRv6");
+ if (CHECK_FLAG(fa->dataplanes, FLEX_ALGO_IP))
+ vty_out(vty, " IP");
+ vty_out(vty, "\n\n");
+
+
+ router_fad = isis_flex_algo_elected(algo, area);
+ vty_out(vty,
+ " Elected and running Flexible-Algorithm Definition:\n");
+ if (router_fad)
+ vty_out(vty, " Source: %pSY\n",
+ router_fad->sysid);
+ else
+ vty_out(vty, " Source: Not found\n");
+
+ if (!router_fad) {
+ vty_out(vty, "\n");
+ continue;
+ }
+
+ fad_identical =
+ flex_algo_definition_cmp(fa, &router_fad->fad);
+ fad_supported =
+ isis_flex_algo_supported(&router_fad->fad);
+ vty_out(vty, " Priority: %d\n",
+ router_fad->fad.priority);
+ vty_out(vty, " Equal to local: %s\n",
+ fad_identical ? "yes" : "no");
+ vty_out(vty, " Local state: %s\n",
+ fad_supported
+ ? "enabled"
+ : "disabled (unsupported definition)");
+ vty_out(vty, " Calculation type: ");
+ if (router_fad->fad.calc_type == 0)
+ vty_out(vty, "spf\n");
+ else
+ vty_out(vty, "%d\n", router_fad->fad.calc_type);
+ vty_out(vty, " Metric type: %s\n",
+ flex_algo_metric_type_print(
+ buf, sizeof(buf),
+ router_fad->fad.metric_type));
+ vty_out(vty, " Prefix-metric: %s\n",
+ CHECK_FLAG(router_fad->fad.flags, FAD_FLAG_M)
+ ? "enabled"
+ : "disabled");
+ if (router_fad->fad.flags != 0 &&
+ router_fad->fad.flags != FAD_FLAG_M)
+ vty_out(vty, " Flags: 0x%x\n",
+ router_fad->fad.flags);
+ vty_out(vty, " Exclude SRLG: %s\n",
+ router_fad->fad.exclude_srlg ? "enabled"
+ : "disabled");
+
+ admin_group = &router_fad->fad.admin_group_exclude_any;
+ indent = vty_out(vty, " Exclude-any admin-group: ");
+ show_isis_flex_algo_display_eag(vty, buf, indent,
+ admin_group);
+
+ admin_group = &router_fad->fad.admin_group_include_all;
+ indent = vty_out(vty, " Include-all admin-group: ");
+ show_isis_flex_algo_display_eag(vty, buf, indent,
+ admin_group);
+
+ admin_group = &router_fad->fad.admin_group_include_any;
+ indent = vty_out(vty, " Include-any admin-group: ");
+ show_isis_flex_algo_display_eag(vty, buf, indent,
+ admin_group);
+
+ if (router_fad->fad.unsupported_subtlv)
+ vty_out(vty,
+ " Unsupported sub-TLV: Present (see logs)");
+
+ vty_out(vty, "\n");
+ }
+ }
+}
+
+DEFUN(show_isis_flex_algo, show_isis_flex_algo_cmd,
+ "show " PROTO_NAME
+ " [vrf <NAME|all>] flex-algo"
+ " [(128-255)]",
+ SHOW_STR PROTO_HELP VRF_CMD_HELP_STR
+ "All VRFs\n"
+ "IS-IS Flex-algo information\n"
+ "Algorithm number\n")
+{
+ struct isis *isis;
+ struct listnode *node;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx = 0;
+ int idx_vrf = 0;
+ uint8_t flex_algo;
+
+ if (!im) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+
+ if (argv_find(argv, argc, "flex-algo", &idx) && (idx + 1) < argc)
+ flex_algo = (uint8_t)strtoul(argv[idx + 1]->arg, NULL, 10);
+ else
+ flex_algo = SR_ALGORITHM_UNSET;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
+ show_isis_flex_algo_common(vty, isis,
+ flex_algo);
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL)
+ show_isis_flex_algo_common(vty, isis, flex_algo);
+ }
+
+ return CMD_SUCCESS;
+}
+#endif /* ifndef FABRICD */
+
+static void isis_print_route(struct ttable *tt, const struct prefix *prefix,
+ struct isis_route_info *rinfo, bool prefix_sid,
+ bool no_adjacencies, bool json)
+{
+ struct isis_nexthop *nexthop;
+ struct listnode *node;
+ bool first = true;
+ char buf_prefix[BUFSIZ];
+
+ (void)prefix2str(prefix, buf_prefix, sizeof(buf_prefix));
+ for (int alg = 0; alg < SR_ALGORITHM_COUNT; alg++) {
+ for (ALL_LIST_ELEMENTS_RO(rinfo->sr_algo[alg].nexthops, node,
+ nexthop)) {
+ struct interface *ifp;
+ char buf_iface[BUFSIZ];
+ char buf_nhop[BUFSIZ];
+
+ if (!no_adjacencies) {
+ inet_ntop(nexthop->family, &nexthop->ip,
+ buf_nhop, sizeof(buf_nhop));
+ ifp = if_lookup_by_index(nexthop->ifindex,
+ VRF_DEFAULT);
+ if (ifp)
+ strlcpy(buf_iface, ifp->name,
+ sizeof(buf_iface));
+ else
+ snprintf(buf_iface, sizeof(buf_iface),
+ "ifindex %u",
+ nexthop->ifindex);
+ } else {
+ strlcpy(buf_nhop,
+ print_sys_hostname(nexthop->sysid),
+ sizeof(buf_nhop));
+ strlcpy(buf_iface, "-", sizeof(buf_iface));
+ }
+
+ if (prefix_sid) {
+ char buf_sid[BUFSIZ] = {};
+ char buf_lblop[BUFSIZ] = {};
+
+ if (rinfo->sr_algo[alg].present) {
+ snprintf(buf_sid, sizeof(buf_sid), "%u",
+ rinfo->sr_algo[alg].sid.value);
+ sr_op2str(buf_lblop, sizeof(buf_lblop),
+ rinfo->sr_algo[alg].label,
+ nexthop->sr.label);
+ } else if (alg == SR_ALGORITHM_SPF) {
+ strlcpy(buf_sid, "-", sizeof(buf_sid));
+ strlcpy(buf_lblop, "-",
+ sizeof(buf_lblop));
+ } else {
+ continue;
+ }
+
+ if (first || json) {
+ ttable_add_row(tt,
+ "%s|%u|%s|%s|%s|%s|%d",
+ buf_prefix, rinfo->cost,
+ buf_iface, buf_nhop,
+ buf_sid, buf_lblop, alg);
+ first = false;
+ } else
+ ttable_add_row(tt, "||%s|%s|%s|%s|%d",
+ buf_iface, buf_nhop,
+ buf_sid, buf_lblop, alg);
+ } else {
+ char buf_labels[BUFSIZ] = {};
+
+ if (nexthop->label_stack) {
+ for (int i = 0;
+ i <
+ nexthop->label_stack->num_labels;
+ i++) {
+ char buf_label[BUFSIZ];
+
+ label2str(nexthop->label_stack
+ ->label[i],
+ 0, buf_label,
+ sizeof(buf_label));
+ if (i != 0)
+ strlcat(buf_labels, "/",
+ sizeof(buf_labels));
+ strlcat(buf_labels, buf_label,
+ sizeof(buf_labels));
+ }
+ } else if (nexthop->sr.present)
+ label2str(nexthop->sr.label, 0,
+ buf_labels,
+ sizeof(buf_labels));
+ else
+ strlcpy(buf_labels, "-",
+ sizeof(buf_labels));
+
+ if (first || json) {
+ ttable_add_row(tt, "%s|%u|%s|%s|%s",
+ buf_prefix, rinfo->cost,
+ buf_iface, buf_nhop,
+ buf_labels);
+ first = false;
+ } else
+ ttable_add_row(tt, "||%s|%s|%s",
+ buf_iface, buf_nhop,
+ buf_labels);
+ }
+ }
+ }
+
+ if (list_isempty(rinfo->nexthops)) {
+ if (prefix_sid) {
+ char buf_sid[BUFSIZ] = {};
+ char buf_lblop[BUFSIZ] = {};
+
+ if (rinfo->sr_algo[SR_ALGORITHM_SPF].present) {
+ snprintf(buf_sid, sizeof(buf_sid), "%u",
+ rinfo->sr_algo[SR_ALGORITHM_SPF]
+ .sid.value);
+ sr_op2str(
+ buf_lblop, sizeof(buf_lblop),
+ rinfo->sr_algo[SR_ALGORITHM_SPF].label,
+ MPLS_LABEL_IMPLICIT_NULL);
+ } else {
+ strlcpy(buf_sid, "-", sizeof(buf_sid));
+ strlcpy(buf_lblop, "-", sizeof(buf_lblop));
+ }
+
+ ttable_add_row(tt, "%s|%u|%s|%s|%s|%s", buf_prefix,
+ rinfo->cost, "-", "-", buf_sid,
+ buf_lblop);
+ } else
+ ttable_add_row(tt, "%s|%u|%s|%s|%s", buf_prefix,
+ rinfo->cost, "-", "-", "-");
+ }
+}
+
+void isis_print_routes(struct vty *vty, struct isis_spftree *spftree,
+ struct json_object **json, bool prefix_sid, bool backup)
+{
+ struct route_table *route_table;
+ struct ttable *tt;
+ struct route_node *rn;
+ bool no_adjacencies = false;
+ const char *tree_id_text = NULL;
+
+ if (!spftree)
+ return;
+
+ switch (spftree->tree_id) {
+ case SPFTREE_IPV4:
+ tree_id_text = "IPv4";
+ break;
+ case SPFTREE_IPV6:
+ tree_id_text = "IPv6";
+ break;
+ case SPFTREE_DSTSRC:
+ tree_id_text = "IPv6 (dst-src routing)";
+ break;
+ case SPFTREE_COUNT:
+ assert(!"isis_print_routes shouldn't be called with SPFTREE_COUNT as type");
+ return;
+ }
+
+ if (json == NULL)
+ vty_out(vty, "IS-IS %s %s routing table:\n\n",
+ circuit_t2string(spftree->level), tree_id_text);
+
+ /* Prepare table. */
+ tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+ if (prefix_sid)
+ ttable_add_row(
+ tt,
+ "Prefix|Metric|Interface|Nexthop|SID|Label Op.|Algo");
+ else
+ ttable_add_row(tt, "Prefix|Metric|Interface|Nexthop|Label(s)");
+ tt->style.cell.rpad = 2;
+ tt->style.corner = '+';
+ ttable_restyle(tt);
+ ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+ if (CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES))
+ no_adjacencies = true;
+
+ route_table =
+ (backup) ? spftree->route_table_backup : spftree->route_table;
+ for (rn = route_top(route_table); rn; rn = route_next(rn)) {
+ struct isis_route_info *rinfo;
+
+ rinfo = rn->info;
+ if (!rinfo)
+ continue;
+
+ isis_print_route(tt, &rn->p, rinfo, prefix_sid, no_adjacencies,
+ json != NULL);
+ }
+
+ /* Dump the generated table. */
+ if (json == NULL && tt->nrows > 1) {
+ char *table;
+
+ table = ttable_dump(tt, "\n");
+ vty_out(vty, "%s\n", table);
+ XFREE(MTYPE_TMP, table);
+ } else if (json) {
+ *json = ttable_json(tt, prefix_sid ? "sdssdsdd" : "sdsss");
+ }
+ ttable_del(tt);
+}
+
+static void show_isis_route_common(struct vty *vty, int levels,
+ struct isis *isis, bool prefix_sid,
+ bool backup, uint8_t algo,
+ json_object **json)
+{
+ json_object *json_level = NULL, *jstr = NULL, *json_val;
+#ifndef FABRICD
+ struct isis_flex_algo_data *fa_data;
+ struct flex_algo *fa;
+#endif /* ifndef FABRICD */
+ struct isis_spftree *spftree;
+ struct listnode *node;
+ struct isis_area *area;
+ char key[8];
+
+ if (!isis->area_list || isis->area_list->count == 0)
+ return;
+
+ if (json)
+ *json = json_object_new_object();
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+#ifndef FABRICD
+ /*
+ * The shapes of the flex algo spftree 2-dimensional array
+ * and the area spftree 2-dimensional array are not guaranteed
+ * to be identical.
+ */
+ fa = NULL;
+ if (flex_algo_id_valid(algo)) {
+ fa = flex_algo_lookup(area->flex_algos, algo);
+ if (!fa)
+ continue;
+ fa_data = (struct isis_flex_algo_data *)fa->data;
+ } else {
+ fa_data = NULL;
+ }
+#endif /* ifndef FABRICD */
+
+ if (json) {
+ jstr = json_object_new_string(
+ area->area_tag ? area->area_tag : "null");
+ json_object_object_add(*json, "area", jstr);
+ } else {
+ vty_out(vty, "Area %s:",
+ area->area_tag ? area->area_tag : "null");
+#ifndef FABRICD
+ if (algo != SR_ALGORITHM_SPF)
+ vty_out(vty, " Algorithm %hhu\n", algo);
+ else
+#endif /* ifndef FABRICD */
+ vty_out(vty, "\n");
+ }
+
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) {
+ if ((level & levels) == 0)
+ continue;
+
+ if (json) {
+ json_level = json_object_new_object();
+ jstr = json_object_new_string(
+ area->area_tag ? area->area_tag
+ : "null");
+ json_object_object_add(json_level, "area",
+ jstr);
+ }
+
+ if (area->ip_circuits > 0) {
+ json_val = NULL;
+#ifndef FABRICD
+ if (fa_data)
+ spftree = fa_data->spftree[SPFTREE_IPV4]
+ [level - 1];
+ else
+#endif /* ifndef FABRICD */
+ spftree = area->spftree[SPFTREE_IPV4]
+ [level - 1];
+
+ if (!json)
+ isis_print_spftree(vty, spftree);
+
+ isis_print_routes(vty, spftree,
+ json ? &json_val : NULL,
+ prefix_sid, backup);
+ if (json && json_val) {
+ json_object_object_add(
+ json_level, "ipv4", json_val);
+ }
+ }
+ if (area->ipv6_circuits > 0) {
+ json_val = NULL;
+#ifndef FABRICD
+ if (fa_data)
+ spftree = fa_data->spftree[SPFTREE_IPV6]
+ [level - 1];
+ else
+#endif /* ifndef FABRICD */
+ spftree = area->spftree[SPFTREE_IPV6]
+ [level - 1];
+
+ if (!json)
+ isis_print_spftree(vty, spftree);
+
+ isis_print_routes(vty, spftree,
+ json ? &json_val : NULL,
+ prefix_sid, backup);
+ if (json && json_val) {
+ json_object_object_add(
+ json_level, "ipv6", json_val);
+ }
+ }
+ if (isis_area_ipv6_dstsrc_enabled(area)) {
+ json_val = NULL;
+#ifndef FABRICD
+ if (fa_data)
+ spftree =
+ fa_data->spftree[SPFTREE_DSTSRC]
+ [level - 1];
+ else
+#endif /* ifndef FABRICD */
+ spftree = area->spftree[SPFTREE_DSTSRC]
+ [level - 1];
+
+ if (!json)
+ isis_print_spftree(vty, spftree);
+ isis_print_routes(vty, spftree,
+ json ? &json_val : NULL,
+ prefix_sid, backup);
+ if (json && json_val) {
+ json_object_object_add(json_level,
+ "ipv6-dstsrc",
+ json_val);
+ }
+ }
+ if (json) {
+ snprintf(key, sizeof(key), "level-%d", level);
+ json_object_object_add(*json, key, json_level);
+ }
+ }
+ }
+}
+
+DEFUN(show_isis_route, show_isis_route_cmd,
+ "show " PROTO_NAME
+ " [vrf <NAME|all>] route"
+#ifndef FABRICD
+ " [<level-1|level-2>]"
+#endif /* ifndef FABRICD */
+ " [<prefix-sid|backup>]"
+#ifndef FABRICD
+ " [algorithm (128-255)]"
+#endif /* ifndef FABRICD */
+ " [json$uj]",
+ SHOW_STR PROTO_HELP VRF_FULL_CMD_HELP_STR
+ "IS-IS routing table\n"
+#ifndef FABRICD
+ "level-1 routes\n"
+ "level-2 routes\n"
+#endif /* ifndef FABRICD */
+ "Show Prefix-SID information\n"
+ "Show backup routes\n"
+#ifndef FABRICD
+ "Show Flex-algo routes\n"
+ "Algorithm number\n"
+#endif /* ifndef FABRICD */
+ JSON_STR)
+{
+ int levels;
+ struct isis *isis;
+ struct listnode *node;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ bool prefix_sid = false;
+ bool backup = false;
+ bool uj = use_json(argc, argv);
+ int idx = 0;
+ json_object *json = NULL, *json_vrf = NULL;
+ uint8_t algorithm = SR_ALGORITHM_SPF;
+
+ if (argv_find(argv, argc, "level-1", &idx))
+ levels = ISIS_LEVEL1;
+ else if (argv_find(argv, argc, "level-2", &idx))
+ levels = ISIS_LEVEL2;
+ else
+ levels = ISIS_LEVEL1 | ISIS_LEVEL2;
+
+ if (!im) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+ ISIS_FIND_VRF_ARGS(argv, argc, idx, vrf_name, all_vrf);
+
+ if (argv_find(argv, argc, "prefix-sid", &idx))
+ prefix_sid = true;
+ if (argv_find(argv, argc, "backup", &idx))
+ backup = true;
+
+#ifndef FABRICD
+ if (argv_find(argv, argc, "algorithm", &idx))
+ algorithm = (uint8_t)strtoul(argv[idx + 1]->arg, NULL, 10);
+#endif /* ifndef FABRICD */
+
+ if (uj)
+ json = json_object_new_array();
+
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) {
+ show_isis_route_common(
+ vty, levels, isis, prefix_sid, backup,
+ algorithm, uj ? &json_vrf : NULL);
+ if (uj) {
+ json_object_object_add(
+ json_vrf, "vrf_id",
+ json_object_new_int(
+ isis->vrf_id));
+ json_object_array_add(json, json_vrf);
+ }
+ }
+ goto out;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL) {
+ show_isis_route_common(vty, levels, isis, prefix_sid,
+ backup, algorithm,
+ uj ? &json_vrf : NULL);
+ if (uj) {
+ json_object_object_add(
+ json_vrf, "vrf_id",
+ json_object_new_int(isis->vrf_id));
+ json_object_array_add(json, json_vrf);
+ }
+ }
+ }
+
+out:
+ if (uj) {
+ vty_out(vty, "%s\n",
+ json_object_to_json_string_ext(
+ json, JSON_C_TO_STRING_PRETTY));
+ json_object_free(json);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void isis_print_frr_summary_line(struct ttable *tt,
+ const char *protection,
+ uint32_t counters[SPF_PREFIX_PRIO_MAX])
+{
+ uint32_t critical, high, medium, low, total;
+
+ critical = counters[SPF_PREFIX_PRIO_CRITICAL];
+ high = counters[SPF_PREFIX_PRIO_HIGH];
+ medium = counters[SPF_PREFIX_PRIO_MEDIUM];
+ low = counters[SPF_PREFIX_PRIO_LOW];
+ total = critical + high + medium + low;
+
+ ttable_add_row(tt, "%s|%u|%u|%u|%u|%u", protection, critical, high,
+ medium, low, total);
+}
+
+static void
+isis_print_frr_summary_line_coverage(struct ttable *tt, const char *protection,
+ double counters[SPF_PREFIX_PRIO_MAX],
+ double total)
+{
+ double critical, high, medium, low;
+
+ critical = counters[SPF_PREFIX_PRIO_CRITICAL] * 100;
+ high = counters[SPF_PREFIX_PRIO_HIGH] * 100;
+ medium = counters[SPF_PREFIX_PRIO_MEDIUM] * 100;
+ low = counters[SPF_PREFIX_PRIO_LOW] * 100;
+ total *= 100;
+
+ ttable_add_row(tt, "%s|%.2f%%|%.2f%%|%.2f%%|%.2f%%|%.2f%%", protection,
+ critical, high, medium, low, total);
+}
+
+static void isis_print_frr_summary(struct vty *vty,
+ struct isis_spftree *spftree)
+{
+ struct ttable *tt;
+ char *table;
+ const char *tree_id_text = NULL;
+ uint32_t protectd[SPF_PREFIX_PRIO_MAX] = {0};
+ uint32_t unprotected[SPF_PREFIX_PRIO_MAX] = {0};
+ double coverage[SPF_PREFIX_PRIO_MAX] = {0};
+ uint32_t protected_total = 0, grand_total = 0;
+ double coverage_total;
+
+ if (!spftree)
+ return;
+
+ switch (spftree->tree_id) {
+ case SPFTREE_IPV4:
+ tree_id_text = "IPv4";
+ break;
+ case SPFTREE_IPV6:
+ tree_id_text = "IPv6";
+ break;
+ case SPFTREE_DSTSRC:
+ tree_id_text = "IPv6 (dst-src routing)";
+ break;
+ case SPFTREE_COUNT:
+ assert(!"isis_print_frr_summary shouldn't be called with SPFTREE_COUNT as type");
+ return;
+ }
+
+ vty_out(vty, " IS-IS %s %s Fast ReRoute summary:\n\n",
+ circuit_t2string(spftree->level), tree_id_text);
+
+ /* Prepare table. */
+ tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+ ttable_add_row(
+ tt,
+ "Protection \\ Priority|Critical|High |Medium |Low |Total");
+ tt->style.cell.rpad = 2;
+ tt->style.corner = '+';
+ ttable_restyle(tt);
+ ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+ /* Compute unprotected and coverage totals. */
+ for (int priority = SPF_PREFIX_PRIO_CRITICAL;
+ priority < SPF_PREFIX_PRIO_MAX; priority++) {
+ uint32_t *lfa = spftree->lfa.protection_counters.lfa;
+ uint32_t *rlfa = spftree->lfa.protection_counters.rlfa;
+ uint32_t *tilfa = spftree->lfa.protection_counters.tilfa;
+ uint32_t *ecmp = spftree->lfa.protection_counters.ecmp;
+ uint32_t *total = spftree->lfa.protection_counters.total;
+
+ protectd[priority] = lfa[priority] + rlfa[priority]
+ + tilfa[priority] + ecmp[priority];
+ /* Safeguard to protect against possible inconsistencies. */
+ if (protectd[priority] > total[priority])
+ protectd[priority] = total[priority];
+ unprotected[priority] = total[priority] - protectd[priority];
+ protected_total += protectd[priority];
+ grand_total += total[priority];
+
+ if (!total[priority])
+ coverage[priority] = 0;
+ else
+ coverage[priority] =
+ protectd[priority] / (double)total[priority];
+ }
+
+ if (!grand_total)
+ coverage_total = 0;
+ else
+ coverage_total = protected_total / (double)grand_total;
+
+ /* Add rows. */
+ isis_print_frr_summary_line(tt, "Classic LFA",
+ spftree->lfa.protection_counters.lfa);
+ isis_print_frr_summary_line(tt, "Remote LFA",
+ spftree->lfa.protection_counters.rlfa);
+ isis_print_frr_summary_line(tt, "Topology Independent LFA",
+ spftree->lfa.protection_counters.tilfa);
+ isis_print_frr_summary_line(tt, "ECMP",
+ spftree->lfa.protection_counters.ecmp);
+ isis_print_frr_summary_line(tt, "Unprotected", unprotected);
+ isis_print_frr_summary_line_coverage(tt, "Protection coverage",
+ coverage, coverage_total);
+
+ /* Dump the generated table. */
+ table = ttable_dump(tt, "\n");
+ vty_out(vty, "%s\n", table);
+ XFREE(MTYPE_TMP, table);
+ ttable_del(tt);
+}
+
+static void show_isis_frr_summary_common(struct vty *vty, int levels,
+ struct isis *isis)
+{
+ struct listnode *node;
+ struct isis_area *area;
+
+ if (!isis->area_list || isis->area_list->count == 0)
+ return;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ vty_out(vty, "Area %s:\n",
+ area->area_tag ? area->area_tag : "null");
+
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) {
+ if ((level & levels) == 0)
+ continue;
+
+ if (area->ip_circuits > 0) {
+ isis_print_frr_summary(
+ vty,
+ area->spftree[SPFTREE_IPV4][level - 1]);
+ }
+ if (area->ipv6_circuits > 0) {
+ isis_print_frr_summary(
+ vty,
+ area->spftree[SPFTREE_IPV6][level - 1]);
+ }
+ if (isis_area_ipv6_dstsrc_enabled(area)) {
+ isis_print_frr_summary(
+ vty, area->spftree[SPFTREE_DSTSRC]
+ [level - 1]);
+ }
+ }
+ }
+}
+
+DEFUN(show_isis_frr_summary, show_isis_frr_summary_cmd,
+ "show " PROTO_NAME
+ " [vrf <NAME|all>] fast-reroute summary"
+#ifndef FABRICD
+ " [<level-1|level-2>]"
+#endif
+ ,
+ SHOW_STR PROTO_HELP VRF_FULL_CMD_HELP_STR
+ "IS-IS FRR information\n"
+ "FRR summary\n"
+#ifndef FABRICD
+ "level-1 routes\n"
+ "level-2 routes\n"
+#endif
+)
+{
+ int levels;
+ struct isis *isis;
+ struct listnode *node;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx = 0;
+
+ if (argv_find(argv, argc, "level-1", &idx))
+ levels = ISIS_LEVEL1;
+ else if (argv_find(argv, argc, "level-2", &idx))
+ levels = ISIS_LEVEL2;
+ else
+ levels = ISIS_LEVEL1 | ISIS_LEVEL2;
+
+ if (!im) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+ ISIS_FIND_VRF_ARGS(argv, argc, idx, vrf_name, all_vrf);
+
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
+ show_isis_frr_summary_common(vty, levels, isis);
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL)
+ show_isis_frr_summary_common(vty, levels, isis);
+ }
+
+ return CMD_SUCCESS;
+}
+
+void isis_spf_init(void)
+{
+#ifndef FABRICD
+ install_element(VIEW_NODE, &show_isis_flex_algo_cmd);
+#endif /* ifndef FABRICD */
+ install_element(VIEW_NODE, &show_isis_topology_cmd);
+ install_element(VIEW_NODE, &show_isis_route_cmd);
+ install_element(VIEW_NODE, &show_isis_frr_summary_cmd);
+
+ /* Register hook(s). */
+ hook_register(isis_adj_state_change_hook, spf_adj_state_change);
+}
+
+void isis_spf_print(struct isis_spftree *spftree, struct vty *vty)
+{
+ uint64_t last_run_duration = spftree->last_run_duration;
+
+ vty_out(vty, " last run elapsed : ");
+ vty_out_timestr(vty, spftree->last_run_timestamp);
+ vty_out(vty, "\n");
+
+ vty_out(vty, " last run duration : %" PRIu64 " usec\n",
+ last_run_duration);
+
+ vty_out(vty, " run count : %u\n", spftree->runcount);
+}
+void isis_spf_print_json(struct isis_spftree *spftree, struct json_object *json)
+{
+ char uptime[MONOTIME_STRLEN];
+ time_t cur;
+ cur = time(NULL);
+ cur -= spftree->last_run_timestamp;
+ frrtime_to_interval(cur, uptime, sizeof(uptime));
+ json_object_string_add(json, "last-run-elapsed", uptime);
+ json_object_int_add(json, "last-run-duration-usec",
+ spftree->last_run_duration);
+ json_object_int_add(json, "last-run-count", spftree->runcount);
+}
diff --git a/isisd/isis_spf.h b/isisd/isis_spf.h
new file mode 100644
index 0000000..7e9754d
--- /dev/null
+++ b/isisd/isis_spf.h
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_spf.h
+ * IS-IS Shortest Path First algorithm
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#ifndef _ZEBRA_ISIS_SPF_H
+#define _ZEBRA_ISIS_SPF_H
+
+#include "isisd/isis_lfa.h"
+#include "lib/json.h"
+
+struct isis_spftree;
+
+enum spf_type {
+ SPF_TYPE_FORWARD = 1,
+ SPF_TYPE_REVERSE,
+ SPF_TYPE_RLFA,
+ SPF_TYPE_TI_LFA,
+};
+
+struct isis_spf_adj {
+ uint8_t id[ISIS_SYS_ID_LEN + 1];
+ struct isis_adjacency *adj;
+ uint32_t metric;
+ struct isis_ext_subtlvs *subtlvs;
+ struct isis_lsp *lsp;
+ struct {
+ uint8_t desig_is_id[ISIS_SYS_ID_LEN + 1];
+ } lan;
+ uint8_t flags;
+#define F_ISIS_SPF_ADJ_BROADCAST 0x01
+#define F_ISIS_SPF_ADJ_OLDMETRIC 0x02
+#define F_ISIS_SPF_ADJ_METRIC_INFINITY 0x04
+};
+
+struct isis_spftree *
+isis_spftree_new(struct isis_area *area, struct lspdb_head *lspdb,
+ const uint8_t *sysid, int level, enum spf_tree_id tree_id,
+ enum spf_type type, uint8_t flags, uint8_t algorithm);
+struct isis_vertex *isis_spf_prefix_sid_lookup(struct isis_spftree *spftree,
+ struct isis_prefix_sid *psid);
+void isis_spf_invalidate_routes(struct isis_spftree *tree);
+void isis_spf_verify_routes(struct isis_area *area, struct isis_spftree **trees,
+ int tree);
+void isis_spf_switchover_routes(struct isis_area *area,
+ struct isis_spftree **trees, int family,
+ union g_addr *nexthop_ip, ifindex_t ifindex,
+ int level);
+void isis_spftree_del(struct isis_spftree *spftree);
+void spftree_area_init(struct isis_area *area);
+void spftree_area_del(struct isis_area *area);
+struct isis_lsp *isis_root_system_lsp(struct lspdb_head *lspdb,
+ const uint8_t *sysid);
+#define isis_spf_schedule(area, level) \
+ _isis_spf_schedule((area), (level), __func__, \
+ __FILE__, __LINE__)
+int _isis_spf_schedule(struct isis_area *area, int level,
+ const char *func, const char *file, int line);
+void isis_print_spftree(struct vty *vty, struct isis_spftree *spftree);
+void isis_print_routes(struct vty *vty, struct isis_spftree *spftree,
+ json_object **json, bool prefix_sid, bool backup);
+void isis_spf_init(void);
+void isis_spf_print(struct isis_spftree *spftree, struct vty *vty);
+void isis_spf_print_json(struct isis_spftree *spftree,
+ struct json_object *json);
+void isis_run_spf(struct isis_spftree *spftree);
+struct isis_spftree *isis_run_hopcount_spf(struct isis_area *area,
+ uint8_t *sysid,
+ struct isis_spftree *spftree);
+
+void isis_spf_timer_free(void *run);
+#endif /* _ZEBRA_ISIS_SPF_H */
diff --git a/isisd/isis_spf_private.h b/isisd/isis_spf_private.h
new file mode 100644
index 0000000..7636730
--- /dev/null
+++ b/isisd/isis_spf_private.h
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_spf_private.h
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ * Copyright (C) 2017 Christian Franke <chris@opensourcerouting.org>
+ */
+#ifndef ISIS_SPF_PRIVATE_H
+#define ISIS_SPF_PRIVATE_H
+
+#include "hash.h"
+#include "jhash.h"
+#include "skiplist.h"
+#include "lib_errors.h"
+
+enum vertextype {
+ VTYPE_PSEUDO_IS = 1,
+ VTYPE_PSEUDO_TE_IS,
+ VTYPE_NONPSEUDO_IS,
+ VTYPE_NONPSEUDO_TE_IS,
+ VTYPE_ES,
+ VTYPE_IPREACH_INTERNAL,
+ VTYPE_IPREACH_EXTERNAL,
+ VTYPE_IPREACH_TE,
+ VTYPE_IP6REACH_INTERNAL,
+ VTYPE_IP6REACH_EXTERNAL
+};
+
+#define VTYPE_IS(t) ((t) >= VTYPE_PSEUDO_IS && (t) <= VTYPE_NONPSEUDO_TE_IS)
+#define VTYPE_ES(t) ((t) == VTYPE_ES)
+#define VTYPE_IP(t) ((t) >= VTYPE_IPREACH_INTERNAL && (t) <= VTYPE_IP6REACH_EXTERNAL)
+
+struct prefix_pair {
+ struct prefix dest;
+ struct prefix_ipv6 src;
+};
+
+struct isis_vertex_adj {
+ struct isis_spf_adj *sadj;
+ struct isis_sr_psid_info sr;
+ struct mpls_label_stack *label_stack;
+ uint32_t lfa_metric;
+};
+
+/*
+ * Triple <N, d(N), {Adj(N)}>
+ */
+struct isis_vertex {
+ enum vertextype type;
+ union {
+ uint8_t id[ISIS_SYS_ID_LEN + 1];
+ struct {
+ struct prefix_pair p;
+ struct isis_sr_psid_info sr;
+ enum spf_prefix_priority priority;
+ } ip;
+ } N;
+ uint32_t d_N; /* d(N) Distance from this IS */
+ uint16_t depth; /* The depth in the imaginary tree */
+ struct list *Adj_N; /* {Adj(N)} next hop or neighbor list */
+ struct list *parents; /* list of parents for ECMP */
+ struct hash *firsthops; /* first two hops to neighbor */
+ uint64_t insert_counter;
+ uint8_t flags;
+};
+#define F_ISIS_VERTEX_LFA_PROTECTED 0x01
+
+/* Vertex Queue and associated functions */
+
+struct isis_vertex_queue {
+ union {
+ struct skiplist *slist;
+ struct list *list;
+ } l;
+ struct hash *hash;
+ uint64_t insert_counter;
+};
+
+__attribute__((__unused__))
+static unsigned isis_vertex_queue_hash_key(const void *vp)
+{
+ const struct isis_vertex *vertex = vp;
+
+ if (VTYPE_IP(vertex->type)) {
+ uint32_t key;
+
+ key = prefix_hash_key(&vertex->N.ip.p.dest);
+ key = jhash_1word(prefix_hash_key(&vertex->N.ip.p.src), key);
+ return key;
+ }
+
+ return jhash(vertex->N.id, ISIS_SYS_ID_LEN + 1, 0x55aa5a5a);
+}
+
+__attribute__((__unused__))
+static bool isis_vertex_queue_hash_cmp(const void *a, const void *b)
+{
+ const struct isis_vertex *va = a, *vb = b;
+
+ if (va->type != vb->type)
+ return false;
+
+ if (VTYPE_IP(va->type)) {
+ if (prefix_cmp(&va->N.ip.p.dest, &vb->N.ip.p.dest))
+ return false;
+
+ return prefix_cmp((const struct prefix *)&va->N.ip.p.src,
+ (const struct prefix *)&vb->N.ip.p.src)
+ == 0;
+ }
+
+ return memcmp(va->N.id, vb->N.id, ISIS_SYS_ID_LEN + 1) == 0;
+}
+
+/*
+ * Compares vertizes for sorting in the TENT list. Returns true
+ * if candidate should be considered before current, false otherwise.
+ */
+__attribute__((__unused__)) static int isis_vertex_queue_tent_cmp(const void *a,
+ const void *b)
+{
+ const struct isis_vertex *va = a;
+ const struct isis_vertex *vb = b;
+
+ if (va->d_N < vb->d_N)
+ return -1;
+
+ if (va->d_N > vb->d_N)
+ return 1;
+
+ if (va->type < vb->type)
+ return -1;
+
+ if (va->type > vb->type)
+ return 1;
+
+ if (va->insert_counter < vb->insert_counter)
+ return -1;
+
+ if (va->insert_counter > vb->insert_counter)
+ return 1;
+
+ return 0;
+}
+
+__attribute__((__unused__))
+static struct skiplist *isis_vertex_queue_skiplist(void)
+{
+ return skiplist_new(0, isis_vertex_queue_tent_cmp, NULL);
+}
+
+__attribute__((__unused__))
+static void isis_vertex_queue_init(struct isis_vertex_queue *queue,
+ const char *name, bool ordered)
+{
+ if (ordered) {
+ queue->insert_counter = 1;
+ queue->l.slist = isis_vertex_queue_skiplist();
+ } else {
+ queue->insert_counter = 0;
+ queue->l.list = list_new();
+ }
+ queue->hash = hash_create(isis_vertex_queue_hash_key,
+ isis_vertex_queue_hash_cmp, name);
+}
+
+void isis_vertex_del(struct isis_vertex *vertex);
+
+bool isis_vertex_adj_exists(const struct isis_spftree *spftree,
+ const struct isis_vertex *vertex,
+ const struct isis_spf_adj *sadj);
+void isis_vertex_adj_free(void *arg);
+struct isis_vertex_adj *
+isis_vertex_adj_add(struct isis_spftree *spftree, struct isis_vertex *vertex,
+ struct list *vadj_list, struct isis_spf_adj *sadj,
+ struct isis_prefix_sid *psid, bool last_hop);
+
+__attribute__((__unused__))
+static void isis_vertex_queue_clear(struct isis_vertex_queue *queue)
+{
+ hash_clean(queue->hash, NULL);
+
+ if (queue->insert_counter) {
+ struct isis_vertex *vertex;
+ while (0 == skiplist_first(queue->l.slist, NULL,
+ (void **)&vertex)) {
+ isis_vertex_del(vertex);
+ skiplist_delete_first(queue->l.slist);
+ }
+ queue->insert_counter = 1;
+ } else {
+ queue->l.list->del = (void (*)(void *))isis_vertex_del;
+ list_delete_all_node(queue->l.list);
+ queue->l.list->del = NULL;
+ }
+}
+
+__attribute__((__unused__))
+static void isis_vertex_queue_free(struct isis_vertex_queue *queue)
+{
+ isis_vertex_queue_clear(queue);
+
+ hash_free(queue->hash);
+ queue->hash = NULL;
+
+ if (queue->insert_counter) {
+ skiplist_free(queue->l.slist);
+ queue->l.slist = NULL;
+ } else
+ list_delete(&queue->l.list);
+}
+
+__attribute__((__unused__))
+static unsigned int isis_vertex_queue_count(struct isis_vertex_queue *queue)
+{
+ return hashcount(queue->hash);
+}
+
+__attribute__((__unused__))
+static void isis_vertex_queue_append(struct isis_vertex_queue *queue,
+ struct isis_vertex *vertex)
+{
+ assert(!queue->insert_counter);
+
+ listnode_add(queue->l.list, vertex);
+
+ struct isis_vertex *inserted;
+
+ inserted = hash_get(queue->hash, vertex, hash_alloc_intern);
+ assert(inserted == vertex);
+}
+
+__attribute__((__unused__))
+static struct isis_vertex *isis_vertex_queue_last(struct isis_vertex_queue *queue)
+{
+ struct listnode *tail;
+
+ assert(!queue->insert_counter);
+ tail = listtail(queue->l.list);
+ assert(tail);
+ return listgetdata(tail);
+}
+
+__attribute__((__unused__))
+static void isis_vertex_queue_insert(struct isis_vertex_queue *queue,
+ struct isis_vertex *vertex)
+{
+ assert(queue->insert_counter);
+ vertex->insert_counter = queue->insert_counter++;
+ assert(queue->insert_counter != (uint64_t)-1);
+
+ skiplist_insert(queue->l.slist, vertex, vertex);
+
+ struct isis_vertex *inserted;
+ inserted = hash_get(queue->hash, vertex, hash_alloc_intern);
+ assert(inserted == vertex);
+}
+
+__attribute__((__unused__))
+static struct isis_vertex *
+isis_vertex_queue_pop(struct isis_vertex_queue *queue)
+{
+ assert(queue->insert_counter);
+
+ struct isis_vertex *rv;
+
+ if (skiplist_first(queue->l.slist, NULL, (void **)&rv))
+ return NULL;
+
+ skiplist_delete_first(queue->l.slist);
+ hash_release(queue->hash, rv);
+
+ return rv;
+}
+
+__attribute__((__unused__))
+static void isis_vertex_queue_delete(struct isis_vertex_queue *queue,
+ struct isis_vertex *vertex)
+{
+ assert(queue->insert_counter);
+
+ skiplist_delete(queue->l.slist, vertex, vertex);
+ hash_release(queue->hash, vertex);
+}
+
+#define ALL_QUEUE_ELEMENTS_RO(queue, node, data) \
+ ALL_LIST_ELEMENTS_RO((queue)->l.list, node, data)
+
+/* End of vertex queue definitions */
+
+struct isis_spftree {
+ struct isis_vertex_queue paths; /* the SPT */
+ struct isis_vertex_queue tents; /* TENT */
+ struct route_table *route_table;
+ struct route_table *route_table_backup;
+ struct lspdb_head *lspdb; /* link-state db */
+ struct hash *prefix_sids; /* SR Prefix-SIDs. */
+ struct list *sadj_list;
+ struct isis_spf_nodes adj_nodes;
+ struct isis_area *area; /* back pointer to area */
+ unsigned int runcount; /* number of runs since uptime */
+ time_t last_run_timestamp; /* last run timestamp as wall time for display */
+ time_t last_run_monotime; /* last run as monotime for scheduling */
+ time_t last_run_duration; /* last run duration in msec */
+
+ enum spf_type type;
+ uint8_t sysid[ISIS_SYS_ID_LEN];
+ uint16_t mtid;
+ int family;
+ int level;
+ enum spf_tree_id tree_id;
+ struct {
+ /* Original pre-failure local SPTs. */
+ struct {
+ struct isis_spftree *spftree;
+ struct isis_spftree *spftree_reverse;
+ } old;
+
+ /* Protected resource. */
+ struct lfa_protected_resource protected_resource;
+
+ /* P-space and Q-space. */
+ struct isis_spf_nodes p_space;
+ struct isis_spf_nodes q_space;
+
+ /* Remote LFA related information. */
+ struct {
+ /* List of RLFAs eligible to be installed. */
+ struct rlfa_tree_head rlfas;
+
+ /*
+ * RLFA post-convergence SPTs (needed to activate RLFAs
+ * once label information is received from LDP).
+ */
+ struct list *pc_spftrees;
+
+ /* RLFA maximum metric (or zero if absent). */
+ uint32_t max_metric;
+ } remote;
+
+ /* Protection counters. */
+ struct {
+ uint32_t lfa[SPF_PREFIX_PRIO_MAX];
+ uint32_t rlfa[SPF_PREFIX_PRIO_MAX];
+ uint32_t tilfa[SPF_PREFIX_PRIO_MAX];
+ uint32_t ecmp[SPF_PREFIX_PRIO_MAX];
+ uint32_t total[SPF_PREFIX_PRIO_MAX];
+ } protection_counters;
+ } lfa;
+ uint8_t algorithm;
+ uint8_t flags;
+};
+#define F_SPFTREE_HOPCOUNT_METRIC 0x01
+#define F_SPFTREE_NO_ROUTES 0x02
+#define F_SPFTREE_NO_ADJACENCIES 0x04
+#ifndef FABRICD
+/* flex-algo */
+#define F_SPFTREE_DISABLED 0x08
+#endif /* ifndef FABRICD */
+
+__attribute__((__unused__))
+static void isis_vertex_id_init(struct isis_vertex *vertex, const void *id,
+ enum vertextype vtype)
+{
+ vertex->type = vtype;
+
+ if (VTYPE_IS(vtype) || VTYPE_ES(vtype)) {
+ memcpy(vertex->N.id, id, ISIS_SYS_ID_LEN + 1);
+ } else if (VTYPE_IP(vtype)) {
+ memcpy(&vertex->N.ip.p, id, sizeof(vertex->N.ip.p));
+ } else {
+ flog_err(EC_LIB_DEVELOPMENT, "Unknown Vertex Type");
+ }
+}
+
+__attribute__((__unused__))
+static struct isis_vertex *isis_find_vertex(struct isis_vertex_queue *queue,
+ const void *id,
+ enum vertextype vtype)
+{
+ struct isis_vertex querier;
+
+ isis_vertex_id_init(&querier, id, vtype);
+ return hash_lookup(queue->hash, &querier);
+}
+
+__attribute__((__unused__))
+static struct isis_lsp *lsp_for_vertex(struct isis_spftree *spftree,
+ struct isis_vertex *vertex)
+{
+ uint8_t lsp_id[ISIS_SYS_ID_LEN + 2];
+
+ assert(VTYPE_IS(vertex->type));
+
+ memcpy(lsp_id, vertex->N.id, ISIS_SYS_ID_LEN + 1);
+ LSP_FRAGMENT(lsp_id) = 0;
+
+ struct isis_lsp *lsp = lsp_search(spftree->lspdb, lsp_id);
+
+ if (lsp && lsp->hdr.rem_lifetime != 0)
+ return lsp;
+
+ return NULL;
+}
+
+#define VID2STR_BUFFER SRCDEST2STR_BUFFER
+const char *vtype2string(enum vertextype vtype);
+const char *vid2string(const struct isis_vertex *vertex, char *buff, int size);
+
+#endif
diff --git a/isisd/isis_sr.c b/isisd/isis_sr.c
new file mode 100644
index 0000000..76cde6d
--- /dev/null
+++ b/isisd/isis_sr.c
@@ -0,0 +1,1322 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This is an implementation of Segment Routing for IS-IS as per RFC 8667
+ *
+ * Copyright (C) 2019 Orange http://www.orange.com
+ *
+ * Author: Olivier Dugeon <olivier.dugeon@orange.com>
+ * Contributor: Renato Westphal <renato@opensourcerouting.org> for NetDEF
+ */
+
+#include <zebra.h>
+
+#include "if.h"
+#include "linklist.h"
+#include "log.h"
+#include "command.h"
+#include "termtable.h"
+#include "memory.h"
+#include "prefix.h"
+#include "table.h"
+#include "srcdest_table.h"
+#include "vty.h"
+#include "zclient.h"
+#include "lib/lib_errors.h"
+
+#include "isisd/isisd.h"
+#include "isisd/isis_spf.h"
+#include "isisd/isis_spf_private.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_route.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_sr.h"
+#include "isisd/isis_tlvs.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_zebra.h"
+#include "isisd/isis_errors.h"
+
+/* Local variables and functions */
+DEFINE_MTYPE_STATIC(ISISD, ISIS_SR_INFO, "ISIS segment routing information");
+
+static void sr_local_block_delete(struct isis_area *area);
+static int sr_local_block_init(struct isis_area *area);
+static void sr_adj_sid_update(struct sr_adjacency *sra,
+ struct sr_local_block *srlb);
+static void sr_adj_sid_del(struct sr_adjacency *sra);
+
+/* --- RB-Tree Management functions ----------------------------------------- */
+
+/**
+ * Configured SR Prefix comparison for RB-Tree.
+ *
+ * @param a First SR prefix
+ * @param b Second SR prefix
+ *
+ * @return -1 (a < b), 0 (a == b) or +1 (a > b)
+ */
+static inline int sr_prefix_sid_cfg_compare(const struct sr_prefix_cfg *a,
+ const struct sr_prefix_cfg *b)
+{
+ int ret;
+
+ ret = prefix_cmp(&a->prefix, &b->prefix);
+ if (ret != 0)
+ return ret;
+
+ ret = a->algorithm - b->algorithm;
+ if (ret != 0)
+ return ret;
+
+ return 0;
+}
+DECLARE_RBTREE_UNIQ(srdb_prefix_cfg, struct sr_prefix_cfg, entry,
+ sr_prefix_sid_cfg_compare);
+
+/**
+ * Find SRGB associated to a System ID.
+ *
+ * @param area IS-IS LSP database
+ * @param sysid System ID to lookup
+ *
+ * @return Pointer to SRGB if found, NULL otherwise
+ */
+struct isis_sr_block *isis_sr_find_srgb(struct lspdb_head *lspdb,
+ const uint8_t *sysid)
+{
+ struct isis_lsp *lsp;
+
+ lsp = isis_root_system_lsp(lspdb, sysid);
+ if (!lsp)
+ return NULL;
+
+ if (!lsp->tlvs->router_cap
+ || lsp->tlvs->router_cap->srgb.range_size == 0)
+ return NULL;
+
+ return &lsp->tlvs->router_cap->srgb;
+}
+
+/**
+ * Compute input label for the given Prefix-SID.
+ *
+ * @param area IS-IS area
+ * @param psid IS-IS Prefix-SID Sub-TLV
+ * @param local Indicates whether the Prefix-SID is local or not
+ *
+ * @return MPLS label or MPLS_INVALID_LABEL in case of SRGB overflow
+ */
+mpls_label_t sr_prefix_in_label(struct isis_area *area,
+ struct isis_prefix_sid *psid, bool local)
+{
+ /*
+ * No need to assign a label for local Prefix-SIDs unless the no-PHP
+ * flag is set.
+ */
+ if (local
+ && (!CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP)
+ || CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL)))
+ return MPLS_INVALID_LABEL;
+
+ /* Return SID value as MPLS label if it is an Absolute SID */
+ if (CHECK_FLAG(psid->flags,
+ ISIS_PREFIX_SID_VALUE | ISIS_PREFIX_SID_LOCAL))
+ return psid->value;
+
+ /* Check that SID index falls inside the SRGB */
+ if (psid->value >= (area->srdb.config.srgb_upper_bound
+ - area->srdb.config.srgb_lower_bound + 1)) {
+ flog_warn(EC_ISIS_SID_OVERFLOW,
+ "%s: SID index %u falls outside local SRGB range",
+ __func__, psid->value);
+ return MPLS_INVALID_LABEL;
+ }
+
+ /* Return MPLS label as SID index + SRGB_lower_bound as per RFC 8667 */
+ return (area->srdb.config.srgb_lower_bound + psid->value);
+}
+
+/**
+ * Compute output label for the given Prefix-SID.
+ *
+ * @param lspdb IS-IS LSP database
+ * @param family Prefix-SID address family
+ * @param psid Prefix-SID Sub-TLV
+ * @param nh_sysid System ID of the nexthop node
+ * @param last_hop Indicates whether the nexthop node is the last hop
+ *
+ * @return MPLS label or MPLS_INVALID_LABEL in case of error
+ */
+mpls_label_t sr_prefix_out_label(struct lspdb_head *lspdb, int family,
+ struct isis_prefix_sid *psid,
+ const uint8_t *nh_sysid, bool last_hop)
+{
+ struct isis_sr_block *nh_srgb;
+
+ if (last_hop) {
+ if (!CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP))
+ return MPLS_LABEL_IMPLICIT_NULL;
+
+ if (CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL)) {
+ if (family == AF_INET)
+ return MPLS_LABEL_IPV4_EXPLICIT_NULL;
+ else
+ return MPLS_LABEL_IPV6_EXPLICIT_NULL;
+ }
+ /* Fallthrough */
+ }
+
+ /* Return SID value as MPLS label if it is an Absolute SID */
+ if (CHECK_FLAG(psid->flags,
+ ISIS_PREFIX_SID_VALUE | ISIS_PREFIX_SID_LOCAL)) {
+ /*
+ * V/L SIDs have local significance, so only adjacent routers
+ * can use them (RFC8667 section #2.1.1.1)
+ */
+ if (!last_hop)
+ return MPLS_INVALID_LABEL;
+ return psid->value;
+ }
+
+ /* Check that SID index falls inside the SRGB */
+ nh_srgb = isis_sr_find_srgb(lspdb, nh_sysid);
+ if (!nh_srgb)
+ return MPLS_INVALID_LABEL;
+
+ /*
+ * Check if the nexthop can handle SR-MPLS encapsulated IPv4 or
+ * IPv6 packets.
+ */
+ if ((family == AF_INET && !IS_SR_IPV4(nh_srgb))
+ || (family == AF_INET6 && !IS_SR_IPV6(nh_srgb)))
+ return MPLS_INVALID_LABEL;
+
+ if (psid->value >= nh_srgb->range_size) {
+ flog_warn(EC_ISIS_SID_OVERFLOW,
+ "%s: SID index %u falls outside remote SRGB range",
+ __func__, psid->value);
+ return MPLS_INVALID_LABEL;
+ }
+
+ /* Return MPLS label as SID index + SRGB_lower_bound as per RFC 8667 */
+ return (nh_srgb->lower_bound + psid->value);
+}
+
+/* --- Functions used for Yang model and CLI to configure Segment Routing --- */
+
+/**
+ * Check if prefix correspond to a Node SID.
+ *
+ * @param ifp Interface
+ * @param prefix Prefix to be checked
+ *
+ * @return True if the interface/address pair corresponds to a Node-SID
+ */
+static bool sr_prefix_is_node_sid(const struct interface *ifp,
+ const struct prefix *prefix)
+{
+ return (if_is_loopback(ifp) && is_host_route(prefix));
+}
+
+/**
+ * Update local SRGB configuration. SRGB is reserved though Label Manager.
+ * This function trigger the update of local Prefix-SID installation.
+ *
+ * @param area IS-IS area
+ * @param lower_bound Lower bound of SRGB
+ * @param upper_bound Upper bound of SRGB
+ *
+ * @return 0 on success, -1 otherwise
+ */
+int isis_sr_cfg_srgb_update(struct isis_area *area, uint32_t lower_bound,
+ uint32_t upper_bound)
+{
+ struct isis_sr_db *srdb = &area->srdb;
+
+ sr_debug("ISIS-Sr (%s): Update SRGB with new range [%u/%u]",
+ area->area_tag, lower_bound, upper_bound);
+
+ /* Just store new SRGB values if Label Manager is not available.
+ * SRGB will be configured later when SR start */
+ if (!isis_zebra_label_manager_ready()) {
+ srdb->config.srgb_lower_bound = lower_bound;
+ srdb->config.srgb_upper_bound = upper_bound;
+ return 0;
+ }
+
+ /* Label Manager is ready, start by releasing the old SRGB. */
+ if (srdb->srgb_active) {
+ isis_zebra_release_label_range(srdb->config.srgb_lower_bound,
+ srdb->config.srgb_upper_bound);
+ srdb->srgb_active = false;
+ }
+
+ srdb->config.srgb_lower_bound = lower_bound;
+ srdb->config.srgb_upper_bound = upper_bound;
+
+ if (srdb->enabled) {
+ /* then request new SRGB if SR is enabled. */
+ if (isis_zebra_request_label_range(
+ srdb->config.srgb_lower_bound,
+ srdb->config.srgb_upper_bound
+ - srdb->config.srgb_lower_bound + 1) < 0) {
+ srdb->srgb_active = false;
+ return -1;
+ } else
+ srdb->srgb_active = true;
+
+
+ sr_debug(" |- Got new SRGB [%u/%u]",
+ srdb->config.srgb_lower_bound,
+ srdb->config.srgb_upper_bound);
+
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ } else if (srdb->config.enabled) {
+ /* Try to enable SR again using the new SRGB. */
+ isis_sr_start(area);
+ }
+
+ return 0;
+}
+
+/**
+ * Update Segment Routing Local Block range which is reserved though the
+ * Label Manager. This function trigger the update of local Adjacency-SID
+ * installation.
+ *
+ * @param area IS-IS area
+ * @param lower_bound Lower bound of SRLB
+ * @param upper_bound Upper bound of SRLB
+ *
+ * @return 0 on success, -1 otherwise
+ */
+int isis_sr_cfg_srlb_update(struct isis_area *area, uint32_t lower_bound,
+ uint32_t upper_bound)
+{
+ struct isis_sr_db *srdb = &area->srdb;
+ struct listnode *node;
+ struct sr_adjacency *sra;
+
+ sr_debug("ISIS-Sr (%s): Update SRLB with new range [%u/%u]",
+ area->area_tag, lower_bound, upper_bound);
+
+ /* Just store new SRLB values if Label Manager is not available.
+ * SRLB will be configured later when SR start */
+ if (!isis_zebra_label_manager_ready()) {
+ srdb->config.srlb_lower_bound = lower_bound;
+ srdb->config.srlb_upper_bound = upper_bound;
+ return 0;
+ }
+
+ /* LM is ready, start by deleting the old SRLB */
+ sr_local_block_delete(area);
+
+ srdb->config.srlb_lower_bound = lower_bound;
+ srdb->config.srlb_upper_bound = upper_bound;
+
+ if (srdb->enabled) {
+ /* Initialize new SRLB */
+ if (sr_local_block_init(area) != 0)
+ return -1;
+
+ /* Reinstall local Adjacency-SIDs with new labels. */
+ for (ALL_LIST_ELEMENTS_RO(area->srdb.adj_sids, node, sra))
+ sr_adj_sid_update(sra, &srdb->srlb);
+
+ /* Update and Flood LSP */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ } else if (srdb->config.enabled) {
+ /* Try to enable SR again using the new SRLB. */
+ isis_sr_start(area);
+ }
+
+ return 0;
+}
+
+/**
+ * Add new Prefix-SID configuration to the SRDB.
+ *
+ * @param area IS-IS area
+ * @param prefix Prefix to be added
+ *
+ * @return Newly added Prefix-SID configuration structure
+ */
+struct sr_prefix_cfg *isis_sr_cfg_prefix_add(struct isis_area *area,
+ const struct prefix *prefix,
+ uint8_t algorithm)
+{
+ struct sr_prefix_cfg *pcfg;
+ struct interface *ifp;
+
+ sr_debug("ISIS-Sr (%s): Add local prefix %pFX", area->area_tag, prefix);
+
+ pcfg = XCALLOC(MTYPE_ISIS_SR_INFO, sizeof(*pcfg));
+ pcfg->prefix = *prefix;
+ pcfg->area = area;
+ pcfg->algorithm = algorithm;
+
+ /* Pull defaults from the YANG module. */
+ pcfg->sid_type = yang_get_default_enum(
+ "%s/prefix-sid-map/prefix-sid/sid-value-type", ISIS_SR);
+ pcfg->last_hop_behavior = yang_get_default_enum(
+ "%s/prefix-sid-map/prefix-sid/last-hop-behavior", ISIS_SR);
+
+ /* Mark as node Sid if the prefix is host and configured in loopback */
+ ifp = if_lookup_prefix(prefix, VRF_DEFAULT);
+ if (ifp && sr_prefix_is_node_sid(ifp, prefix))
+ pcfg->node_sid = true;
+
+ /* Save prefix-sid configuration. */
+ srdb_prefix_cfg_add(&area->srdb.config.prefix_sids, pcfg);
+
+ return pcfg;
+}
+
+/**
+ * Removal of locally configured Prefix-SID.
+ *
+ * @param pcfg Configured Prefix-SID
+ */
+void isis_sr_cfg_prefix_del(struct sr_prefix_cfg *pcfg)
+{
+ struct isis_area *area = pcfg->area;
+
+ sr_debug("ISIS-Sr (%s): Delete local Prefix-SID %pFX %s %u",
+ area->area_tag, &pcfg->prefix,
+ pcfg->sid_type == SR_SID_VALUE_TYPE_INDEX ? "index" : "label",
+ pcfg->sid);
+
+ srdb_prefix_cfg_del(&area->srdb.config.prefix_sids, pcfg);
+ XFREE(MTYPE_ISIS_SR_INFO, pcfg);
+}
+
+/**
+ * Lookup for Prefix-SID in the local configuration.
+ *
+ * @param area IS-IS area
+ * @param prefix Prefix to lookup
+ *
+ * @return Configured Prefix-SID structure if found, NULL otherwise
+ */
+struct sr_prefix_cfg *isis_sr_cfg_prefix_find(struct isis_area *area,
+ union prefixconstptr prefix,
+ uint8_t algorithm)
+{
+ struct sr_prefix_cfg pcfg = {};
+
+ prefix_copy(&pcfg.prefix, prefix.p);
+ pcfg.algorithm = algorithm;
+ return srdb_prefix_cfg_find(&area->srdb.config.prefix_sids, &pcfg);
+}
+
+/**
+ * Fill in Prefix-SID Sub-TLV according to the corresponding configuration.
+ *
+ * @param pcfg Prefix-SID configuration
+ * @param external False if prefix is locally configured, true otherwise
+ * @param psid Prefix-SID sub-TLV to be updated
+ */
+void isis_sr_prefix_cfg2subtlv(const struct sr_prefix_cfg *pcfg, bool external,
+ struct isis_prefix_sid *psid)
+{
+ /* Set SID algorithm. */
+ psid->algorithm = pcfg->algorithm;
+
+ /* Set SID flags. */
+ psid->flags = 0;
+ switch (pcfg->last_hop_behavior) {
+ case SR_LAST_HOP_BEHAVIOR_EXP_NULL:
+ SET_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP);
+ SET_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL);
+ break;
+ case SR_LAST_HOP_BEHAVIOR_NO_PHP:
+ SET_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP);
+ UNSET_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL);
+ break;
+ case SR_LAST_HOP_BEHAVIOR_PHP:
+ UNSET_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP);
+ UNSET_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL);
+ break;
+ }
+ if (external)
+ SET_FLAG(psid->flags, ISIS_PREFIX_SID_READVERTISED);
+ if (pcfg->node_sid && !pcfg->n_flag_clear)
+ SET_FLAG(psid->flags, ISIS_PREFIX_SID_NODE);
+
+ /* Set SID value. */
+ psid->value = pcfg->sid;
+ if (pcfg->sid_type == SR_SID_VALUE_TYPE_ABSOLUTE) {
+ SET_FLAG(psid->flags, ISIS_PREFIX_SID_VALUE);
+ SET_FLAG(psid->flags, ISIS_PREFIX_SID_LOCAL);
+ }
+}
+
+/**
+ * Delete all backup Adj-SIDs.
+ *
+ * @param area IS-IS area
+ * @param level IS-IS level
+ */
+void isis_area_delete_backup_adj_sids(struct isis_area *area, int level)
+{
+ struct sr_adjacency *sra;
+ struct listnode *node, *nnode;
+
+ for (ALL_LIST_ELEMENTS(area->srdb.adj_sids, node, nnode, sra))
+ if (sra->type == ISIS_SR_LAN_BACKUP
+ && (sra->adj->level & level))
+ sr_adj_sid_del(sra);
+}
+
+/* --- Segment Routing Local Block management functions --------------------- */
+
+/**
+ * Initialize Segment Routing Local Block from SRDB configuration and reserve
+ * block of bits to manage label allocation.
+ *
+ * @param area IS-IS area
+ */
+static int sr_local_block_init(struct isis_area *area)
+{
+ struct isis_sr_db *srdb = &area->srdb;
+ struct sr_local_block *srlb = &srdb->srlb;
+
+ /* Check if SRLB is not already configured */
+ if (srlb->active)
+ return 0;
+
+ /*
+ * Request SRLB to the label manager. If the allocation fails, return
+ * an error to disable SR until a new SRLB is successfully allocated.
+ */
+ if (isis_zebra_request_label_range(
+ srdb->config.srlb_lower_bound,
+ srdb->config.srlb_upper_bound
+ - srdb->config.srlb_lower_bound + 1)) {
+ srlb->active = false;
+ return -1;
+ }
+
+ sr_debug("ISIS-Sr (%s): Got new SRLB [%u/%u]", area->area_tag,
+ srdb->config.srlb_lower_bound, srdb->config.srlb_upper_bound);
+
+ /* Initialize the SRLB */
+ srlb->start = srdb->config.srlb_lower_bound;
+ srlb->end = srdb->config.srlb_upper_bound;
+ srlb->current = 0;
+ /* Compute the needed Used Mark number and allocate them */
+ srlb->max_block = (srlb->end - srlb->start + 1) / SRLB_BLOCK_SIZE;
+ if (((srlb->end - srlb->start + 1) % SRLB_BLOCK_SIZE) != 0)
+ srlb->max_block++;
+ srlb->used_mark = XCALLOC(MTYPE_ISIS_SR_INFO,
+ srlb->max_block * SRLB_BLOCK_SIZE);
+ srlb->active = true;
+
+ return 0;
+}
+
+/**
+ * Remove Segment Routing Local Block.
+ *
+ * @param area IS-IS area
+ */
+static void sr_local_block_delete(struct isis_area *area)
+{
+ struct isis_sr_db *srdb = &area->srdb;
+ struct sr_local_block *srlb = &srdb->srlb;
+
+ /* Check if SRLB is not already delete */
+ if (!srlb->active)
+ return;
+
+ sr_debug("ISIS-Sr (%s): Remove SRLB [%u/%u]", area->area_tag,
+ srlb->start, srlb->end);
+
+ /* First release the label block */
+ isis_zebra_release_label_range(srdb->config.srlb_lower_bound,
+ srdb->config.srlb_upper_bound);
+
+ /* Then reset SRLB structure */
+ if (srlb->used_mark != NULL)
+ XFREE(MTYPE_ISIS_SR_INFO, srlb->used_mark);
+ srlb->active = false;
+}
+
+/**
+ * Request a label from the Segment Routing Local Block.
+ *
+ * @param srlb Segment Routing Local Block
+ *
+ * @return First available label on success or MPLS_INVALID_LABEL if the
+ * block of labels is full
+ */
+static mpls_label_t sr_local_block_request_label(struct sr_local_block *srlb)
+{
+ mpls_label_t label;
+ uint32_t index;
+ uint32_t pos;
+ uint32_t size = srlb->end - srlb->start + 1;
+
+ /* Check if we ran out of available labels */
+ if (srlb->current >= size)
+ return MPLS_INVALID_LABEL;
+
+ /* Get first available label and mark it used */
+ label = srlb->current + srlb->start;
+ index = srlb->current / SRLB_BLOCK_SIZE;
+ pos = 1ULL << (srlb->current % SRLB_BLOCK_SIZE);
+ srlb->used_mark[index] |= pos;
+
+ /* Jump to the next free position */
+ srlb->current++;
+ pos = srlb->current % SRLB_BLOCK_SIZE;
+ while (srlb->current < size) {
+ if (pos == 0)
+ index++;
+ if (!((1ULL << pos) & srlb->used_mark[index]))
+ break;
+ else {
+ srlb->current++;
+ pos = srlb->current % SRLB_BLOCK_SIZE;
+ }
+ }
+
+ if (srlb->current == size)
+ zlog_warn(
+ "SR: Warning, SRLB is depleted and next label request will fail");
+
+ return label;
+}
+
+/**
+ * Release label in the Segment Routing Local Block.
+ *
+ * @param srlb Segment Routing Local Block
+ * @param label Label to be release
+ *
+ * @return 0 on success or -1 if label falls outside SRLB
+ */
+static int sr_local_block_release_label(struct sr_local_block *srlb,
+ mpls_label_t label)
+{
+ uint32_t index;
+ uint32_t pos;
+
+ /* Check that label falls inside the SRLB */
+ if ((label < srlb->start) || (label > srlb->end)) {
+ flog_warn(EC_ISIS_SID_OVERFLOW,
+ "%s: Returning label %u is outside SRLB [%u/%u]",
+ __func__, label, srlb->start, srlb->end);
+ return -1;
+ }
+
+ index = (label - srlb->start) / SRLB_BLOCK_SIZE;
+ pos = 1ULL << ((label - srlb->start) % SRLB_BLOCK_SIZE);
+ srlb->used_mark[index] &= ~pos;
+ /* Reset current to the first available position */
+ for (index = 0; index < srlb->max_block; index++) {
+ if (srlb->used_mark[index] != 0xFFFFFFFFFFFFFFFF) {
+ for (pos = 0; pos < SRLB_BLOCK_SIZE; pos++)
+ if (!((1ULL << pos) & srlb->used_mark[index])) {
+ srlb->current =
+ index * SRLB_BLOCK_SIZE + pos;
+ break;
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/* --- Segment Routing Adjacency-SID management functions ------------------- */
+
+/**
+ * Add new local Adjacency-SID.
+ *
+ * @param adj IS-IS Adjacency
+ * @param family Inet Family (IPv4 or IPv6)
+ * @param backup True to initialize backup Adjacency SID
+ * @param nexthops List of backup nexthops (for backup Adj-SIDs only)
+ */
+void sr_adj_sid_add_single(struct isis_adjacency *adj, int family, bool backup,
+ struct list *nexthops)
+{
+ struct isis_circuit *circuit = adj->circuit;
+ struct isis_area *area = circuit->area;
+ struct sr_adjacency *sra;
+ struct isis_adj_sid *adj_sid;
+ struct isis_lan_adj_sid *ladj_sid;
+ union g_addr nexthop = {};
+ uint8_t flags;
+ mpls_label_t input_label;
+
+ sr_debug("ISIS-Sr (%s): Add %s Adjacency SID", area->area_tag,
+ backup ? "Backup" : "Primary");
+
+ /* Determine nexthop IP address */
+ switch (family) {
+ case AF_INET:
+ if (!circuit->ip_router || !adj->ipv4_address_count)
+ return;
+
+ nexthop.ipv4 = adj->ipv4_addresses[0];
+ break;
+ case AF_INET6:
+ if (!circuit->ipv6_router || !adj->ll_ipv6_count)
+ return;
+
+ nexthop.ipv6 = adj->ll_ipv6_addrs[0];
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unexpected address-family: %u", __func__, family);
+ exit(1);
+ }
+
+ /* Prepare Segment Routing Adjacency as per RFC8667 section #2.2 */
+ flags = EXT_SUBTLV_LINK_ADJ_SID_VFLG | EXT_SUBTLV_LINK_ADJ_SID_LFLG;
+ if (family == AF_INET6)
+ SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_FFLG);
+ if (backup)
+ SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_BFLG);
+
+ /* Get a label from the SRLB for this Adjacency */
+ input_label = sr_local_block_request_label(&area->srdb.srlb);
+ if (input_label == MPLS_INVALID_LABEL)
+ return;
+
+ if (circuit->ext == NULL)
+ circuit->ext = isis_alloc_ext_subtlvs();
+
+ sra = XCALLOC(MTYPE_ISIS_SR_INFO, sizeof(*sra));
+ sra->type = backup ? ISIS_SR_LAN_BACKUP : ISIS_SR_ADJ_NORMAL;
+ sra->input_label = input_label;
+ sra->nexthop.family = family;
+ sra->nexthop.address = nexthop;
+
+ if (backup && nexthops) {
+ struct isis_vertex_adj *vadj;
+ struct listnode *node;
+
+ sra->backup_nexthops = list_new();
+ for (ALL_LIST_ELEMENTS_RO(nexthops, node, vadj)) {
+ struct isis_adjacency *adj = vadj->sadj->adj;
+ struct mpls_label_stack *label_stack;
+
+ label_stack = vadj->label_stack;
+ adjinfo2nexthop(family, sra->backup_nexthops, adj, NULL,
+ label_stack);
+ }
+ }
+
+ switch (circuit->circ_type) {
+ /* LAN Adjacency-SID for Broadcast interface section #2.2.2 */
+ case CIRCUIT_T_BROADCAST:
+ ladj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*ladj_sid));
+ ladj_sid->family = family;
+ ladj_sid->flags = flags;
+ ladj_sid->weight = 0;
+ memcpy(ladj_sid->neighbor_id, adj->sysid,
+ sizeof(ladj_sid->neighbor_id));
+ ladj_sid->sid = input_label;
+ isis_tlvs_add_lan_adj_sid(circuit->ext, ladj_sid);
+ sra->u.ladj_sid = ladj_sid;
+ break;
+ /* Adjacency-SID for Point to Point interface section #2.2.1 */
+ case CIRCUIT_T_P2P:
+ adj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*adj_sid));
+ adj_sid->family = family;
+ adj_sid->flags = flags;
+ adj_sid->weight = 0;
+ adj_sid->sid = input_label;
+ isis_tlvs_add_adj_sid(circuit->ext, adj_sid);
+ sra->u.adj_sid = adj_sid;
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u",
+ __func__, circuit->circ_type);
+ exit(1);
+ }
+
+ /* Add Adjacency-SID in SRDB */
+ sra->adj = adj;
+ listnode_add(area->srdb.adj_sids, sra);
+ listnode_add(adj->adj_sids, sra);
+
+ isis_zebra_send_adjacency_sid(ZEBRA_MPLS_LABELS_ADD, sra);
+}
+
+/**
+ * Add Primary and Backup local Adjacency SID.
+ *
+ * @param adj IS-IS Adjacency
+ * @param family Inet Family (IPv4 or IPv6)
+ */
+static void sr_adj_sid_add(struct isis_adjacency *adj, int family)
+{
+ sr_adj_sid_add_single(adj, family, false, NULL);
+}
+
+static void sr_adj_sid_update(struct sr_adjacency *sra,
+ struct sr_local_block *srlb)
+{
+ struct isis_circuit *circuit = sra->adj->circuit;
+
+ /* First remove the old MPLS Label */
+ isis_zebra_send_adjacency_sid(ZEBRA_MPLS_LABELS_DELETE, sra);
+
+ /* Got new label in the new SRLB */
+ sra->input_label = sr_local_block_request_label(srlb);
+ if (sra->input_label == MPLS_INVALID_LABEL)
+ return;
+
+ switch (circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ sra->u.ladj_sid->sid = sra->input_label;
+ break;
+ case CIRCUIT_T_P2P:
+ sra->u.adj_sid->sid = sra->input_label;
+ break;
+ default:
+ flog_warn(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u",
+ __func__, circuit->circ_type);
+ break;
+ }
+
+ /* Finally configure the new MPLS Label */
+ isis_zebra_send_adjacency_sid(ZEBRA_MPLS_LABELS_ADD, sra);
+}
+
+/**
+ * Delete local Adj-SID.
+ *
+ * @param sra Segment Routing Adjacency
+ */
+static void sr_adj_sid_del(struct sr_adjacency *sra)
+{
+ struct isis_circuit *circuit = sra->adj->circuit;
+ struct isis_area *area = circuit->area;
+
+ sr_debug("ISIS-Sr (%s): Delete Adjacency SID", area->area_tag);
+
+ isis_zebra_send_adjacency_sid(ZEBRA_MPLS_LABELS_DELETE, sra);
+
+ /* Release dynamic label and remove subTLVs */
+ switch (circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ sr_local_block_release_label(&area->srdb.srlb,
+ sra->u.ladj_sid->sid);
+ isis_tlvs_del_lan_adj_sid(circuit->ext, sra->u.ladj_sid);
+ break;
+ case CIRCUIT_T_P2P:
+ sr_local_block_release_label(&area->srdb.srlb,
+ sra->u.adj_sid->sid);
+ isis_tlvs_del_adj_sid(circuit->ext, sra->u.adj_sid);
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u",
+ __func__, circuit->circ_type);
+ exit(1);
+ }
+
+ if (sra->type == ISIS_SR_LAN_BACKUP && sra->backup_nexthops) {
+ sra->backup_nexthops->del =
+ (void (*)(void *))isis_nexthop_delete;
+ list_delete(&sra->backup_nexthops);
+ }
+
+ /* Remove Adjacency-SID from the SRDB */
+ listnode_delete(area->srdb.adj_sids, sra);
+ listnode_delete(sra->adj->adj_sids, sra);
+ XFREE(MTYPE_ISIS_SR_INFO, sra);
+}
+
+/**
+ * Lookup Segment Routing Adj-SID by family and type.
+ *
+ * @param adj IS-IS Adjacency
+ * @param family Inet Family (IPv4 or IPv6)
+ * @param type Adjacency SID type
+ */
+struct sr_adjacency *isis_sr_adj_sid_find(struct isis_adjacency *adj,
+ int family, enum sr_adj_type type)
+{
+ struct sr_adjacency *sra;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(adj->adj_sids, node, sra))
+ if (sra->nexthop.family == family && sra->type == type)
+ return sra;
+
+ return NULL;
+}
+
+/**
+ * Remove all Adjacency-SIDs associated to an adjacency that is going down.
+ *
+ * @param adj IS-IS Adjacency
+ *
+ * @return 0
+ */
+static int sr_adj_state_change(struct isis_adjacency *adj)
+{
+ struct sr_adjacency *sra;
+ struct listnode *node, *nnode;
+
+ if (!adj->circuit->area->srdb.enabled)
+ return 0;
+
+ if (adj->adj_state == ISIS_ADJ_UP)
+ return 0;
+
+ for (ALL_LIST_ELEMENTS(adj->adj_sids, node, nnode, sra))
+ sr_adj_sid_del(sra);
+
+ return 0;
+}
+
+/**
+ * When IS-IS Adjacency got one or more IPv4/IPv6 addresses, add new IPv4 or
+ * IPv6 address to corresponding Adjacency-SID accordingly.
+ *
+ * @param adj IS-IS Adjacency
+ * @param family Inet Family (IPv4 or IPv6)
+ * @param global Indicate if it concerns the Local or Global IPv6 addresses
+ *
+ * @return 0
+ */
+static int sr_adj_ip_enabled(struct isis_adjacency *adj, int family,
+ bool global)
+{
+ if (!adj->circuit->area->srdb.enabled || global)
+ return 0;
+
+ sr_adj_sid_add(adj, family);
+
+ return 0;
+}
+
+/**
+ * When IS-IS Adjacency doesn't have any IPv4 or IPv6 addresses anymore,
+ * delete the corresponding Adjacency-SID(s) accordingly.
+ *
+ * @param adj IS-IS Adjacency
+ * @param family Inet Family (IPv4 or IPv6)
+ * @param global Indicate if it concerns the Local or Global IPv6 addresses
+ *
+ * @return 0
+ */
+static int sr_adj_ip_disabled(struct isis_adjacency *adj, int family,
+ bool global)
+{
+ struct sr_adjacency *sra;
+ struct listnode *node, *nnode;
+
+ if (!adj->circuit->area->srdb.enabled || global)
+ return 0;
+
+ for (ALL_LIST_ELEMENTS(adj->adj_sids, node, nnode, sra))
+ if (sra->nexthop.family == family)
+ sr_adj_sid_del(sra);
+
+ return 0;
+}
+
+/**
+ * Update the Node-SID flag of the configured Prefix-SID mappings in response
+ * to an address addition or removal event.
+ *
+ * @param ifp Interface
+ *
+ * @return 0
+ */
+int sr_if_addr_update(struct interface *ifp)
+{
+ struct sr_prefix_cfg *pcfgs[SR_ALGORITHM_COUNT] = {NULL};
+ struct isis_circuit *circuit;
+ struct isis_area *area;
+ struct connected *connected;
+ struct listnode *node;
+ bool need_lsp_regenerate = false;
+
+ /* Get corresponding circuit */
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit)
+ return 0;
+
+ area = circuit->area;
+ if (!area)
+ return 0;
+
+ FOR_ALL_INTERFACES_ADDRESSES (ifp, connected, node) {
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ pcfgs[i] = isis_sr_cfg_prefix_find(
+ area, connected->address, i);
+
+ if (!pcfgs[i])
+ continue;
+
+ if (sr_prefix_is_node_sid(ifp, &pcfgs[i]->prefix)) {
+ pcfgs[i]->node_sid = true;
+ need_lsp_regenerate = true;
+ }
+ }
+ }
+
+ if (need_lsp_regenerate)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return 0;
+}
+
+/**
+ * Show LFIB operation in human readable format.
+ *
+ * @param buf Buffer to store string output. Must be pre-allocate
+ * @param size Size of the buffer
+ * @param label_in Input Label
+ * @param label_out Output Label
+ *
+ * @return String containing LFIB operation in human readable format
+ */
+char *sr_op2str(char *buf, size_t size, mpls_label_t label_in,
+ mpls_label_t label_out)
+{
+ if (size < 24)
+ return NULL;
+
+ if (label_in == MPLS_INVALID_LABEL) {
+ snprintf(buf, size, "no-op.");
+ return buf;
+ }
+
+ switch (label_out) {
+ case MPLS_LABEL_IMPLICIT_NULL:
+ snprintf(buf, size, "Pop(%u)", label_in);
+ break;
+ case MPLS_LABEL_IPV4_EXPLICIT_NULL:
+ case MPLS_LABEL_IPV6_EXPLICIT_NULL:
+ snprintf(buf, size, "Swap(%u, null)", label_in);
+ break;
+ case MPLS_INVALID_LABEL:
+ snprintf(buf, size, "no-op.");
+ break;
+ default:
+ snprintf(buf, size, "Swap(%u, %u)", label_in, label_out);
+ break;
+ }
+ return buf;
+}
+
+/**
+ * Show Segment Routing Node.
+ *
+ * @param vty VTY output
+ * @param area IS-IS area
+ * @param level IS-IS level
+ */
+static void show_node(struct vty *vty, struct isis_area *area, int level,
+ uint8_t algo)
+{
+ struct isis_lsp *lsp;
+ struct ttable *tt;
+ char buf[128];
+
+ vty_out(vty, " IS-IS %s SR-Nodes:\n\n", circuit_t2string(level));
+
+ /* Prepare table. */
+ tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+ ttable_add_row(tt, "System ID|SRGB|SRLB|Algorithm|MSD");
+ tt->style.cell.rpad = 2;
+ tt->style.corner = '+';
+ ttable_restyle(tt);
+ ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+ frr_each (lspdb, &area->lspdb[level - 1], lsp) {
+ struct isis_router_cap *cap;
+
+ if (!lsp->tlvs)
+ continue;
+ cap = lsp->tlvs->router_cap;
+ if (!cap)
+ continue;
+ if (cap->algo[algo] == SR_ALGORITHM_UNSET)
+ continue;
+
+ if (cap->algo[algo] == SR_ALGORITHM_SPF)
+ snprintf(buf, sizeof(buf), "SPF");
+ else if (cap->algo[algo] == SR_ALGORITHM_STRICT_SPF)
+ snprintf(buf, sizeof(buf), "S-SPF");
+#ifndef FABRICD
+ else
+ snprintf(buf, sizeof(buf), "Flex-Algo %d", algo);
+#endif /* ifndef FABRICD */
+
+ ttable_add_row(tt, "%pSY|%u - %u|%u - %u|%s|%u",
+ lsp->hdr.lsp_id, cap->srgb.lower_bound,
+ cap->srgb.lower_bound + cap->srgb.range_size - 1,
+ cap->srlb.lower_bound,
+ cap->srlb.lower_bound + cap->srlb.range_size - 1,
+ buf, cap->msd);
+ }
+
+ /* Dump the generated table. */
+ if (tt->nrows > 1) {
+ char *table;
+
+ table = ttable_dump(tt, "\n");
+ vty_out(vty, "%s\n", table);
+ XFREE(MTYPE_TMP, table);
+ }
+ ttable_del(tt);
+}
+
+DEFUN(show_sr_node, show_sr_node_cmd,
+ "show " PROTO_NAME
+ " segment-routing node"
+#ifndef FABRICD
+ " [algorithm (128-255)]"
+#endif /* ifndef FABRICD */
+ ,
+ SHOW_STR PROTO_HELP
+ "Segment-Routing\n"
+ "Segment-Routing node\n"
+#ifndef FABRICD
+ "Show Flex-algo nodes\n"
+ "Algorithm number\n"
+#endif /* ifndef FABRICD */
+)
+{
+ struct listnode *node, *inode;
+ struct isis_area *area;
+ uint8_t algorithm = SR_ALGORITHM_SPF;
+ struct isis *isis;
+#ifndef FABRICD
+ int idx = 0;
+
+ if (argv_find(argv, argc, "algorithm", &idx))
+ algorithm = (uint8_t)strtoul(argv[idx + 1]->arg, NULL, 10);
+#endif /* ifndef FABRICD */
+
+ for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ vty_out(vty, "Area %s:\n",
+ area->area_tag ? area->area_tag : "null");
+ if (!area->srdb.enabled) {
+ vty_out(vty, " Segment Routing is disabled\n");
+ continue;
+ }
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS;
+ level++)
+ show_node(vty, area, level, algorithm);
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* --- IS-IS Segment Routing Management function ---------------------------- */
+
+/**
+ * Thread function to re-attempt connection to the Label Manager and thus be
+ * able to start Segment Routing.
+ *
+ * @param start Thread structure that contains area as argument
+ *
+ * @return 1 on success
+ */
+static void sr_start_label_manager(struct event *start)
+{
+ struct isis_area *area;
+
+ area = EVENT_ARG(start);
+
+ /* re-attempt to start SR & Label Manager connection */
+ isis_sr_start(area);
+}
+
+/**
+ * Enable SR on the given IS-IS area.
+ *
+ * @param area IS-IS area
+ *
+ * @return 0 on success, -1 otherwise
+ */
+int isis_sr_start(struct isis_area *area)
+{
+ struct isis_sr_db *srdb = &area->srdb;
+ struct isis_adjacency *adj;
+ struct listnode *node;
+
+ /* First start Label Manager if not ready */
+ if (!isis_zebra_label_manager_ready())
+ if (isis_zebra_label_manager_connect() < 0) {
+ /* Re-attempt to connect to Label Manager in 1 sec. */
+ event_add_timer(master, sr_start_label_manager, area, 1,
+ &srdb->t_start_lm);
+ return -1;
+ }
+
+ /* Label Manager is ready, initialize the SRLB */
+ if (sr_local_block_init(area) < 0)
+ return -1;
+
+ /*
+ * Request SGRB to the label manager if not already active. If the
+ * allocation fails, return an error to disable SR until a new SRGB
+ * is successfully allocated.
+ */
+ if (!srdb->srgb_active) {
+ if (isis_zebra_request_label_range(
+ srdb->config.srgb_lower_bound,
+ srdb->config.srgb_upper_bound
+ - srdb->config.srgb_lower_bound + 1)
+ < 0) {
+ srdb->srgb_active = false;
+ return -1;
+ } else
+ srdb->srgb_active = true;
+ }
+
+ sr_debug("ISIS-Sr: Starting Segment Routing for area %s",
+ area->area_tag);
+
+ /* Create Adjacency-SIDs from existing IS-IS Adjacencies. */
+ for (ALL_LIST_ELEMENTS_RO(area->adjacency_list, node, adj)) {
+ if (adj->ipv4_address_count > 0)
+ sr_adj_sid_add(adj, AF_INET);
+ if (adj->ll_ipv6_count > 0)
+ sr_adj_sid_add(adj, AF_INET6);
+ }
+
+ area->srdb.enabled = true;
+
+ /* Regenerate LSPs to advertise Segment Routing capabilities. */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return 0;
+}
+
+/**
+ * Disable SR on the given IS-IS area.
+ *
+ * @param area IS-IS area
+ */
+void isis_sr_stop(struct isis_area *area)
+{
+ struct isis_sr_db *srdb = &area->srdb;
+ struct sr_adjacency *sra;
+ struct listnode *node, *nnode;
+
+ sr_debug("ISIS-Sr: Stopping Segment Routing for area %s",
+ area->area_tag);
+
+ /* Disable any re-attempt to connect to Label Manager */
+ EVENT_OFF(srdb->t_start_lm);
+
+ /* Uninstall all local Adjacency-SIDs. */
+ for (ALL_LIST_ELEMENTS(area->srdb.adj_sids, node, nnode, sra))
+ sr_adj_sid_del(sra);
+
+ /* Release SRGB if active. */
+ if (srdb->srgb_active) {
+ isis_zebra_release_label_range(srdb->config.srgb_lower_bound,
+ srdb->config.srgb_upper_bound);
+ srdb->srgb_active = false;
+ }
+
+ /* Delete SRLB */
+ sr_local_block_delete(area);
+
+ area->srdb.enabled = false;
+
+ /* Regenerate LSPs to advertise that the Node is no more SR enable. */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+}
+
+/**
+ * IS-IS Segment Routing initialization for given area.
+ *
+ * @param area IS-IS area
+ */
+void isis_sr_area_init(struct isis_area *area)
+{
+ struct isis_sr_db *srdb = &area->srdb;
+
+ sr_debug("ISIS-Sr (%s): Initialize Segment Routing SRDB",
+ area->area_tag);
+
+ /* Initialize Segment Routing Data Base */
+ memset(srdb, 0, sizeof(*srdb));
+ srdb->adj_sids = list_new();
+
+ /* Pull defaults from the YANG module. */
+#ifndef FABRICD
+ srdb->config.enabled = yang_get_default_bool("%s/enabled", ISIS_SR);
+ srdb->config.srgb_lower_bound = yang_get_default_uint32(
+ "%s/label-blocks/srgb/lower-bound", ISIS_SR);
+ srdb->config.srgb_upper_bound = yang_get_default_uint32(
+ "%s/label-blocks/srgb/upper-bound", ISIS_SR);
+ srdb->config.srlb_lower_bound = yang_get_default_uint32(
+ "%s/label-blocks/srlb/lower-bound", ISIS_SR);
+ srdb->config.srlb_upper_bound = yang_get_default_uint32(
+ "%s/label-blocks/srlb/upper-bound", ISIS_SR);
+#else
+ srdb->config.enabled = false;
+ srdb->config.srgb_lower_bound = SRGB_LOWER_BOUND;
+ srdb->config.srgb_upper_bound = SRGB_UPPER_BOUND;
+ srdb->config.srlb_lower_bound = SRLB_LOWER_BOUND;
+ srdb->config.srlb_upper_bound = SRLB_UPPER_BOUND;
+#endif
+ srdb->config.msd = 0;
+ srdb_prefix_cfg_init(&srdb->config.prefix_sids);
+}
+
+/**
+ * Terminate IS-IS Segment Routing for the given area.
+ *
+ * @param area IS-IS area
+ */
+void isis_sr_area_term(struct isis_area *area)
+{
+ struct isis_sr_db *srdb = &area->srdb;
+
+ /* Stop Segment Routing */
+ if (area->srdb.enabled)
+ isis_sr_stop(area);
+
+ /* Free Adjacency SID list */
+ list_delete(&srdb->adj_sids);
+
+ /* Clear Prefix-SID configuration. */
+ while (srdb_prefix_cfg_count(&srdb->config.prefix_sids) > 0) {
+ struct sr_prefix_cfg *pcfg;
+
+ pcfg = srdb_prefix_cfg_first(&srdb->config.prefix_sids);
+ isis_sr_cfg_prefix_del(pcfg);
+ }
+}
+
+/**
+ * IS-IS Segment Routing global initialization.
+ */
+void isis_sr_init(void)
+{
+ install_element(VIEW_NODE, &show_sr_node_cmd);
+
+ /* Register hooks. */
+ hook_register(isis_adj_state_change_hook, sr_adj_state_change);
+ hook_register(isis_adj_ip_enabled_hook, sr_adj_ip_enabled);
+ hook_register(isis_adj_ip_disabled_hook, sr_adj_ip_disabled);
+}
+
+/**
+ * IS-IS Segment Routing global terminate.
+ */
+void isis_sr_term(void)
+{
+ /* Unregister hooks. */
+ hook_unregister(isis_adj_state_change_hook, sr_adj_state_change);
+ hook_unregister(isis_adj_ip_enabled_hook, sr_adj_ip_enabled);
+ hook_unregister(isis_adj_ip_disabled_hook, sr_adj_ip_disabled);
+}
diff --git a/isisd/isis_sr.h b/isisd/isis_sr.h
new file mode 100644
index 0000000..4378760
--- /dev/null
+++ b/isisd/isis_sr.h
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This is an implementation of Segment Routing for IS-IS as per RFC 8667
+ *
+ * Copyright (C) 2019 Orange http://www.orange.com
+ *
+ * Author: Olivier Dugeon <olivier.dugeon@orange.com>
+ * Contributor: Renato Westphal <renato@opensourcerouting.org> for NetDEF
+ */
+
+#ifndef _FRR_ISIS_SR_H
+#define _FRR_ISIS_SR_H
+
+#include "lib/linklist.h"
+#include "lib/mpls.h"
+#include "lib/nexthop.h"
+#include "lib/typesafe.h"
+
+#include "isisd/isis_tlvs.h"
+
+/*
+ * Segment Routing information is transported through the following Sub-TLVs:
+ *
+ * Sub-TLV Name Value TLVs
+ * ---------------------------------------------------------------------
+ * SID Label 1
+ *
+ * Prefix Segment Identifier 3 135, 235, 236 and 237
+ *
+ * Adjacency Segment Identifier 31 22, 23, 141, 222 and 223
+ * LAN Adjacency Segment Identifier 32 22, 23, 141, 222 and 223
+ *
+ * Segment Routing Capability 2 242
+ * Segment Routing Algorithm 19 242
+ * Node Maximum Stack Depth (MSD) 23 242
+ *
+ * Sub-TLV definitions, serialization and de-serialization are defined
+ * in isis_tlvs.[c,h].
+ */
+
+#define SRGB_LOWER_BOUND 16000
+#define SRGB_UPPER_BOUND 23999
+#define SRLB_LOWER_BOUND 15000
+#define SRLB_UPPER_BOUND 15999
+
+/* Segment Routing Data Base (SRDB) RB-Tree structure */
+PREDECL_RBTREE_UNIQ(srdb_prefix_cfg);
+
+/*
+ * Segment Routing Prefix-SID information.
+ *
+ * This structure is intended to be embedded inside other structures that
+ * might or might not contain Prefix-SID information.
+ */
+struct isis_sr_psid_info {
+ /* Prefix-SID Sub-TLV information. */
+ struct isis_prefix_sid sid;
+
+ /* Resolved input/output label. */
+ mpls_label_t label;
+
+ /* Indicates whether the Prefix-SID is present or not. */
+ bool present;
+
+ uint8_t algorithm;
+
+ struct list *nexthops;
+ struct list *nexthops_backup;
+};
+
+/* Segment Routing Local Block allocation */
+struct sr_local_block {
+ bool active;
+ uint32_t start;
+ uint32_t end;
+ uint32_t current;
+ uint32_t max_block;
+ uint64_t *used_mark;
+};
+#define SRLB_BLOCK_SIZE 64
+
+/* Segment Routing Adjacency-SID type. */
+enum sr_adj_type {
+ ISIS_SR_ADJ_NORMAL = 0,
+ ISIS_SR_LAN_BACKUP,
+};
+
+/* Segment Routing Adjacency. */
+struct sr_adjacency {
+ /* Adjacency type. */
+ enum sr_adj_type type;
+
+ /* Adjacency-SID input label. */
+ mpls_label_t input_label;
+
+ /* Adjacency-SID nexthop information. */
+ struct {
+ int family;
+ union g_addr address;
+ } nexthop;
+
+ /* Adjacency-SID TI-LFA backup nexthops. */
+ struct list *backup_nexthops;
+
+ /* (LAN-)Adjacency-SID Sub-TLV. */
+ union {
+ struct isis_adj_sid *adj_sid;
+ struct isis_lan_adj_sid *ladj_sid;
+ } u;
+
+ /* Back pointer to IS-IS adjacency. */
+ struct isis_adjacency *adj;
+};
+
+/* SID type. NOTE: these values must be in sync with the YANG module. */
+enum sr_sid_value_type {
+ SR_SID_VALUE_TYPE_INDEX = 0,
+ SR_SID_VALUE_TYPE_ABSOLUTE = 1,
+};
+
+#define IS_SID_VALUE(flag) CHECK_FLAG(flag, ISIS_PREFIX_SID_VALUE)
+
+/* Last Hop Behavior. NOTE: these values must be in sync with the YANG module */
+enum sr_last_hop_behavior {
+ SR_LAST_HOP_BEHAVIOR_EXP_NULL = 0,
+ SR_LAST_HOP_BEHAVIOR_NO_PHP = 1,
+ SR_LAST_HOP_BEHAVIOR_PHP = 2,
+};
+
+/* Segment Routing Prefix-SID configuration. */
+struct sr_prefix_cfg {
+ /* SRDB RB-tree entry. */
+ struct srdb_prefix_cfg_item entry;
+
+ /* IP prefix. */
+ struct prefix prefix;
+
+ /* SID value. */
+ uint32_t sid;
+
+ /* SID value type. */
+ enum sr_sid_value_type sid_type;
+
+ /* SID last hop behavior. */
+ enum sr_last_hop_behavior last_hop_behavior;
+
+ /* Indicates whether the node flag must be explicitly unset. */
+ bool n_flag_clear;
+
+ /* Does this Prefix-SID refer to a loopback address (Node-SID)? */
+ bool node_sid;
+
+ /* Backpointer to IS-IS area. */
+ struct isis_area *area;
+
+ /* SR Algorithm number */
+ uint8_t algorithm;
+};
+
+/* Per-area IS-IS Segment Routing Data Base (SRDB). */
+struct isis_sr_db {
+ /* Global Operational status of Segment Routing. */
+ bool enabled;
+
+ /* Thread timer to start Label Manager */
+ struct event *t_start_lm;
+
+ /* List of local Adjacency-SIDs. */
+ struct list *adj_sids;
+
+ /* Management of SRLB & SRGB allocation */
+ struct sr_local_block srlb;
+ bool srgb_active;
+
+ /* Area Segment Routing configuration. */
+ struct {
+ /* Administrative status of Segment Routing. */
+ bool enabled;
+
+ /* Segment Routing Global Block lower & upper bound. */
+ uint32_t srgb_lower_bound;
+ uint32_t srgb_upper_bound;
+
+ /* Segment Routing Local Block lower & upper bound. */
+ uint32_t srlb_lower_bound;
+ uint32_t srlb_upper_bound;
+
+ /* Maximum SID Depth supported by the node. */
+ uint8_t msd;
+
+ /* Prefix-SID mappings. */
+ struct srdb_prefix_cfg_head prefix_sids;
+ } config;
+};
+
+/* Prototypes. */
+extern struct isis_sr_block *isis_sr_find_srgb(struct lspdb_head *lspdb,
+ const uint8_t *sysid);
+extern mpls_label_t sr_prefix_in_label(struct isis_area *area,
+ struct isis_prefix_sid *psid,
+ bool local);
+extern mpls_label_t sr_prefix_out_label(struct lspdb_head *lspdb, int family,
+ struct isis_prefix_sid *psid,
+ const uint8_t *nh_sysid, bool last_hop);
+extern int isis_sr_cfg_srgb_update(struct isis_area *area, uint32_t lower_bound,
+ uint32_t upper_bound);
+extern int isis_sr_cfg_srlb_update(struct isis_area *area, uint32_t lower_bound,
+ uint32_t upper_bound);
+extern struct sr_prefix_cfg *isis_sr_cfg_prefix_add(struct isis_area *area,
+ const struct prefix *prefix,
+ uint8_t algorithm);
+extern void isis_sr_cfg_prefix_del(struct sr_prefix_cfg *pcfg);
+extern struct sr_prefix_cfg *
+isis_sr_cfg_prefix_find(struct isis_area *area, union prefixconstptr prefix,
+ uint8_t algorithm);
+extern void isis_sr_prefix_cfg2subtlv(const struct sr_prefix_cfg *pcfg,
+ bool external,
+ struct isis_prefix_sid *psid);
+extern void sr_adj_sid_add_single(struct isis_adjacency *adj, int family,
+ bool backup, struct list *nexthops);
+extern struct sr_adjacency *isis_sr_adj_sid_find(struct isis_adjacency *adj,
+ int family,
+ enum sr_adj_type type);
+extern void isis_area_delete_backup_adj_sids(struct isis_area *area, int level);
+extern int sr_if_addr_update(struct interface *ifp);
+extern char *sr_op2str(char *buf, size_t size, mpls_label_t label_in,
+ mpls_label_t label_out);
+extern int isis_sr_start(struct isis_area *area);
+extern void isis_sr_stop(struct isis_area *area);
+extern void isis_sr_area_init(struct isis_area *area);
+extern void isis_sr_area_term(struct isis_area *area);
+extern void isis_sr_init(void);
+extern void isis_sr_term(void);
+
+#endif /* _FRR_ISIS_SR_H */
diff --git a/isisd/isis_srv6.c b/isisd/isis_srv6.c
new file mode 100644
index 0000000..1b0c706
--- /dev/null
+++ b/isisd/isis_srv6.c
@@ -0,0 +1,862 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This is an implementation of Segment Routing over IPv6 (SRv6) for IS-IS
+ * as per RFC 9352
+ * https://datatracker.ietf.org/doc/html/rfc9352
+ *
+ * Copyright (C) 2023 Carmine Scarpitta - University of Rome Tor Vergata
+ */
+
+#include <zebra.h>
+
+#include "srv6.h"
+#include "termtable.h"
+#include "lib/lib_errors.h"
+
+#include "isisd/isisd.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_route.h"
+#include "isisd/isis_srv6.h"
+#include "isisd/isis_zebra.h"
+
+/* Local variables and functions */
+DEFINE_MTYPE_STATIC(ISISD, ISIS_SRV6_SID, "ISIS SRv6 Segment ID");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_SRV6_INFO, "ISIS SRv6 information");
+
+/**
+ * Fill in SRv6 SID Structure Sub-Sub-TLV with information from an SRv6 SID.
+ *
+ * @param sid SRv6 SID configuration
+ * @param structure_subsubtlv SRv6 SID Structure Sub-Sub-TLV to be updated
+ */
+void isis_srv6_sid_structure2subsubtlv(
+ const struct isis_srv6_sid *sid,
+ struct isis_srv6_sid_structure_subsubtlv *structure_subsubtlv)
+{
+ /* Set Locator Block length */
+ structure_subsubtlv->loc_block_len = sid->structure.loc_block_len;
+
+ /* Set Locator Node length */
+ structure_subsubtlv->loc_node_len = sid->structure.loc_node_len;
+
+ /* Set Function length */
+ structure_subsubtlv->func_len = sid->structure.func_len;
+
+ /* Set Argument length */
+ structure_subsubtlv->arg_len = sid->structure.arg_len;
+}
+
+/**
+ * Fill in SRv6 End SID Sub-TLV with information from an SRv6 SID.
+ *
+ * @param sid SRv6 SID configuration
+ * @param sid_subtlv SRv6 End SID Sub-TLV to be updated
+ */
+void isis_srv6_end_sid2subtlv(const struct isis_srv6_sid *sid,
+ struct isis_srv6_end_sid_subtlv *sid_subtlv)
+{
+ /* Set SRv6 SID flags */
+ sid_subtlv->flags = sid->flags;
+
+ /* Set SRv6 SID behavior */
+ sid_subtlv->behavior = sid->behavior;
+
+ /* Set SRv6 SID value */
+ sid_subtlv->sid = sid->sid;
+}
+
+/**
+ * Fill in SRv6 Locator TLV with information from an SRv6 locator.
+ *
+ * @param loc SRv6 Locator configuration
+ * @param loc_tlv SRv6 Locator TLV to be updated
+ */
+void isis_srv6_locator2tlv(const struct isis_srv6_locator *loc,
+ struct isis_srv6_locator_tlv *loc_tlv)
+{
+ /* Set SRv6 Locator metric */
+ loc_tlv->metric = loc->metric;
+
+ /* Set SRv6 Locator flags */
+ loc_tlv->flags = loc->flags;
+
+ /* Set SRv6 Locator algorithm */
+ loc_tlv->algorithm = loc->algorithm;
+
+ /* Set SRv6 Locator prefix */
+ loc_tlv->prefix = loc->prefix;
+}
+
+/**
+ * Unset the SRv6 locator for a given IS-IS area.
+ *
+ * @param area IS-IS area
+ *
+ * @result True on success, False otherwise
+ */
+bool isis_srv6_locator_unset(struct isis_area *area)
+{
+ int ret;
+ struct listnode *node, *nnode;
+ struct srv6_locator_chunk *chunk;
+ struct isis_srv6_sid *sid;
+ struct srv6_adjacency *sra;
+
+ if (strncmp(area->srv6db.config.srv6_locator_name, "",
+ sizeof(area->srv6db.config.srv6_locator_name)) == 0) {
+ sr_debug("SRv6 locator not set");
+ return true;
+ }
+
+ /* Delete SRv6 SIDs */
+ for (ALL_LIST_ELEMENTS(area->srv6db.srv6_sids, node, nnode, sid)) {
+ sr_debug(
+ "Deleting SRv6 SID (locator %s, sid %pI6) from IS-IS area %s",
+ area->srv6db.config.srv6_locator_name, &sid->sid,
+ area->area_tag);
+
+ /* Uninstall the SRv6 SID from the forwarding plane through
+ * Zebra */
+ isis_zebra_srv6_sid_uninstall(area, sid);
+
+ listnode_delete(area->srv6db.srv6_sids, sid);
+ isis_srv6_sid_free(sid);
+ }
+
+ /* Uninstall all local Adjacency-SIDs. */
+ for (ALL_LIST_ELEMENTS(area->srv6db.srv6_endx_sids, node, nnode, sra))
+ srv6_endx_sid_del(sra);
+
+ /* Inform Zebra that we are releasing the SRv6 locator */
+ ret = isis_zebra_srv6_manager_release_locator_chunk(
+ area->srv6db.config.srv6_locator_name);
+ if (ret < 0)
+ return false;
+
+ /* Delete chunks */
+ for (ALL_LIST_ELEMENTS(area->srv6db.srv6_locator_chunks, node, nnode,
+ chunk)) {
+ sr_debug(
+ "Releasing chunk of locator %s (prefix %pFX) for IS-IS area %s",
+ area->srv6db.config.srv6_locator_name, &chunk->prefix,
+ area->area_tag);
+
+ listnode_delete(area->srv6db.srv6_locator_chunks, chunk);
+ srv6_locator_chunk_free(&chunk);
+ }
+
+ /* Clear locator name */
+ memset(area->srv6db.config.srv6_locator_name, 0,
+ sizeof(area->srv6db.config.srv6_locator_name));
+
+ /* Regenerate LSPs to advertise that the SRv6 locator no longer exists
+ */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return true;
+}
+
+/**
+ * Set the interface used to install SRv6 SIDs into the data plane.
+ *
+ * @param area IS-IS area
+ */
+void isis_srv6_interface_set(struct isis_area *area, const char *ifname)
+{
+ struct listnode *node;
+ struct isis_srv6_sid *sid;
+
+ if (!ifname)
+ return;
+
+ if (!strncmp(ifname, area->srv6db.config.srv6_ifname, IF_NAMESIZE)) {
+ /* The interface has not changed, nothing to do */
+ return;
+ }
+
+ sr_debug("SRv6 interface for IS-IS area %s changed (old interface: %s, new interface: %s)", area->area_tag, area->srv6db.config.srv6_ifname, ifname);
+
+ /* Walk through all SIDs and uninstall them from the data plane */
+ for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node, sid)) {
+ sr_debug("Uninstalling SID %pI6 from the data plane", &sid->sid);
+ isis_zebra_srv6_sid_uninstall(area, sid);
+ }
+
+ strlcpy(area->srv6db.config.srv6_ifname, ifname, sizeof(area->srv6db.config.srv6_ifname));
+
+ if (!if_lookup_by_name(area->srv6db.config.srv6_ifname, VRF_DEFAULT)) {
+ sr_debug("Interface %s not yet exist in data plane, deferring SIDs installation until it's created", area->srv6db.config.srv6_ifname);
+ return;
+ }
+
+ /* Walk through all SIDs and re-install them into the data plane with the newly configured interface */
+ for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node, sid)) {
+ sr_debug("Installing SID %pI6 from the data plane", &sid->sid);
+ isis_zebra_srv6_sid_install(area, sid);
+ }
+}
+
+/**
+ * Encode SID function in the SRv6 SID.
+ *
+ * @param sid
+ * @param func
+ * @param offset
+ * @param len
+ */
+static void encode_sid_func(struct in6_addr *sid, uint32_t func, uint8_t offset,
+ uint8_t len)
+{
+ for (uint8_t idx = 0; idx < len; idx++) {
+ uint8_t tidx = offset + idx;
+ sid->s6_addr[tidx / 8] &= ~(0x1 << (7 - tidx % 8));
+ if (func >> (len - 1 - idx) & 0x1)
+ sid->s6_addr[tidx / 8] |= 0x1 << (7 - tidx % 8);
+ }
+}
+
+static bool sid_exist(struct isis_area *area, const struct in6_addr *sid)
+{
+ struct listnode *node;
+ struct isis_srv6_sid *s;
+ struct srv6_adjacency *sra;
+
+ for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node, s))
+ if (sid_same(&s->sid, sid))
+ return true;
+ for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_endx_sids, node, sra))
+ if (sid_same(&sra->sid, sid))
+ return true;
+ return false;
+}
+
+/**
+ * Request a SID from the SRv6 locator.
+ *
+ * @param area IS-IS area
+ * @param chunk SRv6 locator chunk
+ * @param sid_func The FUNCTION part of the SID to be allocated (a negative
+ * number will allocate the first available SID)
+ *
+ * @return First available SID on success or in6addr_any if the SRv6
+ * locator chunk is full
+ */
+static struct in6_addr
+srv6_locator_request_sid(struct isis_area *area,
+ struct srv6_locator_chunk *chunk, int sid_func)
+{
+ struct in6_addr sid;
+ uint8_t offset = 0;
+ uint8_t func_len = 0;
+ uint32_t func_max;
+ bool allocated = false;
+
+ if (!area || !chunk)
+ return in6addr_any;
+
+ sr_debug("ISIS-SRv6 (%s): requested new SID from locator %s",
+ area->area_tag, chunk->locator_name);
+
+ /* Let's build the SID, step by step. A SID has the following structure
+ (defined in RFC 8986): LOCATOR:FUNCTION:ARGUMENT.*/
+
+ /* First, we encode the LOCATOR in the L most significant bits. */
+ sid = chunk->prefix.prefix;
+
+ /* The next part of the SID is the FUNCTION. Let's compute the length
+ * and the offset of the FUNCTION in the SID */
+ func_len = chunk->function_bits_length;
+ offset = chunk->block_bits_length + chunk->node_bits_length;
+
+ /* Then, encode the FUNCTION */
+ if (sid_func >= 0) {
+ /* SID FUNCTION has been specified. We need to allocate a SID
+ * with the requested FUNCTION. */
+ encode_sid_func(&sid, sid_func, offset, func_len);
+ if (sid_exist(area, &sid)) {
+ zlog_warn(
+ "ISIS-SRv6 (%s): the requested SID %pI6 is already used",
+ area->area_tag, &sid);
+ return sid;
+ }
+ allocated = true;
+ } else {
+ /* SID FUNCTION not specified. We need to choose a FUNCTION that
+ * is not already used. So let's iterate through all possible
+ * functions and get the first available one. */
+ func_max = (1 << func_len) - 1;
+ for (uint32_t func = 1; func < func_max; func++) {
+ encode_sid_func(&sid, func, offset, func_len);
+ if (sid_exist(area, &sid))
+ continue;
+ allocated = true;
+ break;
+ }
+ }
+
+ if (!allocated) {
+ /* We ran out of available SIDs */
+ zlog_warn("ISIS-SRv6 (%s): no SIDs available in locator %s",
+ area->area_tag, chunk->locator_name);
+ return in6addr_any;
+ }
+
+ sr_debug("ISIS-SRv6 (%s): allocating new SID %pI6", area->area_tag,
+ &sid);
+
+ return sid;
+}
+
+/**
+ * Allocate an SRv6 SID from an SRv6 locator.
+ *
+ * @param area IS-IS area
+ * @param chunk SRv6 locator chunk
+ * @param behavior SRv6 Endpoint Behavior bound to the SID
+ *
+ * @result the allocated SID on success, NULL otherwise
+ */
+struct isis_srv6_sid *
+isis_srv6_sid_alloc(struct isis_area *area, struct srv6_locator_chunk *chunk,
+ enum srv6_endpoint_behavior_codepoint behavior,
+ int sid_func)
+{
+ struct isis_srv6_sid *sid = NULL;
+
+ if (!area || !chunk)
+ return NULL;
+
+ sid = XCALLOC(MTYPE_ISIS_SRV6_SID, sizeof(struct isis_srv6_sid));
+
+ sid->sid = srv6_locator_request_sid(area, chunk, sid_func);
+ if (IPV6_ADDR_SAME(&sid->sid, &in6addr_any)) {
+ isis_srv6_sid_free(sid);
+ return NULL;
+ }
+
+ sid->behavior = behavior;
+ sid->structure.loc_block_len = chunk->block_bits_length;
+ sid->structure.loc_node_len = chunk->node_bits_length;
+ sid->structure.func_len = chunk->function_bits_length;
+ sid->structure.arg_len = chunk->argument_bits_length;
+ sid->locator = chunk;
+ sid->area = area;
+
+ return sid;
+}
+
+void isis_srv6_sid_free(struct isis_srv6_sid *sid)
+{
+ XFREE(MTYPE_ISIS_SRV6_SID, sid);
+}
+
+/**
+ * Delete all backup SRv6 End.X SIDs.
+ *
+ * @param area IS-IS area
+ * @param level IS-IS level
+ */
+void isis_area_delete_backup_srv6_endx_sids(struct isis_area *area, int level)
+{
+ struct srv6_adjacency *sra;
+ struct listnode *node, *nnode;
+
+ for (ALL_LIST_ELEMENTS(area->srv6db.srv6_endx_sids, node, nnode, sra))
+ if (sra->type == ISIS_SRV6_LAN_BACKUP &&
+ (sra->adj->level & level))
+ srv6_endx_sid_del(sra);
+}
+
+/* --- SRv6 End.X SID management functions ------------------- */
+
+/**
+ * Add new local End.X SID.
+ *
+ * @param adj IS-IS Adjacency
+ * @param backup True to initialize backup Adjacency SID
+ * @param nexthops List of backup nexthops (for backup End.X SIDs only)
+ */
+void srv6_endx_sid_add_single(struct isis_adjacency *adj, bool backup,
+ struct list *nexthops)
+{
+ struct isis_circuit *circuit = adj->circuit;
+ struct isis_area *area = circuit->area;
+ struct srv6_adjacency *sra;
+ struct isis_srv6_endx_sid_subtlv *adj_sid;
+ struct isis_srv6_lan_endx_sid_subtlv *ladj_sid;
+ struct in6_addr nexthop;
+ uint8_t flags = 0;
+ struct srv6_locator_chunk *chunk;
+ uint32_t behavior;
+
+ if (!area || !area->srv6db.srv6_locator_chunks ||
+ list_isempty(area->srv6db.srv6_locator_chunks))
+ return;
+
+ sr_debug("ISIS-SRv6 (%s): Add %s End.X SID", area->area_tag,
+ backup ? "Backup" : "Primary");
+
+ /* Determine nexthop IP address */
+ if (!circuit->ipv6_router || !adj->ll_ipv6_count)
+ return;
+
+ chunk = (struct srv6_locator_chunk *)listgetdata(
+ listhead(area->srv6db.srv6_locator_chunks));
+ if (!chunk)
+ return;
+
+ nexthop = adj->ll_ipv6_addrs[0];
+
+ /* Prepare SRv6 End.X as per RFC9352 section #8.1 */
+ if (backup)
+ SET_FLAG(flags, EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG);
+
+ if (circuit->ext == NULL)
+ circuit->ext = isis_alloc_ext_subtlvs();
+
+ behavior = (CHECK_FLAG(chunk->flags, SRV6_LOCATOR_USID))
+ ? SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID
+ : SRV6_ENDPOINT_BEHAVIOR_END_X;
+
+ sra = XCALLOC(MTYPE_ISIS_SRV6_INFO, sizeof(*sra));
+ sra->type = backup ? ISIS_SRV6_LAN_BACKUP : ISIS_SRV6_ADJ_NORMAL;
+ sra->behavior = behavior;
+ sra->locator = chunk;
+ sra->structure.loc_block_len = chunk->block_bits_length;
+ sra->structure.loc_node_len = chunk->node_bits_length;
+ sra->structure.func_len = chunk->function_bits_length;
+ sra->structure.arg_len = chunk->argument_bits_length;
+ sra->nexthop = nexthop;
+
+ sra->sid = srv6_locator_request_sid(area, chunk, -1);
+ if (IPV6_ADDR_SAME(&sra->sid, &in6addr_any)) {
+ XFREE(MTYPE_ISIS_SRV6_INFO, sra);
+ return;
+ }
+
+ switch (circuit->circ_type) {
+ /* SRv6 LAN End.X SID for Broadcast interface section #8.2 */
+ case CIRCUIT_T_BROADCAST:
+ ladj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*ladj_sid));
+ memcpy(ladj_sid->neighbor_id, adj->sysid,
+ sizeof(ladj_sid->neighbor_id));
+ ladj_sid->flags = flags;
+ ladj_sid->algorithm = SR_ALGORITHM_SPF;
+ ladj_sid->weight = 0;
+ ladj_sid->behavior = sra->behavior;
+ ladj_sid->sid = sra->sid;
+ ladj_sid->subsubtlvs = isis_alloc_subsubtlvs(
+ ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID);
+ ladj_sid->subsubtlvs->srv6_sid_structure = XCALLOC(
+ MTYPE_ISIS_SUBSUBTLV,
+ sizeof(*ladj_sid->subsubtlvs->srv6_sid_structure));
+ ladj_sid->subsubtlvs->srv6_sid_structure->loc_block_len =
+ sra->structure.loc_block_len;
+ ladj_sid->subsubtlvs->srv6_sid_structure->loc_node_len =
+ sra->structure.loc_node_len;
+ ladj_sid->subsubtlvs->srv6_sid_structure->func_len =
+ sra->structure.func_len;
+ ladj_sid->subsubtlvs->srv6_sid_structure->arg_len =
+ sra->structure.arg_len;
+ isis_tlvs_add_srv6_lan_endx_sid(circuit->ext, ladj_sid);
+ sra->u.lendx_sid = ladj_sid;
+ break;
+ /* SRv6 End.X SID for Point to Point interface section #8.1 */
+ case CIRCUIT_T_P2P:
+ adj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*adj_sid));
+ adj_sid->flags = flags;
+ adj_sid->algorithm = SR_ALGORITHM_SPF;
+ adj_sid->weight = 0;
+ adj_sid->behavior = sra->behavior;
+ adj_sid->sid = sra->sid;
+ adj_sid->subsubtlvs = isis_alloc_subsubtlvs(
+ ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID);
+ adj_sid->subsubtlvs->srv6_sid_structure = XCALLOC(
+ MTYPE_ISIS_SUBSUBTLV,
+ sizeof(*adj_sid->subsubtlvs->srv6_sid_structure));
+ adj_sid->subsubtlvs->srv6_sid_structure->loc_block_len =
+ sra->structure.loc_block_len;
+ adj_sid->subsubtlvs->srv6_sid_structure->loc_node_len =
+ sra->structure.loc_node_len;
+ adj_sid->subsubtlvs->srv6_sid_structure->func_len =
+ sra->structure.func_len;
+ adj_sid->subsubtlvs->srv6_sid_structure->arg_len =
+ sra->structure.arg_len;
+ isis_tlvs_add_srv6_endx_sid(circuit->ext, adj_sid);
+ sra->u.endx_sid = adj_sid;
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u",
+ __func__, circuit->circ_type);
+ exit(1);
+ }
+
+ /* Add Adjacency-SID in SRDB */
+ sra->adj = adj;
+ listnode_add(area->srv6db.srv6_endx_sids, sra);
+ listnode_add(adj->srv6_endx_sids, sra);
+
+ isis_zebra_srv6_adj_sid_install(sra);
+}
+
+/**
+ * Add Primary and Backup local SRv6 End.X SID.
+ *
+ * @param adj IS-IS Adjacency
+ */
+void srv6_endx_sid_add(struct isis_adjacency *adj)
+{
+ srv6_endx_sid_add_single(adj, false, NULL);
+}
+
+/**
+ * Delete local SRv6 End.X SID.
+ *
+ * @param sra SRv6 Adjacency
+ */
+void srv6_endx_sid_del(struct srv6_adjacency *sra)
+{
+ struct isis_circuit *circuit = sra->adj->circuit;
+ struct isis_area *area = circuit->area;
+
+ sr_debug("ISIS-SRv6 (%s): Delete SRv6 End.X SID", area->area_tag);
+
+ isis_zebra_srv6_adj_sid_uninstall(sra);
+
+ /* Release dynamic SRv6 SID and remove subTLVs */
+ switch (circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ isis_tlvs_del_srv6_lan_endx_sid(circuit->ext, sra->u.lendx_sid);
+ break;
+ case CIRCUIT_T_P2P:
+ isis_tlvs_del_srv6_endx_sid(circuit->ext, sra->u.endx_sid);
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u",
+ __func__, circuit->circ_type);
+ exit(1);
+ }
+
+ if (sra->type == ISIS_SRV6_LAN_BACKUP && sra->backup_nexthops) {
+ sra->backup_nexthops->del =
+ (void (*)(void *))isis_nexthop_delete;
+ list_delete(&sra->backup_nexthops);
+ }
+
+ /* Remove Adjacency-SID from the SRDB */
+ listnode_delete(area->srv6db.srv6_endx_sids, sra);
+ listnode_delete(sra->adj->srv6_endx_sids, sra);
+ XFREE(MTYPE_ISIS_SRV6_INFO, sra);
+}
+
+/**
+ * Lookup SRv6 End.X SID by type.
+ *
+ * @param adj IS-IS Adjacency
+ * @param type SRv6 End.X SID type
+ */
+struct srv6_adjacency *isis_srv6_endx_sid_find(struct isis_adjacency *adj,
+ enum srv6_adj_type type)
+{
+ struct srv6_adjacency *sra;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(adj->srv6_endx_sids, node, sra))
+ if (sra->type == type)
+ return sra;
+
+ return NULL;
+}
+
+/**
+ * Remove all SRv6 End.X SIDs associated to an adjacency that is going down.
+ *
+ * @param adj IS-IS Adjacency
+ *
+ * @return 0
+ */
+static int srv6_adj_state_change(struct isis_adjacency *adj)
+{
+ struct srv6_adjacency *sra;
+ struct listnode *node, *nnode;
+
+ if (!adj->circuit->area->srv6db.config.enabled)
+ return 0;
+
+ if (adj->adj_state == ISIS_ADJ_UP)
+ return 0;
+
+ for (ALL_LIST_ELEMENTS(adj->srv6_endx_sids, node, nnode, sra))
+ srv6_endx_sid_del(sra);
+
+ return 0;
+}
+
+/**
+ * When IS-IS Adjacency got one or more IPv6 addresses, add new
+ * IPv6 address to corresponding SRv6 End.X SID accordingly.
+ *
+ * @param adj IS-IS Adjacency
+ * @param family Inet Family (IPv4 or IPv6)
+ * @param global Indicate if it concerns the Local or Global IPv6 addresses
+ *
+ * @return 0
+ */
+static int srv6_adj_ip_enabled(struct isis_adjacency *adj, int family,
+ bool global)
+{
+ if (!adj->circuit->area->srv6db.config.enabled || global ||
+ family != AF_INET6)
+ return 0;
+
+ srv6_endx_sid_add(adj);
+
+ return 0;
+}
+
+/**
+ * When IS-IS Adjacency doesn't have any IPv6 addresses anymore,
+ * delete the corresponding SRv6 End.X SID(s) accordingly.
+ *
+ * @param adj IS-IS Adjacency
+ * @param family Inet Family (IPv4 or IPv6)
+ * @param global Indicate if it concerns the Local or Global IPv6 addresses
+ *
+ * @return 0
+ */
+static int srv6_adj_ip_disabled(struct isis_adjacency *adj, int family,
+ bool global)
+{
+ struct srv6_adjacency *sra;
+ struct listnode *node, *nnode;
+
+ if (!adj->circuit->area->srv6db.config.enabled || global ||
+ family != AF_INET6)
+ return 0;
+
+ for (ALL_LIST_ELEMENTS(adj->srv6_endx_sids, node, nnode, sra))
+ srv6_endx_sid_del(sra);
+
+ return 0;
+}
+
+/**
+ * Show Segment Routing over IPv6 (SRv6) Node.
+ *
+ * @param vty VTY output
+ * @param area IS-IS area
+ * @param level IS-IS level
+ */
+static void show_node(struct vty *vty, struct isis_area *area, int level)
+{
+ struct isis_lsp *lsp;
+ struct ttable *tt;
+
+ vty_out(vty, " IS-IS %s SRv6-Nodes:\n\n", circuit_t2string(level));
+
+ /* Prepare table. */
+ tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+ ttable_add_row(
+ tt,
+ "System ID|Algorithm|SRH Max SL|SRH Max End Pop|SRH Max H.encaps|SRH Max End D");
+ tt->style.cell.rpad = 2;
+ tt->style.corner = '+';
+ ttable_restyle(tt);
+ ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+ frr_each (lspdb, &area->lspdb[level - 1], lsp) {
+ struct isis_router_cap *cap;
+
+ if (!lsp->tlvs)
+ continue;
+ cap = lsp->tlvs->router_cap;
+ if (!cap)
+ continue;
+
+ ttable_add_row(tt, "%pSY|%s|%u|%u|%u|%u", lsp->hdr.lsp_id,
+ cap->algo[0] == SR_ALGORITHM_SPF ? "SPF"
+ : "S-SPF",
+ cap->srv6_msd.max_seg_left_msd,
+ cap->srv6_msd.max_end_pop_msd,
+ cap->srv6_msd.max_h_encaps_msd,
+ cap->srv6_msd.max_end_d_msd);
+ }
+
+ /* Dump the generated table. */
+ if (tt->nrows > 1) {
+ char *table;
+
+ table = ttable_dump(tt, "\n");
+ vty_out(vty, "%s\n", table);
+ XFREE(MTYPE_TMP, table);
+ }
+ ttable_del(tt);
+}
+
+DEFUN(show_srv6_node, show_srv6_node_cmd,
+ "show " PROTO_NAME " segment-routing srv6 node",
+ SHOW_STR
+ PROTO_HELP
+ "Segment-Routing\n"
+ "Segment-Routing over IPv6 (SRv6)\n"
+ "SRv6 node\n")
+{
+ struct listnode *node, *inode;
+ struct isis_area *area;
+ struct isis *isis;
+
+ for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ vty_out(vty, "Area %s:\n",
+ area->area_tag ? area->area_tag : "null");
+ if (!area->srv6db.config.enabled) {
+ vty_out(vty, " SRv6 is disabled\n");
+ continue;
+ }
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS;
+ level++)
+ show_node(vty, area, level);
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+int isis_srv6_ifp_up_notify(struct interface *ifp)
+{
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+ struct listnode *node, *node2;
+ struct isis_area *area;
+ struct isis_srv6_sid *sid;
+
+ if (!isis)
+ return 0;
+
+ /* Walk through all areas of the ISIS instance */
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ /* Skip area, if SRv6 is not enabled */
+ if (!area->srv6db.config.enabled)
+ continue;
+
+ /* Skip area if the interface is not the one configured for SRv6 */
+ if (strncmp(area->srv6db.config.srv6_ifname, ifp->name, IF_NAMESIZE))
+ continue;
+
+ sr_debug("Interface %s went up. Installing SIDs for area %s in data plane", ifp->name, area->area_tag);
+
+ /* Walk through all SIDs and re-install them into the data plane with the newly configured interface */
+ for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node2, sid)) {
+ sr_debug("Installing SID %pI6 from the data plane", &sid->sid);
+ isis_zebra_srv6_sid_install(area, sid);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * IS-IS SRv6 initialization for given area.
+ *
+ * @param area IS-IS area
+ */
+void isis_srv6_area_init(struct isis_area *area)
+{
+ struct isis_srv6_db *srv6db;
+
+ if (!area)
+ return;
+
+ srv6db = &area->srv6db;
+
+ sr_debug("ISIS-SRv6 (%s): Initialize Segment Routing SRv6 DB",
+ area->area_tag);
+
+ /* Initialize SRv6 Data Base */
+ memset(srv6db, 0, sizeof(*srv6db));
+ srv6db->srv6_endx_sids = list_new();
+
+ /* Pull defaults from the YANG module */
+#ifndef FABRICD
+ srv6db->config.enabled = yang_get_default_bool("%s/enabled", ISIS_SRV6);
+ srv6db->config.max_seg_left_msd =
+ yang_get_default_uint8("%s/msd/node-msd/max-segs-left",
+ ISIS_SRV6);
+ srv6db->config.max_end_pop_msd =
+ yang_get_default_uint8("%s/msd/node-msd/max-end-pop", ISIS_SRV6);
+ srv6db->config.max_h_encaps_msd =
+ yang_get_default_uint8("%s/msd/node-msd/max-h-encaps",
+ ISIS_SRV6);
+ srv6db->config.max_end_d_msd =
+ yang_get_default_uint8("%s/msd/node-msd/max-end-d", ISIS_SRV6);
+ strlcpy(srv6db->config.srv6_ifname, yang_get_default_string("%s/interface", ISIS_SRV6), sizeof(srv6db->config.srv6_ifname));
+#else
+ srv6db->config.enabled = false;
+ srv6db->config.max_seg_left_msd = ISIS_DEFAULT_SRV6_MAX_SEG_LEFT_MSD;
+ srv6db->config.max_end_pop_msd = ISIS_DEFAULT_SRV6_MAX_END_POP_MSD;
+ srv6db->config.max_h_encaps_msd = ISIS_DEFAULT_SRV6_MAX_H_ENCAPS_MSD;
+ srv6db->config.max_end_d_msd = ISIS_DEFAULT_SRV6_MAX_END_D_MSD;
+ strlcpy(srv6db->config.srv6_ifname, ISIS_DEFAULT_SRV6_IFNAME, sizeof(srv6db->config.srv6_ifname));
+#endif
+
+ /* Initialize SRv6 Locator chunks list */
+ srv6db->srv6_locator_chunks = list_new();
+
+ /* Initialize SRv6 SIDs list */
+ srv6db->srv6_sids = list_new();
+ srv6db->srv6_sids->del = (void (*)(void *))isis_srv6_sid_free;
+}
+
+/**
+ * Terminate IS-IS SRv6 for the given area.
+ *
+ * @param area IS-IS area
+ */
+void isis_srv6_area_term(struct isis_area *area)
+{
+ struct isis_srv6_db *srv6db = &area->srv6db;
+ struct srv6_adjacency *sra;
+ struct listnode *node, *nnode;
+ struct srv6_locator_chunk *chunk;
+
+ sr_debug("ISIS-SRv6 (%s): Terminate SRv6", area->area_tag);
+
+ /* Uninstall all local SRv6 End.X SIDs */
+ if (area->srv6db.config.enabled)
+ for (ALL_LIST_ELEMENTS(area->srv6db.srv6_endx_sids, node, nnode,
+ sra))
+ srv6_endx_sid_del(sra);
+
+ /* Free SRv6 Locator chunks list */
+ for (ALL_LIST_ELEMENTS(srv6db->srv6_locator_chunks, node, nnode, chunk))
+ srv6_locator_chunk_free(&chunk);
+ list_delete(&srv6db->srv6_locator_chunks);
+
+ /* Free SRv6 SIDs list */
+ list_delete(&srv6db->srv6_sids);
+ list_delete(&srv6db->srv6_endx_sids);
+}
+
+/**
+ * IS-IS SRv6 global initialization.
+ */
+void isis_srv6_init(void)
+{
+ install_element(VIEW_NODE, &show_srv6_node_cmd);
+
+ /* Register hooks. */
+ hook_register(isis_adj_state_change_hook, srv6_adj_state_change);
+ hook_register(isis_adj_ip_enabled_hook, srv6_adj_ip_enabled);
+ hook_register(isis_adj_ip_disabled_hook, srv6_adj_ip_disabled);
+}
+
+/**
+ * IS-IS SRv6 global terminate.
+ */
+void isis_srv6_term(void)
+{
+ /* Unregister hooks. */
+ hook_unregister(isis_adj_state_change_hook, srv6_adj_state_change);
+ hook_unregister(isis_adj_ip_enabled_hook, srv6_adj_ip_enabled);
+ hook_unregister(isis_adj_ip_disabled_hook, srv6_adj_ip_disabled);
+}
diff --git a/isisd/isis_srv6.h b/isisd/isis_srv6.h
new file mode 100644
index 0000000..3386436
--- /dev/null
+++ b/isisd/isis_srv6.h
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This is an implementation of Segment Routing over IPv6 (SRv6) for IS-IS
+ * as per RFC 9352
+ * https://datatracker.ietf.org/doc/html/rfc9352
+ *
+ * Copyright (C) 2023 Carmine Scarpitta - University of Rome Tor Vergata
+ */
+
+#ifndef _FRR_ISIS_SRV6_H
+#define _FRR_ISIS_SRV6_H
+
+#include "lib/srv6.h"
+#include "isisd/isis_tlvs.h"
+
+#define ISIS_DEFAULT_SRV6_MAX_SEG_LEFT_MSD 3
+#define ISIS_DEFAULT_SRV6_MAX_END_POP_MSD 3
+#define ISIS_DEFAULT_SRV6_MAX_H_ENCAPS_MSD 2
+#define ISIS_DEFAULT_SRV6_MAX_END_D_MSD 5
+#define ISIS_DEFAULT_SRV6_IFNAME "sr0"
+
+/* SRv6 SID structure */
+struct isis_srv6_sid_structure {
+ uint8_t loc_block_len;
+ uint8_t loc_node_len;
+ uint8_t func_len;
+ uint8_t arg_len;
+};
+
+/* SRv6 SID not bound to any adjacency */
+struct isis_srv6_sid {
+ struct isis_srv6_sid *next;
+
+ /* SID flags */
+ uint8_t flags;
+
+ /* SID value */
+ struct in6_addr sid;
+
+ /* Endpoint behavior bound to the SID */
+ enum srv6_endpoint_behavior_codepoint behavior;
+
+ /* SRv6 SID structure */
+ struct isis_srv6_sid_structure structure;
+
+ /* Parent SRv6 locator */
+ struct srv6_locator_chunk *locator;
+
+ /* Backpointer to IS-IS area */
+ struct isis_area *area;
+};
+
+/* SRv6 Locator */
+struct isis_srv6_locator {
+ struct isis_srv6_locator *next;
+
+ uint32_t metric;
+
+ uint8_t flags;
+#define ISIS_SRV6_LOCATOR_FLAG_D 1 << 7
+
+ uint8_t algorithm;
+ struct prefix_ipv6 prefix;
+
+ struct list *srv6_sid;
+};
+
+/* SRv6 Adjacency-SID type */
+enum srv6_adj_type {
+ ISIS_SRV6_ADJ_NORMAL = 0,
+ ISIS_SRV6_LAN_BACKUP,
+};
+
+/* SRv6 Adjacency. */
+struct srv6_adjacency {
+ /* Adjacency type */
+ enum srv6_adj_type type;
+
+ /* SID flags */
+ uint8_t flags;
+
+ /* SID value */
+ struct in6_addr sid;
+
+ /* Endpoint behavior bound to the SID */
+ enum srv6_endpoint_behavior_codepoint behavior;
+
+ /* SRv6 SID structure */
+ struct isis_srv6_sid_structure structure;
+
+ /* Parent SRv6 locator */
+ struct srv6_locator_chunk *locator;
+
+ /* Adjacency-SID nexthop information */
+ struct in6_addr nexthop;
+
+ /* End.X SID TI-LFA backup nexthops */
+ struct list *backup_nexthops;
+
+ /* SRv6 (LAN) End.X SID Sub-TLV */
+ union {
+ struct isis_srv6_endx_sid_subtlv *endx_sid;
+ struct isis_srv6_lan_endx_sid_subtlv *lendx_sid;
+ } u;
+
+ /* Back pointer to IS-IS adjacency. */
+ struct isis_adjacency *adj;
+};
+
+/* Per-area IS-IS SRv6 Data Base (SRv6 DB) */
+struct isis_srv6_db {
+
+ /* List of SRv6 Locator chunks */
+ struct list *srv6_locator_chunks;
+
+ /* List of SRv6 SIDs allocated by the IS-IS instance */
+ struct list *srv6_sids;
+
+ /* List of SRv6 End.X SIDs allocated by the IS-IS instance */
+ struct list *srv6_endx_sids;
+
+ /* Area SRv6 configuration. */
+ struct {
+ /* Administrative status of SRv6 */
+ bool enabled;
+
+ /* Name of the SRv6 Locator */
+ char srv6_locator_name[SRV6_LOCNAME_SIZE];
+
+ /* Maximum Segments Left Depth supported by the router */
+ uint8_t max_seg_left_msd;
+
+ /* Maximum Maximum End Pop Depth supported by the router */
+ uint8_t max_end_pop_msd;
+
+ /* Maximum H.Encaps supported by the router */
+ uint8_t max_h_encaps_msd;
+
+ /* Maximum End D MSD supported by the router */
+ uint8_t max_end_d_msd;
+
+ /* Interface used for installing SRv6 SIDs into the data plane */
+ char srv6_ifname[IF_NAMESIZE];
+ } config;
+};
+
+bool isis_srv6_locator_unset(struct isis_area *area);
+
+void isis_srv6_interface_set(struct isis_area *area, const char *ifname);
+
+struct isis_srv6_sid *
+isis_srv6_sid_alloc(struct isis_area *area, struct srv6_locator_chunk *chunk,
+ enum srv6_endpoint_behavior_codepoint behavior,
+ int sid_func);
+extern void isis_srv6_sid_free(struct isis_srv6_sid *sid);
+
+extern void isis_srv6_area_init(struct isis_area *area);
+extern void isis_srv6_area_term(struct isis_area *area);
+
+void isis_srv6_init(void);
+void isis_srv6_term(void);
+
+void isis_srv6_sid_structure2subsubtlv(
+ const struct isis_srv6_sid *sid,
+ struct isis_srv6_sid_structure_subsubtlv *structure_subsubtlv);
+void isis_srv6_end_sid2subtlv(const struct isis_srv6_sid *sid,
+ struct isis_srv6_end_sid_subtlv *sid_subtlv);
+void isis_srv6_locator2tlv(const struct isis_srv6_locator *loc,
+ struct isis_srv6_locator_tlv *loc_tlv);
+
+void srv6_endx_sid_add_single(struct isis_adjacency *adj, bool backup,
+ struct list *nexthops);
+void srv6_endx_sid_add(struct isis_adjacency *adj);
+void srv6_endx_sid_del(struct srv6_adjacency *sra);
+struct srv6_adjacency *isis_srv6_endx_sid_find(struct isis_adjacency *adj,
+ enum srv6_adj_type type);
+void isis_area_delete_backup_srv6_endx_sids(struct isis_area *area, int level);
+
+int isis_srv6_ifp_up_notify(struct interface *ifp);
+
+#endif /* _FRR_ISIS_SRV6_H */
diff --git a/isisd/isis_te.c b/isisd/isis_te.c
new file mode 100644
index 0000000..90b53c5
--- /dev/null
+++ b/isisd/isis_te.c
@@ -0,0 +1,2147 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_te.c
+ *
+ * This is an implementation of RFC5305 & RFC 7810
+ *
+ * Author: Olivier Dugeon <olivier.dugeon@orange.com>
+ *
+ * Copyright (C) 2014 - 2019 Orange Labs http://www.orange.com
+ */
+
+#include <zebra.h>
+#include <math.h>
+
+#include "linklist.h"
+#include "frrevent.h"
+#include "vty.h"
+#include "stream.h"
+#include "memory.h"
+#include "log.h"
+#include "prefix.h"
+#include "command.h"
+#include "hash.h"
+#include "if.h"
+#include "vrf.h"
+#include "checksum.h"
+#include "md5.h"
+#include "sockunion.h"
+#include "network.h"
+#include "sbuf.h"
+#include "link_state.h"
+#include "lib/json.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_dynhn.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_spf.h"
+#include "isisd/isis_tlvs.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_zebra.h"
+
+DEFINE_MTYPE_STATIC(ISISD, ISIS_MPLS_TE, "ISIS MPLS_TE parameters");
+
+static void isis_mpls_te_circuit_ip_update(struct isis_circuit *circuit);
+
+/*------------------------------------------------------------------------*
+ * Following are control functions for MPLS-TE parameters management.
+ *------------------------------------------------------------------------*/
+
+/**
+ * Create MPLS Traffic Engineering structure which belongs to given area.
+ *
+ * @param area IS-IS Area
+ */
+void isis_mpls_te_create(struct isis_area *area)
+{
+ struct listnode *node;
+ struct isis_circuit *circuit;
+
+ if (!area)
+ return;
+
+ if (area->mta == NULL) {
+
+ struct mpls_te_area *new;
+
+ zlog_debug("ISIS-TE(%s): Initialize MPLS Traffic Engineering",
+ area->area_tag);
+
+ new = XCALLOC(MTYPE_ISIS_MPLS_TE, sizeof(struct mpls_te_area));
+
+ /* Initialize MPLS_TE structure */
+ new->status = enable;
+ new->level = 0;
+ new->inter_as = off;
+ new->interas_areaid.s_addr = 0;
+ new->router_id.s_addr = 0;
+ new->ted = ls_ted_new(1, "ISIS", 0);
+ if (!new->ted)
+ zlog_warn("Unable to create Link State Data Base");
+
+ area->mta = new;
+ } else {
+ area->mta->status = enable;
+ }
+
+ /* Initialize Link State Database */
+ if (area->mta->ted)
+ isis_te_init_ted(area);
+
+ /* Update Extended TLVs according to Interface link parameters
+ * and neighbor IP addresses
+ */
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
+ isis_link_params_update(circuit, circuit->interface);
+ isis_mpls_te_circuit_ip_update(circuit);
+ }
+}
+
+/**
+ * Disable MPLS Traffic Engineering structure which belongs to given area.
+ *
+ * @param area IS-IS Area
+ */
+void isis_mpls_te_disable(struct isis_area *area)
+{
+ struct listnode *node;
+ struct isis_circuit *circuit;
+
+ if (!area->mta)
+ return;
+
+ area->mta->status = disable;
+
+ /* Remove Link State Database */
+ ls_ted_clean(area->mta->ted);
+
+ /* Disable Extended SubTLVs on all circuit */
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
+ if (!IS_EXT_TE(circuit->ext))
+ continue;
+
+ /* disable MPLS_TE Circuit keeping SR one's */
+ if (IS_SUBTLV(circuit->ext, EXT_ADJ_SID))
+ circuit->ext->status = EXT_ADJ_SID;
+ else if (IS_SUBTLV(circuit->ext, EXT_LAN_ADJ_SID))
+ circuit->ext->status = EXT_LAN_ADJ_SID;
+ else
+ circuit->ext->status = 0;
+ }
+}
+
+void isis_mpls_te_term(struct isis_area *area)
+{
+ struct listnode *node;
+ struct isis_circuit *circuit;
+
+ if (!area->mta)
+ return;
+
+ zlog_info("TE(%s): Terminate MPLS TE", __func__);
+ /* Remove Link State Database */
+ ls_ted_del_all(&area->mta->ted);
+
+ /* Remove Extended SubTLVs */
+ zlog_info(" |- Remove Extended SubTLVS for all circuit");
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
+ zlog_info(" |- Call isis_del_ext_subtlvs()");
+ isis_del_ext_subtlvs(circuit->ext);
+ circuit->ext = NULL;
+ }
+
+ zlog_info(" |- Free MTA structure at %p", area->mta);
+ XFREE(MTYPE_ISIS_MPLS_TE, area->mta);
+}
+
+static void isis_link_params_update_asla(struct isis_circuit *circuit,
+ struct interface *ifp)
+{
+ struct isis_asla_subtlvs *asla;
+ struct listnode *node, *nnode;
+ struct isis_ext_subtlvs *ext = circuit->ext;
+ int i;
+
+ if (!HAS_LINK_PARAMS(ifp)) {
+ list_delete_all_node(ext->aslas);
+ return;
+ }
+
+#ifndef FABRICD
+ /* RFC 8919 Application Specific Link-Attributes
+ * is required by flex-algo application ISIS_SABM_FLAG_X
+ */
+ if (list_isempty(circuit->area->flex_algos->flex_algos))
+ isis_tlvs_free_asla(ext, ISIS_SABM_FLAG_X);
+ else
+ isis_tlvs_find_alloc_asla(ext, ISIS_SABM_FLAG_X);
+#endif /* ifndef FABRICD */
+
+ if (list_isempty(ext->aslas))
+ return;
+
+ for (ALL_LIST_ELEMENTS(ext->aslas, node, nnode, asla)) {
+ asla->legacy = circuit->area->asla_legacy_flag;
+ RESET_SUBTLV(asla);
+
+ if (asla->legacy)
+ continue;
+
+ /* Fulfill ASLA subTLVs from interface link parameters */
+ if (IS_PARAM_SET(ifp->link_params, LP_ADM_GRP)) {
+ asla->admin_group = ifp->link_params->admin_grp;
+ SET_SUBTLV(asla, EXT_ADM_GRP);
+ } else
+ UNSET_SUBTLV(asla, EXT_ADM_GRP);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_EXTEND_ADM_GRP)) {
+ admin_group_copy(&asla->ext_admin_group,
+ &ifp->link_params->ext_admin_grp);
+ SET_SUBTLV(asla, EXT_EXTEND_ADM_GRP);
+ } else
+ UNSET_SUBTLV(asla, EXT_EXTEND_ADM_GRP);
+
+ /* Send admin-group zero for better compatibility
+ * https://www.rfc-editor.org/rfc/rfc7308#section-2.3.2
+ */
+ if (circuit->area->admin_group_send_zero &&
+ !IS_SUBTLV(asla, EXT_ADM_GRP) &&
+ !IS_SUBTLV(asla, EXT_EXTEND_ADM_GRP)) {
+ asla->admin_group = 0;
+ SET_SUBTLV(asla, EXT_ADM_GRP);
+ admin_group_clear(&asla->ext_admin_group);
+ admin_group_allow_explicit_zero(&asla->ext_admin_group);
+ SET_SUBTLV(asla, EXT_EXTEND_ADM_GRP);
+ }
+
+ if (IS_PARAM_SET(ifp->link_params, LP_TE_METRIC)) {
+ asla->te_metric = ifp->link_params->te_metric;
+ SET_SUBTLV(asla, EXT_TE_METRIC);
+ } else
+ UNSET_SUBTLV(asla, EXT_TE_METRIC);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_DELAY)) {
+ asla->delay = ifp->link_params->av_delay;
+ SET_SUBTLV(asla, EXT_DELAY);
+ } else
+ UNSET_SUBTLV(asla, EXT_DELAY);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_MM_DELAY)) {
+ asla->min_delay = ifp->link_params->min_delay;
+ asla->max_delay = ifp->link_params->max_delay;
+ SET_SUBTLV(asla, EXT_MM_DELAY);
+ } else {
+ UNSET_SUBTLV(asla, EXT_MM_DELAY);
+ }
+
+ if (asla->standard_apps == ISIS_SABM_FLAG_X)
+ /* Flex-Algo ASLA does not need the following TE
+ * sub-TLVs
+ */
+ continue;
+
+ if (IS_PARAM_SET(ifp->link_params, LP_MAX_BW)) {
+ asla->max_bw = ifp->link_params->max_bw;
+ SET_SUBTLV(asla, EXT_MAX_BW);
+ } else
+ UNSET_SUBTLV(asla, EXT_MAX_BW);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_MAX_RSV_BW)) {
+ asla->max_rsv_bw = ifp->link_params->max_rsv_bw;
+ SET_SUBTLV(asla, EXT_MAX_RSV_BW);
+ } else
+ UNSET_SUBTLV(asla, EXT_MAX_RSV_BW);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_UNRSV_BW)) {
+ for (i = 0; i < MAX_CLASS_TYPE; i++)
+ asla->unrsv_bw[i] =
+ ifp->link_params->unrsv_bw[i];
+ SET_SUBTLV(asla, EXT_UNRSV_BW);
+ } else
+ UNSET_SUBTLV(asla, EXT_UNRSV_BW);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_DELAY_VAR)) {
+ asla->delay_var = ifp->link_params->delay_var;
+ SET_SUBTLV(asla, EXT_DELAY_VAR);
+ } else
+ UNSET_SUBTLV(asla, EXT_DELAY_VAR);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_PKT_LOSS)) {
+ asla->pkt_loss = ifp->link_params->pkt_loss;
+ SET_SUBTLV(asla, EXT_PKT_LOSS);
+ } else
+ UNSET_SUBTLV(asla, EXT_PKT_LOSS);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_RES_BW)) {
+ asla->res_bw = ifp->link_params->res_bw;
+ SET_SUBTLV(asla, EXT_RES_BW);
+ } else
+ UNSET_SUBTLV(asla, EXT_RES_BW);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_AVA_BW)) {
+ asla->ava_bw = ifp->link_params->ava_bw;
+ SET_SUBTLV(asla, EXT_AVA_BW);
+ } else
+ UNSET_SUBTLV(asla, EXT_AVA_BW);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_USE_BW)) {
+ asla->use_bw = ifp->link_params->use_bw;
+ SET_SUBTLV(asla, EXT_USE_BW);
+ } else
+ UNSET_SUBTLV(asla, EXT_USE_BW);
+ }
+
+
+ for (ALL_LIST_ELEMENTS(ext->aslas, node, nnode, asla)) {
+ if (!asla->legacy && NO_SUBTLV(asla) &&
+ admin_group_nb_words(&asla->ext_admin_group) == 0)
+ /* remove ASLA without info from the list of ASLAs to
+ * not send void ASLA
+ */
+ isis_tlvs_del_asla_flex_algo(ext, asla);
+ }
+}
+
+/* Main initialization / update function of the MPLS TE Circuit context */
+/* Call when interface TE Link parameters are modified */
+void isis_link_params_update(struct isis_circuit *circuit,
+ struct interface *ifp)
+{
+ int i;
+ struct prefix_ipv4 *addr;
+ struct prefix_ipv6 *addr6;
+ struct isis_ext_subtlvs *ext;
+
+ /* Check if TE is enable or not */
+ if (!circuit->area || !IS_MPLS_TE(circuit->area->mta))
+ return;
+
+ /* Sanity Check */
+ if ((ifp == NULL) || (circuit->state != C_STATE_UP))
+ return;
+
+ te_debug("ISIS-TE(%s): Update circuit parameters for interface %s",
+ circuit->area->area_tag, ifp->name);
+
+ /* Check if MPLS TE Circuit context has not been already created */
+ if (circuit->ext == NULL) {
+ circuit->ext = isis_alloc_ext_subtlvs();
+ te_debug(" |- Allocated new Ext-subTLVs for interface %s",
+ ifp->name);
+ }
+
+ ext = circuit->ext;
+
+ /* Fulfill Extended subTLVs from interface link parameters */
+ if (HAS_LINK_PARAMS(ifp)) {
+ /* STD_TE metrics */
+ if (IS_PARAM_SET(ifp->link_params, LP_ADM_GRP)) {
+ ext->adm_group = ifp->link_params->admin_grp;
+ SET_SUBTLV(ext, EXT_ADM_GRP);
+ } else
+ UNSET_SUBTLV(ext, EXT_ADM_GRP);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_EXTEND_ADM_GRP)) {
+ admin_group_copy(&ext->ext_admin_group,
+ &ifp->link_params->ext_admin_grp);
+ SET_SUBTLV(ext, EXT_EXTEND_ADM_GRP);
+ } else
+ UNSET_SUBTLV(ext, EXT_EXTEND_ADM_GRP);
+
+ /* Send admin-group zero for better compatibility
+ * https://www.rfc-editor.org/rfc/rfc7308#section-2.3.2
+ */
+ if (circuit->area->admin_group_send_zero &&
+ !IS_SUBTLV(ext, EXT_ADM_GRP) &&
+ !IS_SUBTLV(ext, EXT_EXTEND_ADM_GRP)) {
+ ext->adm_group = 0;
+ SET_SUBTLV(ext, EXT_ADM_GRP);
+ admin_group_clear(&ext->ext_admin_group);
+ admin_group_allow_explicit_zero(&ext->ext_admin_group);
+ SET_SUBTLV(ext, EXT_EXTEND_ADM_GRP);
+ }
+
+ /* If known, register local IPv4 addr from ip_addr list */
+ if (listcount(circuit->ip_addrs) != 0) {
+ addr = (struct prefix_ipv4 *)listgetdata(
+ (struct listnode *)listhead(circuit->ip_addrs));
+ IPV4_ADDR_COPY(&ext->local_addr, &addr->prefix);
+ SET_SUBTLV(ext, EXT_LOCAL_ADDR);
+ } else
+ UNSET_SUBTLV(ext, EXT_LOCAL_ADDR);
+
+ /* If known, register local IPv6 addr from ip_addr list */
+ if (listcount(circuit->ipv6_non_link) != 0) {
+ addr6 = (struct prefix_ipv6 *)listgetdata(
+ (struct listnode *)listhead(
+ circuit->ipv6_non_link));
+ IPV6_ADDR_COPY(&ext->local_addr6, &addr6->prefix);
+ SET_SUBTLV(ext, EXT_LOCAL_ADDR6);
+ } else
+ UNSET_SUBTLV(ext, EXT_LOCAL_ADDR6);
+
+ /*
+ * Remote IPv4 and IPv6 addresses are now added in
+ * isis_mpls_te_adj_ip_enabled() to get the right IP address
+ * in particular for IPv6 to get the global IPv6 address and
+ * not the link-local IPv6 address.
+ */
+
+ if (IS_PARAM_SET(ifp->link_params, LP_MAX_BW)) {
+ ext->max_bw = ifp->link_params->max_bw;
+ SET_SUBTLV(ext, EXT_MAX_BW);
+ } else
+ UNSET_SUBTLV(ext, EXT_MAX_BW);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_MAX_RSV_BW)) {
+ ext->max_rsv_bw = ifp->link_params->max_rsv_bw;
+ SET_SUBTLV(ext, EXT_MAX_RSV_BW);
+ } else
+ UNSET_SUBTLV(ext, EXT_MAX_RSV_BW);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_UNRSV_BW)) {
+ for (i = 0; i < MAX_CLASS_TYPE; i++)
+ ext->unrsv_bw[i] =
+ ifp->link_params->unrsv_bw[i];
+ SET_SUBTLV(ext, EXT_UNRSV_BW);
+ } else
+ UNSET_SUBTLV(ext, EXT_UNRSV_BW);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_TE_METRIC)) {
+ ext->te_metric = ifp->link_params->te_metric;
+ SET_SUBTLV(ext, EXT_TE_METRIC);
+ } else
+ UNSET_SUBTLV(ext, EXT_TE_METRIC);
+
+ /* TE metric extensions */
+ if (IS_PARAM_SET(ifp->link_params, LP_DELAY)) {
+ ext->delay = ifp->link_params->av_delay;
+ SET_SUBTLV(ext, EXT_DELAY);
+ } else
+ UNSET_SUBTLV(ext, EXT_DELAY);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_MM_DELAY)) {
+ ext->min_delay = ifp->link_params->min_delay;
+ ext->max_delay = ifp->link_params->max_delay;
+ SET_SUBTLV(ext, EXT_MM_DELAY);
+ } else
+ UNSET_SUBTLV(ext, EXT_MM_DELAY);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_DELAY_VAR)) {
+ ext->delay_var = ifp->link_params->delay_var;
+ SET_SUBTLV(ext, EXT_DELAY_VAR);
+ } else
+ UNSET_SUBTLV(ext, EXT_DELAY_VAR);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_PKT_LOSS)) {
+ ext->pkt_loss = ifp->link_params->pkt_loss;
+ SET_SUBTLV(ext, EXT_PKT_LOSS);
+ } else
+ UNSET_SUBTLV(ext, EXT_PKT_LOSS);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_RES_BW)) {
+ ext->res_bw = ifp->link_params->res_bw;
+ SET_SUBTLV(ext, EXT_RES_BW);
+ } else
+ UNSET_SUBTLV(ext, EXT_RES_BW);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_AVA_BW)) {
+ ext->ava_bw = ifp->link_params->ava_bw;
+ SET_SUBTLV(ext, EXT_AVA_BW);
+ } else
+ UNSET_SUBTLV(ext, EXT_AVA_BW);
+
+ if (IS_PARAM_SET(ifp->link_params, LP_USE_BW)) {
+ ext->use_bw = ifp->link_params->use_bw;
+ SET_SUBTLV(ext, EXT_USE_BW);
+ } else
+ UNSET_SUBTLV(ext, EXT_USE_BW);
+
+ /* INTER_AS */
+ if (IS_PARAM_SET(ifp->link_params, LP_RMT_AS)) {
+ ext->remote_as = ifp->link_params->rmt_as;
+ ext->remote_ip = ifp->link_params->rmt_ip;
+ SET_SUBTLV(ext, EXT_RMT_AS);
+ SET_SUBTLV(ext, EXT_RMT_IP);
+ } else {
+ /* reset inter-as TE params */
+ UNSET_SUBTLV(ext, EXT_RMT_AS);
+ UNSET_SUBTLV(ext, EXT_RMT_IP);
+ }
+ te_debug(" |- New MPLS-TE link parameters status 0x%x",
+ ext->status);
+ } else {
+ te_debug(" |- Reset Extended subTLVs status 0x%x",
+ ext->status);
+ /* Reset TE subTLVs keeping SR one's */
+ if (IS_SUBTLV(ext, EXT_ADJ_SID))
+ ext->status = EXT_ADJ_SID;
+ else if (IS_SUBTLV(ext, EXT_LAN_ADJ_SID))
+ ext->status = EXT_LAN_ADJ_SID;
+ else
+ ext->status = 0;
+ }
+
+ isis_link_params_update_asla(circuit, ifp);
+
+ return;
+}
+
+static int _isis_mpls_te_adj_ip_enabled(struct isis_adjacency *adj, int family,
+ bool global)
+{
+ struct isis_circuit *circuit;
+ struct isis_ext_subtlvs *ext;
+
+ circuit = adj->circuit;
+
+ /* Check that MPLS TE is enabled */
+ if (!IS_MPLS_TE(circuit->area->mta) || !circuit->ext)
+ return 0;
+
+ ext = circuit->ext;
+
+ /* Determine nexthop IP address */
+ switch (family) {
+ case AF_INET:
+ if (!circuit->ip_router || !adj->ipv4_address_count)
+ UNSET_SUBTLV(ext, EXT_NEIGH_ADDR);
+ else {
+ IPV4_ADDR_COPY(&ext->neigh_addr,
+ &adj->ipv4_addresses[0]);
+ SET_SUBTLV(ext, EXT_NEIGH_ADDR);
+ }
+ break;
+ case AF_INET6:
+ /* Nothing to do for link-local addresses - ie. not global.
+ * https://datatracker.ietf.org/doc/html/rfc6119#section-3.1.1
+ * Because the IPv6 traffic engineering TLVs present in LSPs are
+ * propagated across networks, they MUST NOT use link-local
+ * addresses.
+ */
+ if (!global)
+ return 0;
+
+ if (!circuit->ipv6_router || !adj->global_ipv6_count)
+ UNSET_SUBTLV(ext, EXT_NEIGH_ADDR6);
+ else {
+ IPV6_ADDR_COPY(&ext->neigh_addr6,
+ &adj->global_ipv6_addrs[0]);
+ SET_SUBTLV(ext, EXT_NEIGH_ADDR6);
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static int isis_mpls_te_adj_ip_enabled(struct isis_adjacency *adj, int family,
+ bool global)
+{
+ int ret;
+
+ /* Sanity Check */
+ if (!adj || !adj->circuit)
+ return 0;
+
+ ret = _isis_mpls_te_adj_ip_enabled(adj, family, global);
+
+ /* Update LSP */
+ lsp_regenerate_schedule(adj->circuit->area, adj->circuit->is_type, 0);
+
+ return ret;
+}
+
+static int _isis_mpls_te_adj_ip_disabled(struct isis_adjacency *adj, int family,
+ bool global)
+{
+ struct isis_circuit *circuit;
+ struct isis_ext_subtlvs *ext;
+
+ circuit = adj->circuit;
+
+ /* Check that MPLS TE is enabled */
+ if (!IS_MPLS_TE(circuit->area->mta) || !circuit->ext)
+ return 0;
+
+ ext = circuit->ext;
+
+ /* Update MPLS TE IP address parameters if possible */
+ if (!IS_MPLS_TE(circuit->area->mta) || !IS_EXT_TE(ext))
+ return 0;
+
+ /* Determine nexthop IP address */
+ switch (family) {
+ case AF_INET:
+ UNSET_SUBTLV(ext, EXT_NEIGH_ADDR);
+ break;
+ case AF_INET6:
+ if (global)
+ UNSET_SUBTLV(ext, EXT_NEIGH_ADDR6);
+ break;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static int isis_mpls_te_adj_ip_disabled(struct isis_adjacency *adj, int family,
+ bool global)
+{
+ int ret;
+
+ /* Sanity Check */
+ if (!adj || !adj->circuit || !adj->circuit->ext)
+ return 0;
+
+ ret = _isis_mpls_te_adj_ip_disabled(adj, family, global);
+
+ /* Update LSP */
+ lsp_regenerate_schedule(adj->circuit->area, adj->circuit->is_type, 0);
+
+ return ret;
+}
+
+static void isis_mpls_te_circuit_ip_update(struct isis_circuit *circuit)
+{
+ struct isis_adjacency *adj;
+
+ /* https://datatracker.ietf.org/doc/html/rfc6119#section-3.2.3
+ * This sub-TLV of the Extended IS Reachability TLV is used for point-
+ * to-point links
+ */
+ if (circuit->circ_type != CIRCUIT_T_P2P)
+ return;
+
+ adj = circuit->u.p2p.neighbor;
+
+ if (!adj)
+ return;
+
+ /* Nothing to do for link-local addresses.
+ * https://datatracker.ietf.org/doc/html/rfc6119#section-3.1.1
+ * Because the IPv6 traffic engineering TLVs present in LSPs are
+ * propagated across networks, they MUST NOT use link-local addresses.
+ */
+ if (adj->ipv4_address_count > 0)
+ _isis_mpls_te_adj_ip_enabled(adj, AF_INET, false);
+ else
+ _isis_mpls_te_adj_ip_disabled(adj, AF_INET, false);
+
+ if (adj->global_ipv6_count > 0)
+ _isis_mpls_te_adj_ip_enabled(adj, AF_INET6, true);
+ else
+ _isis_mpls_te_adj_ip_disabled(adj, AF_INET6, true);
+}
+
+
+int isis_mpls_te_update(struct interface *ifp)
+{
+ struct isis_circuit *circuit;
+ uint8_t rc = 1;
+
+ /* Sanity Check */
+ if (ifp == NULL)
+ return rc;
+
+ /* Get circuit context from interface */
+ circuit = circuit_scan_by_ifp(ifp);
+ if (circuit == NULL)
+ return rc;
+
+ /* Update TE TLVs ... */
+ isis_link_params_update(circuit, ifp);
+
+ /* ... and LSP */
+ if (circuit->area &&
+ (IS_MPLS_TE(circuit->area->mta)
+#ifndef FABRICD
+ || !list_isempty(circuit->area->flex_algos->flex_algos)
+#endif /* ifndef FABRICD */
+ ))
+ lsp_regenerate_schedule(circuit->area, circuit->is_type, 0);
+
+ rc = 0;
+ return rc;
+}
+
+
+/**
+ * Export Link State information to consumer daemon through ZAPI Link State
+ * Opaque Message.
+ *
+ * @param type Type of Link State Element i.e. Vertex, Edge or Subnet
+ * @param link_state Pointer to Link State Vertex, Edge or Subnet
+ *
+ * @return 0 if success, -1 otherwise
+ */
+static int isis_te_export(uint8_t type, void *link_state)
+{
+ struct ls_message msg = {};
+ int rc = 0;
+
+ switch (type) {
+ case LS_MSG_TYPE_NODE:
+ ls_vertex2msg(&msg, (struct ls_vertex *)link_state);
+ rc = ls_send_msg(zclient, &msg, NULL);
+ break;
+ case LS_MSG_TYPE_ATTRIBUTES:
+ ls_edge2msg(&msg, (struct ls_edge *)link_state);
+ rc = ls_send_msg(zclient, &msg, NULL);
+ break;
+ case LS_MSG_TYPE_PREFIX:
+ ls_subnet2msg(&msg, (struct ls_subnet *)link_state);
+ rc = ls_send_msg(zclient, &msg, NULL);
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * Parse LSP and build corresponding vertex. If vertex doesn't exist in the
+ * Link State Database it is created otherwise updated.
+ *
+ * @param ted Traffic Engineering Link State Database
+ * @param lsp IS-IS Link State PDU
+ *
+ * @return Link State Vertex or NULL in case of error
+ */
+static struct ls_vertex *lsp_to_vertex(struct ls_ted *ted, struct isis_lsp *lsp)
+{
+ struct ls_vertex *vertex = NULL;
+ struct ls_node *old, lnode = {};
+ struct isis_tlvs *tlvs;
+ const struct in_addr inaddr_any = {.s_addr = INADDR_ANY};
+
+ /* Sanity check */
+ if (!ted || !lsp)
+ return NULL;
+
+ /* Compute Link State Node ID from IS-IS sysID ... */
+ if (lsp->level == ISIS_LEVEL1)
+ lnode.adv.origin = ISIS_L1;
+ else
+ lnode.adv.origin = ISIS_L2;
+ memcpy(&lnode.adv.id.iso.sys_id, &lsp->hdr.lsp_id, ISIS_SYS_ID_LEN);
+ lnode.adv.id.iso.level = lsp->level;
+ /* ... and search the corresponding vertex */
+ vertex = ls_find_vertex_by_id(ted, lnode.adv);
+ /* Create a new one if not found */
+ if (!vertex) {
+ old = ls_node_new(lnode.adv, inaddr_any, in6addr_any);
+ old->type = STANDARD;
+ vertex = ls_vertex_add(ted, old);
+ }
+ old = vertex->node;
+ te_debug(" |- %s Vertex (%" PRIu64 ") for node %s",
+ vertex->status == NEW ? "Create" : "Found", vertex->key,
+ print_sys_hostname(old->adv.id.iso.sys_id));
+
+ /* Fulfill Link State Node information */
+ tlvs = lsp->tlvs;
+ if (tlvs) {
+ if (tlvs->te_router_id) {
+ IPV4_ADDR_COPY(&lnode.router_id, tlvs->te_router_id);
+ SET_FLAG(lnode.flags, LS_NODE_ROUTER_ID);
+ }
+ if (tlvs->te_router_id_ipv6) {
+ IPV6_ADDR_COPY(&lnode.router_id6,
+ tlvs->te_router_id_ipv6);
+ SET_FLAG(lnode.flags, LS_NODE_ROUTER_ID6);
+ }
+ if (tlvs->hostname) {
+ strlcpy(lnode.name, tlvs->hostname, MAX_NAME_LENGTH);
+ SET_FLAG(lnode.flags, LS_NODE_NAME);
+ }
+ if (tlvs->router_cap) {
+ struct isis_router_cap *cap = tlvs->router_cap;
+
+ if (cap->srgb.lower_bound != 0
+ && cap->srgb.range_size != 0) {
+ SET_FLAG(lnode.flags, LS_NODE_SR);
+ lnode.srgb.flag = cap->srgb.flags;
+ lnode.srgb.lower_bound = cap->srgb.lower_bound;
+ lnode.srgb.range_size = cap->srgb.range_size;
+ for (int i = 0; i < LIB_LS_SR_ALGO_COUNT; i++)
+ lnode.algo[i] = cap->algo[i];
+ }
+
+ if (cap->srlb.lower_bound != 0
+ && cap->srlb.range_size != 0) {
+ lnode.srlb.lower_bound = cap->srlb.lower_bound;
+ lnode.srlb.range_size = cap->srlb.range_size;
+ SET_FLAG(lnode.flags, LS_NODE_SRLB);
+ }
+ if (cap->msd != 0) {
+ lnode.msd = cap->msd;
+ SET_FLAG(lnode.flags, LS_NODE_MSD);
+ }
+ }
+ }
+
+ /* Update Link State Node information */
+ if (!ls_node_same(old, &lnode)) {
+ te_debug(" |- Update Link State Node information");
+ memcpy(old, &lnode, sizeof(struct ls_node));
+ if (vertex->status != NEW)
+ vertex->status = UPDATE;
+ }
+
+ /* Set self TED vertex if LSP corresponds to the own router */
+ if (lsp->own_lsp)
+ ted->self = vertex;
+
+ return vertex;
+}
+
+/**
+ * Get Link State Edge from Link State Attributes in TE Database.
+ * Edge structure is dynamically allocated and fulfill with Link State
+ * Attributes if not found.
+ *
+ * @param ted Link State Database
+ * @param attr Link State Attributes
+ *
+ * @return New Link State Edge if success, NULL otherwise
+ */
+static struct ls_edge *get_edge(struct ls_ted *ted, struct ls_attributes *attr)
+{
+ struct ls_edge *edge;
+ struct ls_standard *std;
+ struct ls_edge_key key;
+
+ /* Check parameters */
+ if (!ted || !attr)
+ return NULL;
+
+ std = &attr->standard;
+
+ /* Compute keys in function of local address (IPv4/v6) or identifier */
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)) {
+ key.family = AF_INET;
+ IPV4_ADDR_COPY(&key.k.addr, &std->local);
+ } else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)) {
+ key.family = AF_INET6;
+ IPV6_ADDR_COPY(&key.k.addr6, &std->local6);
+ } else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID)) {
+ key.family = AF_LOCAL;
+ key.k.link_id = (((uint64_t)std->local_id) & 0xffffffff) |
+ ((uint64_t)std->remote_id << 32);
+ } else {
+ key.family = AF_UNSPEC;
+ }
+
+ /* Stop here if we don't got a valid key */
+ if (key.family == AF_UNSPEC)
+ return NULL;
+
+ /* Get corresponding Edge by key from Link State Data Base */
+ edge = ls_find_edge_by_key(ted, key);
+
+ /* and create new one if not exist */
+ if (!edge) {
+ edge = ls_edge_add(ted, attr);
+ /*
+ * Edge could be Null if no local ID is found in Attributes.
+ * Stop the processing as without any local ID it is not
+ * possible to store Edge in the TED.
+ */
+ if (!edge)
+ return NULL;
+ }
+
+ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR))
+ te_debug(" |- %s Edge (%pI4) from Extended Reach. %pI4",
+ edge->status == NEW ? "Create" : "Found",
+ &edge->key.k.addr, &attr->standard.local);
+ else if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR6))
+ te_debug(" |- %s Edge (%pI6) from Extended Reach. %pI6",
+ edge->status == NEW ? "Create" : "Found",
+ &edge->key.k.addr6, &attr->standard.local6);
+ else
+ te_debug(" |- %s Edge (%" PRIu64 ")",
+ edge->status == NEW ? "Create" : "Found",
+ edge->key.k.link_id);
+
+ return edge;
+}
+
+/**
+ * Get Link State Attributes from IS-IS Sub-TLVs. Structure is dynamically
+ * allocated and should be free once not use anymore.
+ *
+ * @param adv Link State Node ID
+ * @param tlvs IS-IS Sub TLVs
+ *
+ * @return New Link State attributes if success, NULL otherwise
+ */
+static struct ls_attributes *get_attributes(struct ls_node_id adv,
+ struct isis_ext_subtlvs *tlvs)
+{
+ struct ls_attributes *attr;
+ struct in_addr local = {.s_addr = INADDR_ANY};
+ struct in6_addr local6 = in6addr_any;
+ uint32_t local_id = 0;
+
+ /* Got Local identifier */
+ if (CHECK_FLAG(tlvs->status, EXT_LOCAL_ADDR))
+ local.s_addr = tlvs->local_addr.s_addr;
+
+ if (CHECK_FLAG(tlvs->status, EXT_LOCAL_ADDR6))
+ memcpy(&local6, &tlvs->local_addr6, IPV6_MAX_BYTELEN);
+
+ if (CHECK_FLAG(tlvs->status, EXT_LLRI))
+ local_id = tlvs->local_llri;
+
+ /* Create LS Attributes */
+ attr = ls_attributes_new(adv, local, local6, local_id);
+ if (!attr)
+ return NULL;
+
+ /* Browse sub-TLV and fulfill Link State Attributes */
+ if (CHECK_FLAG(tlvs->status, EXT_ADM_GRP)) {
+ attr->standard.admin_group = tlvs->adm_group;
+ SET_FLAG(attr->flags, LS_ATTR_ADM_GRP);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_EXTEND_ADM_GRP)) {
+ admin_group_copy(&attr->ext_admin_group,
+ &tlvs->ext_admin_group);
+ SET_FLAG(attr->flags, LS_ATTR_EXT_ADM_GRP);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_LLRI)) {
+ attr->standard.local_id = tlvs->local_llri;
+ attr->standard.remote_id = tlvs->remote_llri;
+ SET_FLAG(attr->flags, LS_ATTR_LOCAL_ID);
+ SET_FLAG(attr->flags, LS_ATTR_NEIGH_ID);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_NEIGH_ADDR)) {
+ attr->standard.remote.s_addr = tlvs->neigh_addr.s_addr;
+ SET_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_NEIGH_ADDR6)) {
+ memcpy(&attr->standard.remote6, &tlvs->neigh_addr6,
+ IPV6_MAX_BYTELEN);
+ SET_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_MAX_BW)) {
+ attr->standard.max_bw = tlvs->max_bw;
+ SET_FLAG(attr->flags, LS_ATTR_MAX_BW);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_MAX_RSV_BW)) {
+ attr->standard.max_rsv_bw = tlvs->max_rsv_bw;
+ SET_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_UNRSV_BW)) {
+ memcpy(&attr->standard.unrsv_bw, tlvs->unrsv_bw,
+ ISIS_SUBTLV_UNRSV_BW_SIZE);
+ SET_FLAG(attr->flags, LS_ATTR_UNRSV_BW);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_TE_METRIC)) {
+ attr->standard.te_metric = tlvs->te_metric;
+ SET_FLAG(attr->flags, LS_ATTR_TE_METRIC);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_RMT_AS)) {
+ attr->standard.remote_as = tlvs->remote_as;
+ SET_FLAG(attr->flags, LS_ATTR_REMOTE_AS);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_RMT_IP)) {
+ attr->standard.remote_addr = tlvs->remote_ip;
+ SET_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_DELAY)) {
+ attr->extended.delay = tlvs->delay;
+ SET_FLAG(attr->flags, LS_ATTR_DELAY);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_MM_DELAY)) {
+ attr->extended.min_delay = tlvs->min_delay;
+ attr->extended.max_delay = tlvs->max_delay;
+ SET_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_DELAY_VAR)) {
+ attr->extended.jitter = tlvs->delay_var;
+ SET_FLAG(attr->flags, LS_ATTR_JITTER);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_PKT_LOSS)) {
+ attr->extended.pkt_loss = tlvs->pkt_loss;
+ SET_FLAG(attr->flags, LS_ATTR_PACKET_LOSS);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_AVA_BW)) {
+ attr->extended.ava_bw = tlvs->ava_bw;
+ SET_FLAG(attr->flags, LS_ATTR_AVA_BW);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_RES_BW)) {
+ attr->extended.rsv_bw = tlvs->res_bw;
+ SET_FLAG(attr->flags, LS_ATTR_RSV_BW);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_USE_BW)) {
+ attr->extended.used_bw = tlvs->use_bw;
+ SET_FLAG(attr->flags, LS_ATTR_USE_BW);
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_ADJ_SID)) {
+ struct isis_adj_sid *adj =
+ (struct isis_adj_sid *)tlvs->adj_sid.head;
+ int i;
+ for (; adj; adj = adj->next) {
+ i = adj->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG ? 1 : 0;
+ i += adj->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG ? 2 : 0;
+ attr->adj_sid[i].flags = adj->flags;
+ attr->adj_sid[i].weight = adj->weight;
+ attr->adj_sid[i].sid = adj->sid;
+ switch (i) {
+ case ADJ_PRI_IPV4:
+ SET_FLAG(attr->flags, LS_ATTR_ADJ_SID);
+ break;
+ case ADJ_BCK_IPV4:
+ SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID);
+ break;
+ case ADJ_PRI_IPV6:
+ SET_FLAG(attr->flags, LS_ATTR_ADJ_SID6);
+ break;
+ case ADJ_BCK_IPV6:
+ SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6);
+ break;
+ }
+ }
+ }
+ if (CHECK_FLAG(tlvs->status, EXT_LAN_ADJ_SID)) {
+ struct isis_lan_adj_sid *ladj =
+ (struct isis_lan_adj_sid *)tlvs->lan_sid.head;
+ int i;
+ for (; ladj; ladj = ladj->next) {
+ i = ladj->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG ? 1 : 0;
+ i += ladj->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG ? 2 : 0;
+ attr->adj_sid[i].flags = ladj->flags;
+ attr->adj_sid[i].weight = ladj->weight;
+ attr->adj_sid[i].sid = ladj->sid;
+ memcpy(&attr->adj_sid[i].neighbor.sysid,
+ &ladj->neighbor_id, ISIS_SYS_ID_LEN);
+ switch (i) {
+ case ADJ_PRI_IPV4:
+ SET_FLAG(attr->flags, LS_ATTR_ADJ_SID);
+ break;
+ case ADJ_BCK_IPV4:
+ SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID);
+ break;
+ case ADJ_PRI_IPV6:
+ SET_FLAG(attr->flags, LS_ATTR_ADJ_SID6);
+ break;
+ case ADJ_BCK_IPV6:
+ SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6);
+ break;
+ }
+ }
+ }
+
+ return attr;
+}
+
+/**
+ * Parse Extended Reachability TLVs and create or update the corresponding
+ * Link State Edge and Attributes. Vertex connections are also updated if
+ * needed based on the remote IP address of the Edge and existing reverse Edge.
+ *
+ * @param id ID of Extended IS
+ * @param metric Metric of the link
+ * @param old_metric Boolean that indicate if it is an old metric (no TE)
+ * @param tlvs SubTlvs that contains TE information
+ * @param arg IS-IS TE argument (TED, Vertex, and export indication)
+ *
+ * @return 0 if success, -1 otherwise
+ */
+static int lsp_to_edge_cb(const uint8_t *id, uint32_t metric, bool old_metric,
+ struct isis_ext_subtlvs *tlvs, void *arg)
+{
+ struct isis_te_args *args = (struct isis_te_args *)arg;
+ struct ls_vertex *vertex;
+ struct ls_edge *edge, *dst;
+ struct ls_attributes *attr;
+
+ te_debug(" |- Process Extended IS for %pSY", id);
+
+ /* Check parameters */
+ if (old_metric || !args || !tlvs)
+ return LSP_ITER_CONTINUE;
+
+ /* Initialize Link State Attributes */
+ vertex = args->vertex;
+ attr = get_attributes(vertex->node->adv, tlvs);
+ /*
+ * Attributes may be Null if no local ID has been found in the LSP.
+ * Stop processing here as without any local ID it is not possible to
+ * create corresponding Edge in the TED.
+ */
+ if (!attr)
+ return LSP_ITER_CONTINUE;
+
+ attr->metric = metric;
+ SET_FLAG(attr->flags, LS_ATTR_METRIC);
+
+ /* Get corresponding Edge from Link State Data Base */
+ edge = get_edge(args->ted, attr);
+ /*
+ * Edge could be Null if no local ID has been found in Attributes.
+ * Stop processing here as without any local ID it is not possible to
+ * create corresponding Edge in the TED.
+ */
+ if (!edge) {
+ ls_attributes_del(attr);
+ return LSP_ITER_CONTINUE;
+ }
+
+ /* Update Attribute fields if there are different */
+ if (edge->status != NEW) {
+ if (!ls_attributes_same(edge->attributes, attr)) {
+ te_debug(" |- Update Edge Attributes information");
+ ls_attributes_del(edge->attributes);
+ edge->attributes = attr;
+ edge->status = UPDATE;
+ } else {
+ if (edge->attributes != attr)
+ ls_attributes_del(attr);
+ edge->status = SYNC;
+ }
+ }
+
+ /* Try to update remote Link from remote address or reachability ID */
+ if (edge->key.family == AF_INET)
+ te_debug(" |- Link Edge (%pI4) to destination vertex (%s)",
+ &edge->key.k.addr, print_sys_hostname(id));
+ else if (edge->key.family == AF_INET6)
+ te_debug(" |- Link Edge (%pI6) to destination vertex (%s)",
+ &edge->key.k.addr6, print_sys_hostname(id));
+ else if (edge->key.family == AF_LOCAL)
+ te_debug(" |- Link Edge (%" PRIu64
+ ") to destination vertex (%s)",
+ edge->key.k.link_id, print_sys_hostname(id));
+ else
+ te_debug(
+ " |- Link Edge (Unknown) to destination vertex (%s)",
+ print_sys_hostname(id));
+
+ dst = ls_find_edge_by_destination(args->ted, edge->attributes);
+ if (dst) {
+ /* Attach remote link if not set */
+ if (edge->source && dst->destination == NULL) {
+ vertex = edge->source;
+ if (vertex->incoming_edges)
+ listnode_add_sort_nodup(vertex->incoming_edges,
+ dst);
+ dst->destination = vertex;
+ }
+ /* and destination vertex to this edge if not set */
+ if (dst->source && edge->destination == NULL) {
+ vertex = dst->source;
+ if (vertex->incoming_edges)
+ listnode_add_sort_nodup(vertex->incoming_edges,
+ edge);
+ edge->destination = vertex;
+ }
+ } else {
+ /* Search dst. Vertex by Extended Reach. ID if not found */
+ if (edge->destination == NULL) {
+ vertex = ls_find_vertex_by_key(args->ted,
+ sysid_to_key(id));
+ if (vertex && vertex->incoming_edges)
+ listnode_add_sort_nodup(vertex->incoming_edges,
+ edge);
+ edge->destination = vertex;
+ }
+ }
+
+ /* Update status and Export Link State Edge if needed */
+ if (edge->status != SYNC) {
+ if (args->export)
+ isis_te_export(LS_MSG_TYPE_ATTRIBUTES, edge);
+ edge->status = SYNC;
+ }
+
+ return LSP_ITER_CONTINUE;
+}
+
+/**
+ * Parse Extended IP Reachability or MT IPv6 Reachability TLVs and create or
+ * update the corresponding Link State Subnet and Prefix.
+ *
+ * @param prefix Prefix associated to this subnet
+ * @param metric Metric of this prefix
+ * @param external Boolean to indicate if the prefix is external
+ * @param subtlvs Subtlvs if any (mostly Segment Routing ID)
+ * @param arg IS-IS TE argument (TED, Vertex, and export indication)
+ *
+ * @return 0 if success, -1 otherwise
+ */
+static int lsp_to_subnet_cb(const struct prefix *prefix, uint32_t metric,
+ bool external, struct isis_subtlvs *subtlvs,
+ void *arg)
+{
+ struct isis_te_args *args = (struct isis_te_args *)arg;
+ struct ls_vertex *vertex;
+ struct ls_subnet *subnet;
+ struct ls_prefix *ls_pref;
+ struct listnode *node;
+ struct ls_edge *edge;
+ struct ls_standard *std = NULL;
+ struct prefix p;
+
+ /* Sanity Check */
+ if (!args || !prefix)
+ return LSP_ITER_CONTINUE;
+
+ te_debug(" |- Process Extended %s Reachability %pFX",
+ prefix->family == AF_INET ? "IP" : "IPv6", prefix);
+
+ vertex = args->vertex;
+
+ /*
+ * Prefix with mask different from /32 or /128 are advertised by at
+ * least 2 nodes. To avoid subnet attached to undetermined vertex, and
+ * gives the possibility to send the information to client e.g. BGP for
+ * Link State advertisement, we adjust the prefix with the corresponding
+ * IP address of the belonging interface when it is available. Other
+ * prefixes are kept unchanged.
+ */
+ if (prefix->family == AF_INET && prefix->prefixlen < IPV4_MAX_BITLEN) {
+ std = NULL;
+ for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) {
+ if (!CHECK_FLAG(edge->attributes->flags,
+ LS_ATTR_LOCAL_ADDR))
+ continue;
+
+ p.u.prefix4 = edge->attributes->standard.local;
+ p.family = AF_INET;
+ p.prefixlen = prefix->prefixlen;
+ apply_mask_ipv4((struct prefix_ipv4 *)&p);
+ if (IPV4_ADDR_SAME(&p.u.prefix4, &prefix->u.prefix4)) {
+ std = &edge->attributes->standard;
+ break;
+ }
+ }
+ if (std)
+ p.u.prefix4 = std->local;
+
+ } else if (prefix->family == AF_INET6
+ && prefix->prefixlen < IPV6_MAX_BITLEN) {
+ std = NULL;
+ for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) {
+ if (!CHECK_FLAG(edge->attributes->flags,
+ LS_ATTR_LOCAL_ADDR6))
+ continue;
+
+ p.u.prefix6 = edge->attributes->standard.local6;
+ p.family = AF_INET6;
+ p.prefixlen = prefix->prefixlen;
+ apply_mask_ipv6((struct prefix_ipv6 *)&p);
+ if (IPV6_ADDR_SAME(&p.u.prefix6, &prefix->u.prefix6)) {
+ std = &edge->attributes->standard;
+ break;
+ }
+ }
+ if (std)
+ p.u.prefix6 = std->local6;
+ }
+ if (!std)
+ prefix_copy(&p, prefix);
+ else {
+ /* Remove old subnet if any before prefix adjustment */
+ subnet = ls_find_subnet(args->ted, prefix);
+ if (subnet) {
+ if (args->export) {
+ subnet->status = DELETE;
+ isis_te_export(LS_MSG_TYPE_PREFIX, subnet);
+ }
+ te_debug(" |- Remove subnet with prefix %pFX",
+ &subnet->key);
+ ls_subnet_del_all(args->ted, subnet);
+ }
+ te_debug(" |- Adjust prefix %pFX with local address to: %pFX",
+ prefix, &p);
+ }
+
+ /* Search existing Subnet in TED ... */
+ subnet = ls_find_subnet(args->ted, &p);
+ /* ... and create a new Subnet if not found */
+ if (!subnet) {
+ ls_pref = ls_prefix_new(vertex->node->adv, &p);
+ subnet = ls_subnet_add(args->ted, ls_pref);
+ /* Stop processing if we are unable to create a new subnet */
+ if (!subnet)
+ return LSP_ITER_CONTINUE;
+ }
+ ls_pref = subnet->ls_pref;
+
+ te_debug(" |- %s Subnet from prefix %pFX",
+ subnet->status == NEW ? "Create" : "Found", &p);
+
+ /* Update Metric */
+ if (!CHECK_FLAG(ls_pref->flags, LS_PREF_METRIC)
+ || (ls_pref->metric != metric)) {
+ ls_pref->metric = metric;
+ SET_FLAG(ls_pref->flags, LS_PREF_METRIC);
+ if (subnet->status != NEW)
+ subnet->status = UPDATE;
+ } else {
+ if (subnet->status == ORPHAN)
+ subnet->status = SYNC;
+ }
+
+ /* Update Prefix SID if any */
+ if (subtlvs && subtlvs->prefix_sids.count != 0) {
+ struct isis_prefix_sid *psid;
+ struct ls_sid sr = {};
+
+ psid = (struct isis_prefix_sid *)subtlvs->prefix_sids.head;
+ sr.algo = psid->algorithm;
+ sr.sid_flag = psid->flags;
+ sr.sid = psid->value;
+
+ if (!CHECK_FLAG(ls_pref->flags, LS_PREF_SR)
+ || !memcmp(&ls_pref->sr, &sr, sizeof(struct ls_sid))) {
+ memcpy(&ls_pref->sr, &sr, sizeof(struct ls_sid));
+ SET_FLAG(ls_pref->flags, LS_PREF_SR);
+ if (subnet->status != NEW)
+ subnet->status = UPDATE;
+ } else {
+ if (subnet->status == ORPHAN)
+ subnet->status = SYNC;
+ }
+ } else {
+ if (CHECK_FLAG(ls_pref->flags, LS_PREF_SR)) {
+ UNSET_FLAG(ls_pref->flags, LS_PREF_SR);
+ if (subnet->status != NEW)
+ subnet->status = UPDATE;
+ } else {
+ if (subnet->status == ORPHAN)
+ subnet->status = SYNC;
+ }
+ }
+
+ /* Update status and Export Link State Edge if needed */
+ if (subnet->status != SYNC) {
+ if (args->export)
+ isis_te_export(LS_MSG_TYPE_PREFIX, subnet);
+ subnet->status = SYNC;
+ }
+
+ return LSP_ITER_CONTINUE;
+}
+
+/**
+ * Parse ISIS LSP to fulfill the Link State Database
+ *
+ * @param ted Link State Database
+ * @param lsp ISIS Link State PDU
+ */
+static void isis_te_parse_lsp(struct mpls_te_area *mta, struct isis_lsp *lsp)
+{
+ struct ls_ted *ted;
+ struct ls_vertex *vertex;
+ struct ls_edge *edge;
+ struct ls_subnet *subnet;
+ struct listnode *node;
+ struct isis_te_args args;
+
+ /* Sanity Check */
+ if (!IS_MPLS_TE(mta) || !mta->ted || !lsp)
+ return;
+
+ ted = mta->ted;
+
+ te_debug("ISIS-TE(%s): Parse LSP %pSY", lsp->area->area_tag,
+ lsp->hdr.lsp_id);
+
+ /* First parse LSP to obtain the corresponding Vertex */
+ vertex = lsp_to_vertex(ted, lsp);
+ if (!vertex) {
+ zlog_warn("Unable to build Vertex from LSP %pSY. Abort!",
+ lsp->hdr.lsp_id);
+ return;
+ }
+
+ /* Check if Vertex has been modified */
+ if (vertex->status != SYNC) {
+ /* Vertex is out of sync: export it if requested */
+ if (IS_EXPORT_TE(mta))
+ isis_te_export(LS_MSG_TYPE_NODE, vertex);
+ vertex->status = SYNC;
+ }
+
+ /* Mark outgoing Edges and Subnets as ORPHAN to detect deletion */
+ for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge))
+ edge->status = ORPHAN;
+
+ for (ALL_LIST_ELEMENTS_RO(vertex->prefixes, node, subnet))
+ subnet->status = ORPHAN;
+
+ /* Process all Extended Reachability in LSP (all fragments) */
+ args.ted = ted;
+ args.vertex = vertex;
+ args.export = mta->export;
+ isis_lsp_iterate_is_reach(lsp, ISIS_MT_IPV4_UNICAST, lsp_to_edge_cb,
+ &args);
+
+ isis_lsp_iterate_is_reach(lsp, ISIS_MT_IPV6_UNICAST, lsp_to_edge_cb,
+ &args);
+
+ /* Process all Extended IP (v4 & v6) in LSP (all fragments) */
+ isis_lsp_iterate_ip_reach(lsp, AF_INET, ISIS_MT_IPV4_UNICAST,
+ lsp_to_subnet_cb, &args);
+ isis_lsp_iterate_ip_reach(lsp, AF_INET6, ISIS_MT_IPV6_UNICAST,
+ lsp_to_subnet_cb, &args);
+ isis_lsp_iterate_ip_reach(lsp, AF_INET6, ISIS_MT_IPV4_UNICAST,
+ lsp_to_subnet_cb, &args);
+
+ /* Clean remaining Orphan Edges or Subnets */
+ if (IS_EXPORT_TE(mta))
+ ls_vertex_clean(ted, vertex, zclient);
+ else
+ ls_vertex_clean(ted, vertex, NULL);
+}
+
+/**
+ * Delete Link State Database Vertex, Edge & Prefix that correspond to this
+ * ISIS Link State PDU
+ *
+ * @param ted Link State Database
+ * @param lsp ISIS Link State PDU
+ */
+static void isis_te_delete_lsp(struct mpls_te_area *mta, struct isis_lsp *lsp)
+{
+ struct ls_ted *ted;
+ struct ls_vertex *vertex = NULL;
+ struct ls_node lnode = {};
+ struct ls_edge *edge;
+ struct ls_subnet *subnet;
+ struct listnode *nnode, *node;
+
+ /* Sanity Check */
+ if (!IS_MPLS_TE(mta) || !mta->ted || !lsp)
+ return;
+
+ te_debug("ISIS-TE(%s): Delete Link State TED objects from LSP %pSY",
+ lsp->area->area_tag, lsp->hdr.lsp_id);
+
+ /* Compute Link State Node ID from IS-IS sysID ... */
+ if (lsp->level == ISIS_LEVEL1)
+ lnode.adv.origin = ISIS_L1;
+ else
+ lnode.adv.origin = ISIS_L2;
+ memcpy(&lnode.adv.id.iso.sys_id, &lsp->hdr.lsp_id, ISIS_SYS_ID_LEN);
+ lnode.adv.id.iso.level = lsp->level;
+ ted = mta->ted;
+ /* ... and search the corresponding vertex */
+ vertex = ls_find_vertex_by_id(ted, lnode.adv);
+ if (!vertex)
+ return;
+
+ te_debug(" |- Delete Vertex %s", vertex->node->name);
+
+ /*
+ * We can't use the ls_vertex_del_all() function if export TE is set,
+ * as we must first advertise the client daemons of each removal.
+ */
+ /* Remove outgoing Edges */
+ for (ALL_LIST_ELEMENTS(vertex->outgoing_edges, node, nnode, edge)) {
+ if (IS_EXPORT_TE(mta)) {
+ edge->status = DELETE;
+ isis_te_export(LS_MSG_TYPE_ATTRIBUTES, edge);
+ }
+ ls_edge_del_all(ted, edge);
+ }
+
+ /* Disconnect incoming Edges */
+ for (ALL_LIST_ELEMENTS(vertex->incoming_edges, node, nnode, edge)) {
+ ls_disconnect(vertex, edge, false);
+ if (edge->source == NULL) {
+ if (IS_EXPORT_TE(mta)) {
+ edge->status = DELETE;
+ isis_te_export(LS_MSG_TYPE_ATTRIBUTES, edge);
+ }
+ ls_edge_del_all(ted, edge);
+ }
+ }
+
+ /* Remove subnets */
+ for (ALL_LIST_ELEMENTS(vertex->prefixes, node, nnode, subnet)) {
+ if (IS_EXPORT_TE(mta)) {
+ subnet->status = DELETE;
+ isis_te_export(LS_MSG_TYPE_PREFIX, subnet);
+ }
+ ls_subnet_del_all(ted, subnet);
+ }
+
+ /* Then remove Link State Node */
+ if (IS_EXPORT_TE(mta)) {
+ vertex->status = DELETE;
+ isis_te_export(LS_MSG_TYPE_NODE, vertex);
+ }
+ ls_node_del(vertex->node);
+
+ /* Finally, remove Vertex */
+ ls_vertex_del(ted, vertex);
+}
+
+/**
+ * Process ISIS LSP according to the event to add, update or remove
+ * corresponding vertex, edge and prefix in the Link State database.
+ * Since LSP could be fragmented, the function starts by searching the root LSP
+ * to retrieve the complete LSP, including eventual fragment before processing
+ * all of them.
+ *
+ * @param lsp ISIS Link State PDU
+ * @param event LSP event: ADD, UPD, INC & DEL (TICK are ignored)
+ *
+ */
+void isis_te_lsp_event(struct isis_lsp *lsp, enum lsp_event event)
+{
+ struct isis_area *area;
+ struct isis_lsp *lsp0;
+
+ /* Sanity check */
+ if (!lsp || !lsp->area)
+ return;
+
+ area = lsp->area;
+ if (!IS_MPLS_TE(area->mta))
+ return;
+
+ /* Adjust LSP0 in case of fragment */
+ if (LSP_FRAGMENT(lsp->hdr.lsp_id))
+ lsp0 = lsp->lspu.zero_lsp;
+ else
+ lsp0 = lsp;
+
+ /* Then process event */
+ switch (event) {
+ case LSP_ADD:
+ case LSP_UPD:
+ case LSP_INC:
+ isis_te_parse_lsp(area->mta, lsp0);
+ break;
+ case LSP_DEL:
+ isis_te_delete_lsp(area->mta, lsp0);
+ break;
+ case LSP_UNKNOWN:
+ case LSP_TICK:
+ break;
+ }
+}
+
+/**
+ * Send the whole Link State Traffic Engineering Database to the consumer that
+ * request it through a ZAPI Link State Synchronous Opaque Message.
+ *
+ * @param info ZAPI Opaque message
+ *
+ * @return 0 if success, -1 otherwise
+ */
+int isis_te_sync_ted(struct zapi_opaque_reg_info dst)
+{
+ struct listnode *node, *inode;
+ struct isis *isis;
+ struct isis_area *area;
+ struct mpls_te_area *mta;
+ int rc = -1;
+
+ te_debug("ISIS-TE(%s): Received TED synchro from client %d", __func__,
+ dst.proto);
+ /* For each area, send TED if TE distribution is enabled */
+ for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ mta = area->mta;
+ if (IS_MPLS_TE(mta) && IS_EXPORT_TE(mta)) {
+ te_debug(" |- Export TED from area %s",
+ area->area_tag);
+ rc = ls_sync_ted(mta->ted, zclient, &dst);
+ if (rc != 0)
+ return rc;
+ }
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Initialize the Link State database from the LSP already stored for this area
+ *
+ * @param area ISIS area
+ */
+void isis_te_init_ted(struct isis_area *area)
+{
+ struct isis_lsp *lsp;
+
+ /* Iterate over all lsp. */
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++)
+ frr_each (lspdb, &area->lspdb[level - 1], lsp)
+ isis_te_parse_lsp(area->mta, lsp);
+}
+
+/* Following are vty command functions */
+#ifndef FABRICD
+
+static void show_router_id(struct vty *vty, struct isis_area *area)
+{
+ bool no_match = true;
+
+ vty_out(vty, "Area %s:\n", area->area_tag);
+ if (area->mta->router_id.s_addr != 0) {
+ vty_out(vty, " MPLS-TE IPv4 Router-Address: %pI4\n",
+ &area->mta->router_id);
+ no_match = false;
+ }
+ if (!IN6_IS_ADDR_UNSPECIFIED(&area->mta->router_id_ipv6)) {
+ vty_out(vty, " MPLS-TE IPv6 Router-Address: %pI6\n",
+ &area->mta->router_id_ipv6);
+ no_match = false;
+ }
+ if (no_match)
+ vty_out(vty, " N/A\n");
+}
+
+DEFUN(show_isis_mpls_te_router,
+ show_isis_mpls_te_router_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] mpls-te router",
+ SHOW_STR
+ PROTO_HELP
+ VRF_CMD_HELP_STR "All VRFs\n"
+ MPLS_TE_STR "Router information\n")
+{
+
+ struct listnode *anode, *inode;
+ struct isis_area *area;
+ struct isis *isis = NULL;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx_vrf = 0;
+
+ if (!im) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list,
+ anode, area)) {
+ if (!IS_MPLS_TE(area->mta))
+ continue;
+
+ show_router_id(vty, area);
+ }
+ }
+ return 0;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode,
+ area)) {
+
+ if (!IS_MPLS_TE(area->mta))
+ continue;
+
+ show_router_id(vty, area);
+ }
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void show_ext_sub(struct vty *vty, char *name,
+ struct isis_ext_subtlvs *ext)
+{
+ struct sbuf buf;
+ char ibuf[PREFIX2STR_BUFFER];
+
+ sbuf_init(&buf, NULL, 0);
+
+ if (!ext || ext->status == EXT_DISABLE)
+ return;
+
+ vty_out(vty, "-- MPLS-TE link parameters for %s --\n", name);
+
+ sbuf_reset(&buf);
+
+ if (IS_SUBTLV(ext, EXT_ADM_GRP))
+ sbuf_push(&buf, 4, "Administrative Group: 0x%x\n",
+ ext->adm_group);
+ if (IS_SUBTLV(ext, EXT_LLRI)) {
+ sbuf_push(&buf, 4, "Link Local ID: %u\n",
+ ext->local_llri);
+ sbuf_push(&buf, 4, "Link Remote ID: %u\n",
+ ext->remote_llri);
+ }
+ if (IS_SUBTLV(ext, EXT_LOCAL_ADDR))
+ sbuf_push(&buf, 4, "Local Interface IP Address(es): %pI4\n",
+ &ext->local_addr);
+ if (IS_SUBTLV(ext, EXT_NEIGH_ADDR))
+ sbuf_push(&buf, 4, "Remote Interface IP Address(es): %pI4\n",
+ &ext->neigh_addr);
+ if (IS_SUBTLV(ext, EXT_LOCAL_ADDR6))
+ sbuf_push(&buf, 4, "Local Interface IPv6 Address(es): %s\n",
+ inet_ntop(AF_INET6, &ext->local_addr6, ibuf,
+ PREFIX2STR_BUFFER));
+ if (IS_SUBTLV(ext, EXT_NEIGH_ADDR6))
+ sbuf_push(&buf, 4, "Remote Interface IPv6 Address(es): %s\n",
+ inet_ntop(AF_INET6, &ext->local_addr6, ibuf,
+ PREFIX2STR_BUFFER));
+ if (IS_SUBTLV(ext, EXT_MAX_BW))
+ sbuf_push(&buf, 4, "Maximum Bandwidth: %g (Bytes/sec)\n",
+ ext->max_bw);
+ if (IS_SUBTLV(ext, EXT_MAX_RSV_BW))
+ sbuf_push(&buf, 4,
+ "Maximum Reservable Bandwidth: %g (Bytes/sec)\n",
+ ext->max_rsv_bw);
+ if (IS_SUBTLV(ext, EXT_UNRSV_BW)) {
+ sbuf_push(&buf, 4, "Unreserved Bandwidth:\n");
+ for (int j = 0; j < MAX_CLASS_TYPE; j += 2) {
+ sbuf_push(&buf, 4 + 2,
+ "[%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)\n",
+ j, ext->unrsv_bw[j],
+ j + 1, ext->unrsv_bw[j + 1]);
+ }
+ }
+ if (IS_SUBTLV(ext, EXT_TE_METRIC))
+ sbuf_push(&buf, 4, "Traffic Engineering Metric: %u\n",
+ ext->te_metric);
+ if (IS_SUBTLV(ext, EXT_RMT_AS))
+ sbuf_push(&buf, 4,
+ "Inter-AS TE Remote AS number: %u\n",
+ ext->remote_as);
+ if (IS_SUBTLV(ext, EXT_RMT_IP))
+ sbuf_push(&buf, 4,
+ "Inter-AS TE Remote ASBR IP address: %pI4\n",
+ &ext->remote_ip);
+ if (IS_SUBTLV(ext, EXT_DELAY))
+ sbuf_push(&buf, 4,
+ "%s Average Link Delay: %u (micro-sec)\n",
+ IS_ANORMAL(ext->delay) ? "Anomalous" : "Normal",
+ ext->delay & TE_EXT_MASK);
+ if (IS_SUBTLV(ext, EXT_MM_DELAY)) {
+ sbuf_push(&buf, 4, "%s Min/Max Link Delay: %u / %u (micro-sec)\n",
+ IS_ANORMAL(ext->min_delay) ? "Anomalous" : "Normal",
+ ext->min_delay & TE_EXT_MASK,
+ ext->max_delay & TE_EXT_MASK);
+ }
+ if (IS_SUBTLV(ext, EXT_DELAY_VAR))
+ sbuf_push(&buf, 4,
+ "Delay Variation: %u (micro-sec)\n",
+ ext->delay_var & TE_EXT_MASK);
+ if (IS_SUBTLV(ext, EXT_PKT_LOSS))
+ sbuf_push(&buf, 4, "%s Link Packet Loss: %g (%%)\n",
+ IS_ANORMAL(ext->pkt_loss) ? "Anomalous" : "Normal",
+ (float)((ext->pkt_loss & TE_EXT_MASK)
+ * LOSS_PRECISION));
+ if (IS_SUBTLV(ext, EXT_RES_BW))
+ sbuf_push(&buf, 4,
+ "Unidirectional Residual Bandwidth: %g (Bytes/sec)\n",
+ ext->res_bw);
+ if (IS_SUBTLV(ext, EXT_AVA_BW))
+ sbuf_push(&buf, 4,
+ "Unidirectional Available Bandwidth: %g (Bytes/sec)\n",
+ ext->ava_bw);
+ if (IS_SUBTLV(ext, EXT_USE_BW))
+ sbuf_push(&buf, 4,
+ "Unidirectional Utilized Bandwidth: %g (Bytes/sec)\n",
+ ext->use_bw);
+
+ vty_multiline(vty, "", "%s", sbuf_buf(&buf));
+ vty_out(vty, "---------------\n\n");
+
+ sbuf_free(&buf);
+ return;
+}
+
+DEFUN (show_isis_mpls_te_interface,
+ show_isis_mpls_te_interface_cmd,
+ "show " PROTO_NAME " mpls-te interface [INTERFACE]",
+ SHOW_STR
+ PROTO_HELP
+ MPLS_TE_STR
+ "Interface information\n"
+ "Interface name\n")
+{
+ struct listnode *anode, *cnode, *inode;
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ struct interface *ifp;
+ int idx_interface = 4;
+ struct isis *isis = NULL;
+
+ if (!im) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+
+ if (argc == idx_interface) {
+ /* Show All Interfaces. */
+ for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode,
+ area)) {
+
+ if (!IS_MPLS_TE(area->mta))
+ continue;
+
+ vty_out(vty, "Area %s:\n", area->area_tag);
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list,
+ cnode, circuit))
+ show_ext_sub(vty,
+ circuit->interface->name,
+ circuit->ext);
+ }
+ }
+ } else {
+ /* Interface name is specified. */
+ ifp = if_lookup_by_name(argv[idx_interface]->arg, VRF_DEFAULT);
+ if (ifp == NULL)
+ vty_out(vty, "No such interface name\n");
+ else {
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit)
+ vty_out(vty,
+ "ISIS is not enabled on circuit %s\n",
+ ifp->name);
+ else
+ show_ext_sub(vty, ifp->name, circuit->ext);
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+/**
+ * Search Vertex in TED that corresponds to the given string that represent
+ * the ISO system ID in the forms <systemid/hostname>[.<pseudo-id>-<framenent>]
+ *
+ * @param ted Link State Database
+ * @param id ISO System ID
+ * @param isis Main reference to the isis daemon
+ *
+ * @return Vertex if found, NULL otherwise
+ */
+static struct ls_vertex *vertex_for_arg(struct ls_ted *ted, const char *id,
+ struct isis *isis)
+{
+ char sysid[255] = {0};
+ uint8_t number[3];
+ const char *pos;
+ uint8_t lspid[ISIS_SYS_ID_LEN + 2] = {0};
+ struct isis_dynhn *dynhn;
+ uint64_t key = 0;
+
+ if (!id)
+ return NULL;
+
+ /*
+ * extract fragment and pseudo id from the string argv
+ * in the forms:
+ * (a) <systemid/hostname>.<pseudo-id>-<framenent> or
+ * (b) <systemid/hostname>.<pseudo-id> or
+ * (c) <systemid/hostname> or
+ * Where systemid is in the form:
+ * xxxx.xxxx.xxxx
+ */
+ strlcpy(sysid, id, sizeof(sysid));
+ if (strlen(id) > 3) {
+ pos = id + strlen(id) - 3;
+ if (strncmp(pos, "-", 1) == 0) {
+ memcpy(number, ++pos, 2);
+ lspid[ISIS_SYS_ID_LEN + 1] =
+ (uint8_t)strtol((char *)number, NULL, 16);
+ pos -= 4;
+ if (strncmp(pos, ".", 1) != 0)
+ return NULL;
+ }
+ if (strncmp(pos, ".", 1) == 0) {
+ memcpy(number, ++pos, 2);
+ lspid[ISIS_SYS_ID_LEN] =
+ (uint8_t)strtol((char *)number, NULL, 16);
+ sysid[pos - id - 1] = '\0';
+ }
+ }
+
+ /*
+ * Try to find the lsp-id if the argv
+ * string is in
+ * the form
+ * hostname.<pseudo-id>-<fragment>
+ */
+ if (sysid2buff(lspid, sysid)) {
+ key = sysid_to_key(lspid);
+ } else if ((dynhn = dynhn_find_by_name(isis, sysid))) {
+ memcpy(lspid, dynhn->id, ISIS_SYS_ID_LEN);
+ key = sysid_to_key(lspid);
+ } else if (strncmp(cmd_hostname_get(), sysid, 15) == 0) {
+ memcpy(lspid, isis->sysid, ISIS_SYS_ID_LEN);
+ key = sysid_to_key(lspid);
+ }
+
+ if (key == 0)
+ return NULL;
+
+ return ls_find_vertex_by_key(ted, key);
+}
+
+/**
+ * Show Link State Traffic Engineering Database extracted from IS-IS LSP.
+ *
+ * @param vty VTY output console
+ * @param argv Command line argument
+ * @param argc Number of command line argument
+ * @param ted Traffic Engineering Database
+ * @param isis isis Main reference to the isis daemon
+ *
+ * @return Command Success if OK, Command Warning otherwise
+ */
+static int show_ted(struct vty *vty, struct cmd_token *argv[], int argc,
+ struct isis_area *area, struct isis *isis)
+{
+ int idx;
+ char *id;
+ struct in_addr ip_addr;
+ struct in6_addr ip6_addr;
+ struct prefix pref;
+ struct ls_ted *ted;
+ struct ls_vertex *vertex;
+ struct ls_edge *edge;
+ struct ls_subnet *subnet;
+ struct ls_edge_key key;
+ bool detail = false;
+ bool uj = use_json(argc, argv);
+ json_object *json = NULL;
+
+ if (!IS_MPLS_TE(area->mta) || !area->mta->ted) {
+ vty_out(vty, "MPLS-TE is disabled for Area %s\n",
+ area->area_tag ? area->area_tag : "null");
+ return CMD_SUCCESS;
+ }
+
+ ted = area->mta->ted;
+
+ if (uj)
+ json = json_object_new_object();
+ else
+ vty_out(vty, "Area %s:\n",
+ area->area_tag ? area->area_tag : "null");
+
+ if (argv[argc - 1]->arg && strmatch(argv[argc - 1]->text, "detail"))
+ detail = true;
+
+ idx = 4;
+ if (argv_find(argv, argc, "vertex", &idx)) {
+ /* Show Vertex */
+ id = argv_find(argv, argc, "WORD", &idx) ? argv[idx]->arg
+ : NULL;
+ if (!id)
+ vertex = NULL;
+ else if (!strncmp(id, "self", 4))
+ vertex = ted->self;
+ else {
+ vertex = vertex_for_arg(ted, id, isis);
+ if (!vertex) {
+ vty_out(vty, "No vertex found for ID %s\n", id);
+ return CMD_WARNING;
+ }
+ }
+
+ if (vertex)
+ ls_show_vertex(vertex, vty, json, detail);
+ else
+ ls_show_vertices(ted, vty, json, detail);
+
+ } else if (argv_find(argv, argc, "edge", &idx)) {
+ /* Show Edge */
+ if (argv_find(argv, argc, "A.B.C.D", &idx)) {
+ if (!inet_pton(AF_INET, argv[idx]->arg, &ip_addr)) {
+ vty_out(vty,
+ "Specified Edge ID %s is invalid\n",
+ argv[idx]->arg);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ /* Get the Edge from the Link State Database */
+ key.family = AF_INET;
+ IPV4_ADDR_COPY(&key.k.addr, &ip_addr);
+ edge = ls_find_edge_by_key(ted, key);
+ if (!edge) {
+ vty_out(vty, "No edge found for ID %pI4\n",
+ &ip_addr);
+ return CMD_WARNING;
+ }
+ } else if (argv_find(argv, argc, "X:X::X:X", &idx)) {
+ if (!inet_pton(AF_INET6, argv[idx]->arg, &ip6_addr)) {
+ vty_out(vty,
+ "Specified Edge ID %s is invalid\n",
+ argv[idx]->arg);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ /* Get the Edge from the Link State Database */
+ key.family = AF_INET6;
+ IPV6_ADDR_COPY(&key.k.addr6, &ip6_addr);
+ edge = ls_find_edge_by_key(ted, key);
+ if (!edge) {
+ vty_out(vty, "No edge found for ID %pI6\n",
+ &ip6_addr);
+ return CMD_WARNING;
+ }
+ } else
+ edge = NULL;
+
+ if (edge)
+ ls_show_edge(edge, vty, json, detail);
+ else
+ ls_show_edges(ted, vty, json, detail);
+
+ } else if (argv_find(argv, argc, "subnet", &idx)) {
+ /* Show Subnet */
+ if (argv_find(argv, argc, "A.B.C.D/M", &idx)) {
+ if (!str2prefix(argv[idx]->arg, &pref)) {
+ vty_out(vty, "Invalid prefix format %s\n",
+ argv[idx]->arg);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ /* Get the Subnet from the Link State Database */
+ subnet = ls_find_subnet(ted, &pref);
+ if (!subnet) {
+ vty_out(vty, "No subnet found for ID %pFX\n",
+ &pref);
+ return CMD_WARNING;
+ }
+ } else if (argv_find(argv, argc, "X:X::X:X/M", &idx)) {
+ if (!str2prefix(argv[idx]->arg, &pref)) {
+ vty_out(vty, "Invalid prefix format %s\n",
+ argv[idx]->arg);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ /* Get the Subnet from the Link State Database */
+ subnet = ls_find_subnet(ted, &pref);
+ if (!subnet) {
+ vty_out(vty, "No subnet found for ID %pFX\n",
+ &pref);
+ return CMD_WARNING;
+ }
+ } else
+ subnet = NULL;
+
+ if (subnet)
+ ls_show_subnet(subnet, vty, json, detail);
+ else
+ ls_show_subnets(ted, vty, json, detail);
+
+ } else {
+ /* Show the complete TED */
+ ls_show_ted(ted, vty, json, detail);
+ }
+
+ if (uj)
+ vty_json(vty, json);
+
+ return CMD_SUCCESS;
+}
+
+/**
+ * Show ISIS Traffic Engineering Database
+ *
+ * @param vty VTY output console
+ * @param argv Command line argument
+ * @param argc Number of command line argument
+ * @param isis isis Main reference to the isis daemon
+
+ * @return Command Success if OK, Command Warning otherwise
+ */
+static int show_isis_ted(struct vty *vty, struct cmd_token *argv[], int argc,
+ struct isis *isis)
+{
+ struct listnode *node;
+ struct isis_area *area;
+ int rc;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ rc = show_ted(vty, argv, argc, area, isis);
+ if (rc != CMD_SUCCESS)
+ return rc;
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_isis_mpls_te_db,
+ show_isis_mpls_te_db_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] mpls-te database [<vertex [WORD]|edge [A.B.C.D|X:X::X:X]|subnet [A.B.C.D/M|X:X::X:X/M]>] [detail|json]",
+ SHOW_STR PROTO_HELP VRF_CMD_HELP_STR
+ "All VRFs\n"
+ MPLS_TE_STR
+ "MPLS-TE database\n"
+ "MPLS-TE Vertex\n"
+ "MPLS-TE Vertex ID (as an ISO ID, hostname or \"self\")\n"
+ "MPLS-TE Edge\n"
+ "MPLS-TE Edge ID (as an IPv4 address)\n"
+ "MPLS-TE Edge ID (as an IPv6 address)\n"
+ "MPLS-TE Subnet\n"
+ "MPLS-TE Subnet ID (as an IPv4 prefix)\n"
+ "MPLS-TE Subnet ID (as an IPv6 prefix)\n"
+ "Detailed information\n"
+ JSON_STR)
+{
+ int idx_vrf = 0;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ struct listnode *node;
+ struct isis *isis;
+ int rc = CMD_WARNING;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) {
+ rc = show_isis_ted(vty, argv, argc, isis);
+ if (rc != CMD_SUCCESS)
+ return rc;
+ }
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis)
+ rc = show_isis_ted(vty, argv, argc, isis);
+ }
+
+ return rc;
+}
+
+#endif /* #ifndef FRABRICD */
+
+/* Initialize MPLS_TE */
+void isis_mpls_te_init(void)
+{
+
+ /* Register Circuit and Adjacency hook */
+ hook_register(isis_if_new_hook, isis_mpls_te_update);
+ hook_register(isis_adj_ip_enabled_hook, isis_mpls_te_adj_ip_enabled);
+ hook_register(isis_adj_ip_disabled_hook, isis_mpls_te_adj_ip_disabled);
+
+#ifndef FABRICD
+ /* Register new VTY commands */
+ install_element(VIEW_NODE, &show_isis_mpls_te_router_cmd);
+ install_element(VIEW_NODE, &show_isis_mpls_te_interface_cmd);
+ install_element(VIEW_NODE, &show_isis_mpls_te_db_cmd);
+#endif
+
+ return;
+}
diff --git a/isisd/isis_te.h b/isisd/isis_te.h
new file mode 100644
index 0000000..5087cda
--- /dev/null
+++ b/isisd/isis_te.h
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_te.c
+ *
+ * This is an implementation of RFC5305, RFC 5307 and RFC 7810
+ *
+ * Author: Olivier Dugeon <olivier.dugeon@orange.com>
+ *
+ * Copyright (C) 2014 - 2019 Orange Labs http://www.orange.com
+ */
+
+#ifndef _ZEBRA_ISIS_MPLS_TE_H
+#define _ZEBRA_ISIS_MPLS_TE_H
+
+/*
+ * Traffic Engineering information are transport through LSP:
+ * - Extended IS Reachability TLV = 22
+ * - Traffic Engineering Router ID TLV = 134
+ * - Extended IP Reachability TLV = 135
+ * - Inter-AS Reachability Information TLV = 141
+ *
+ * and support following sub-TLV:
+ *
+ * Name Value Status
+ * _________________________________________________
+ * Administartive group (color) 3 RFC5305
+ * Link Local/Remote Identifiers 4 RFC5307
+ * IPv4 interface address 6 RFC5305
+ * IPv4 neighbor address 8 RFC5305
+ * Maximum link bandwidth 9 RFC5305
+ * Reservable link bandwidth 10 RFC5305
+ * Unreserved bandwidth 11 RFC5305
+ * TE Default metric 18 RFC5305
+ * Link Protection Type 20 RFC5307
+ * Interface Switching Capability 21 RFC5307
+ * Remote AS number 24 RFC5316
+ * IPv4 Remote ASBR identifier 25 RFC5316
+ *
+ * NOTE: RFC5316 is not fully supported in this version
+ * only subTLVs decoding is provided
+ */
+
+/* Following define the type of TE link regarding the various RFC */
+#define STD_TE 0x01
+#define GMPLS 0x02
+#define INTER_AS 0x04
+#define FLOOD_L1 0x10
+#define FLOOD_L2 0x20
+#define FLOOD_AS 0x40
+#define EMULATED 0x80
+
+#define IS_STD_TE(x) (x & STD_TE)
+#define IS_INTER_AS(x) (x & INTER_AS)
+#define IS_EMULATED(x) (x & EMULATED)
+#define IS_FLOOD_L1(x) (x & FLOOD_L1)
+#define IS_FLOOD_L2(x) (x & FLOOD_L2)
+#define IS_FLOOD_AS(x) (x & FLOOD_AS)
+#define IS_INTER_AS_EMU(x) (x & INTER_AS & EMULATED)
+#define IS_INTER_AS_AS(x) (x & INTER_AS & FLOOD_AS)
+
+/*
+ * Note (since release 7.2), subTLVs definition, serialization
+ * and de-serialization have mode to isis_tlvs.[c,h]
+ */
+
+/* Following declaration concerns the MPLS-TE and LINk-TE management */
+typedef enum _status_t { disable, enable, learn } status_t;
+
+/* Mode for Inter-AS LSP */ /* TODO: Check how if LSP is flooded in RFC5316 */
+typedef enum _interas_mode_t { off, region, as, emulate } interas_mode_t;
+
+#define IS_EXT_TE(e) (e && e->status != 0 \
+ && e->status != EXT_ADJ_SID \
+ && e->status != EXT_LAN_ADJ_SID)
+#define IS_MPLS_TE(a) (a && a->status == enable)
+#define IS_EXPORT_TE(a) (a->export)
+
+/* Per area MPLS-TE parameters */
+struct ls_ted;
+struct mpls_te_area {
+ /* Status of MPLS-TE: enable or disable */
+ status_t status;
+
+ /* L1, L1-L2, L2-Only */
+ uint8_t level;
+
+ /* RFC5316 */
+ interas_mode_t inter_as;
+ struct in_addr interas_areaid;
+
+ /* MPLS_TE IPv4 & IPv6 Router IDs */
+ struct in_addr router_id;
+ struct in6_addr router_id_ipv6;
+
+ /* Link State Database */
+ struct ls_ted *ted;
+ bool export;
+};
+
+/* Structure to provide parameters to lsp iterate callback function */
+struct isis_te_args {
+ struct ls_ted *ted;
+ struct ls_vertex *vertex;
+ bool export;
+};
+
+enum lsp_event { LSP_UNKNOWN, LSP_ADD, LSP_UPD, LSP_DEL, LSP_INC, LSP_TICK };
+
+/* Prototypes. */
+void isis_mpls_te_init(void);
+void isis_mpls_te_create(struct isis_area *area);
+void isis_mpls_te_disable(struct isis_area *area);
+void isis_mpls_te_term(struct isis_area *area);
+void isis_link_params_update(struct isis_circuit *, struct interface *);
+int isis_mpls_te_update(struct interface *);
+void isis_te_lsp_event(struct isis_lsp *lsp, enum lsp_event event);
+int isis_te_sync_ted(struct zapi_opaque_reg_info dst);
+void isis_te_init_ted(struct isis_area *area);
+
+#endif /* _ZEBRA_ISIS_MPLS_TE_H */
diff --git a/isisd/isis_tlvs.c b/isisd/isis_tlvs.c
new file mode 100644
index 0000000..ecf43fa
--- /dev/null
+++ b/isisd/isis_tlvs.c
@@ -0,0 +1,8268 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS TLV Serializer/Deserializer
+ *
+ * Copyright (C) 2015,2017 Christian Franke
+ *
+ * Copyright (C) 2019 Olivier Dugeon - Orange Labs (for TE and SR)
+ *
+ * Copyright (C) 2023 Carmine Scarpitta - University of Rome Tor Vergata
+ * (for IS-IS Extensions to Support SRv6 as per RFC 9352)
+ */
+
+#include <zebra.h>
+#include <json-c/json_object.h>
+
+#ifdef CRYPTO_INTERNAL
+#include "md5.h"
+#endif
+#include "memory.h"
+#include "stream.h"
+#include "sbuf.h"
+#include "network.h"
+
+#include "isisd/isisd.h"
+#include "isisd/isis_tlvs.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_sr.h"
+#include "isisd/isis_flex_algo.h"
+
+#define TLV_SIZE_MISMATCH(log, indent, target) \
+ sbuf_push(log, indent, \
+ "TLV size does not match expected size for " target "!\n")
+
+DEFINE_MTYPE_STATIC(ISISD, ISIS_TLV, "ISIS TLVs");
+DEFINE_MTYPE(ISISD, ISIS_SUBTLV, "ISIS Sub-TLVs");
+DEFINE_MTYPE(ISISD, ISIS_SUBSUBTLV, "ISIS Sub-Sub-TLVs");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_MT_ITEM_LIST, "ISIS MT Item Lists");
+
+typedef int (*unpack_tlv_func)(enum isis_tlv_context context, uint8_t tlv_type,
+ uint8_t tlv_len, struct stream *s,
+ struct sbuf *log, void *dest, int indent);
+typedef int (*pack_item_func)(struct isis_item *item, struct stream *s,
+ size_t *min_length);
+typedef void (*free_item_func)(struct isis_item *i);
+typedef int (*unpack_item_func)(uint16_t mtid, uint8_t len, struct stream *s,
+ struct sbuf *log, void *dest, int indent);
+typedef void (*format_item_func)(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json,
+ int indent);
+typedef struct isis_item *(*copy_item_func)(struct isis_item *i);
+
+struct tlv_ops {
+ const char *name;
+ unpack_tlv_func unpack;
+
+ pack_item_func pack_item;
+ free_item_func free_item;
+ unpack_item_func unpack_item;
+ format_item_func format_item;
+ copy_item_func copy_item;
+};
+
+enum how_to_pack {
+ ISIS_ITEMS,
+ ISIS_MT_ITEMS,
+};
+
+struct pack_order_entry {
+ enum isis_tlv_context context;
+ enum isis_tlv_type type;
+ enum how_to_pack how_to_pack;
+ size_t what_to_pack;
+};
+#define PACK_ENTRY(t, h, w) \
+ { \
+ .context = ISIS_CONTEXT_LSP, .type = ISIS_TLV_##t, \
+ .how_to_pack = (h), \
+ .what_to_pack = offsetof(struct isis_tlvs, w), \
+ }
+
+static const struct pack_order_entry pack_order[] = {
+ PACK_ENTRY(OLDSTYLE_REACH, ISIS_ITEMS, oldstyle_reach),
+ PACK_ENTRY(LAN_NEIGHBORS, ISIS_ITEMS, lan_neighbor),
+ PACK_ENTRY(LSP_ENTRY, ISIS_ITEMS, lsp_entries),
+ PACK_ENTRY(EXTENDED_REACH, ISIS_ITEMS, extended_reach),
+ PACK_ENTRY(MT_REACH, ISIS_MT_ITEMS, mt_reach),
+ PACK_ENTRY(OLDSTYLE_IP_REACH, ISIS_ITEMS, oldstyle_ip_reach),
+ PACK_ENTRY(OLDSTYLE_IP_REACH_EXT, ISIS_ITEMS, oldstyle_ip_reach_ext),
+ PACK_ENTRY(IPV4_ADDRESS, ISIS_ITEMS, ipv4_address),
+ PACK_ENTRY(IPV6_ADDRESS, ISIS_ITEMS, ipv6_address),
+ PACK_ENTRY(GLOBAL_IPV6_ADDRESS, ISIS_ITEMS, global_ipv6_address),
+ PACK_ENTRY(EXTENDED_IP_REACH, ISIS_ITEMS, extended_ip_reach),
+ PACK_ENTRY(MT_IP_REACH, ISIS_MT_ITEMS, mt_ip_reach),
+ PACK_ENTRY(IPV6_REACH, ISIS_ITEMS, ipv6_reach),
+ PACK_ENTRY(MT_IPV6_REACH, ISIS_MT_ITEMS, mt_ipv6_reach),
+ PACK_ENTRY(SRV6_LOCATOR, ISIS_MT_ITEMS, srv6_locator)
+};
+
+/* This is a forward definition. The table is actually initialized
+ * in at the bottom. */
+static const struct tlv_ops *const tlv_table[ISIS_CONTEXT_MAX][ISIS_TLV_MAX];
+
+/* End of _ops forward definition. */
+
+/* Prototypes */
+static void append_item(struct isis_item_list *dest, struct isis_item *item);
+static void init_item_list(struct isis_item_list *items);
+
+static struct isis_subsubtlvs *
+isis_copy_subsubtlvs(struct isis_subsubtlvs *subsubtlvs);
+static void isis_format_subsubtlvs(struct isis_subsubtlvs *subsubtlvs,
+ struct sbuf *buf, struct json_object *json,
+ int indent);
+static int isis_pack_subsubtlvs(struct isis_subsubtlvs *subsubtlvs,
+ struct stream *s);
+static int unpack_tlvs(enum isis_tlv_context context, size_t avail_len,
+ struct stream *stream, struct sbuf *log, void *dest,
+ int indent, bool *unpacked_known_tlvs);
+static void isis_free_subsubtlvs(struct isis_subsubtlvs *subsubtlvs);
+
+/* For tests/isisd, TLV text requires ipv4-unicast instead of standard */
+static const char *isis_mtid2str_fake(uint16_t mtid)
+{
+ if (mtid == ISIS_MT_STANDARD)
+ return "ipv4-unicast";
+ return isis_mtid2str(mtid);
+}
+
+/* Functions for Extended IS Reachability SubTLVs a.k.a Traffic Engineering */
+struct isis_ext_subtlvs *isis_alloc_ext_subtlvs(void)
+{
+ struct isis_ext_subtlvs *ext;
+
+ ext = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(struct isis_ext_subtlvs));
+ init_item_list(&ext->adj_sid);
+ init_item_list(&ext->lan_sid);
+ ext->aslas = list_new();
+
+ init_item_list(&ext->srv6_endx_sid);
+ init_item_list(&ext->srv6_lan_endx_sid);
+
+ admin_group_init(&ext->ext_admin_group);
+
+ return ext;
+}
+
+void isis_del_ext_subtlvs(struct isis_ext_subtlvs *ext)
+{
+ struct isis_item *item, *next_item;
+ struct listnode *node, *nnode;
+ struct isis_asla_subtlvs *asla;
+
+ if (!ext)
+ return;
+
+ /* First, free Adj SID and LAN Adj SID list if needed */
+ for (item = ext->adj_sid.head; item; item = next_item) {
+ next_item = item->next;
+ XFREE(MTYPE_ISIS_SUBTLV, item);
+ }
+ for (item = ext->lan_sid.head; item; item = next_item) {
+ next_item = item->next;
+ XFREE(MTYPE_ISIS_SUBTLV, item);
+ }
+
+ for (ALL_LIST_ELEMENTS(ext->aslas, node, nnode, asla))
+ isis_tlvs_del_asla_flex_algo(ext, asla);
+
+ list_delete(&ext->aslas);
+
+ admin_group_term(&ext->ext_admin_group);
+
+ /* First, free SRv6 End.X SID and SRv6 LAN End.X SID list if needed */
+ for (item = ext->srv6_endx_sid.head; item; item = next_item) {
+ next_item = item->next;
+ isis_free_subsubtlvs(((struct isis_srv6_endx_sid_subtlv *)item)->subsubtlvs);
+ XFREE(MTYPE_ISIS_SUBTLV, item);
+ }
+ for (item = ext->srv6_lan_endx_sid.head; item; item = next_item) {
+ next_item = item->next;
+ isis_free_subsubtlvs(((struct isis_srv6_lan_endx_sid_subtlv *)item)->subsubtlvs);
+ XFREE(MTYPE_ISIS_SUBTLV, item);
+ }
+
+ XFREE(MTYPE_ISIS_SUBTLV, ext);
+}
+
+/*
+ * mtid parameter is used to determine if Adjacency is related to IPv4 or IPv6
+ * Multi-Topology. Special 4096 value i.e. first R flag set is used to indicate
+ * that MT is disabled i.e. IS-IS is working with a Single Topology.
+ */
+static struct isis_ext_subtlvs *
+copy_item_ext_subtlvs(struct isis_ext_subtlvs *exts, uint16_t mtid)
+{
+ struct isis_ext_subtlvs *rv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv));
+ struct isis_adj_sid *adj;
+ struct isis_lan_adj_sid *lan;
+ struct listnode *node, *nnode;
+ struct isis_asla_subtlvs *new_asla, *asla;
+ struct isis_srv6_endx_sid_subtlv *srv6_adj;
+ struct isis_srv6_lan_endx_sid_subtlv *srv6_lan;
+
+ /* Copy the Extended IS main part */
+ memcpy(rv, exts, sizeof(struct isis_ext_subtlvs));
+
+ /* Disable IPv4 / IPv6 advertisement in function of MTID */
+ if (mtid == ISIS_MT_IPV4_UNICAST) {
+ UNSET_SUBTLV(rv, EXT_LOCAL_ADDR6);
+ UNSET_SUBTLV(rv, EXT_NEIGH_ADDR6);
+ }
+ if (mtid == ISIS_MT_IPV6_UNICAST) {
+ UNSET_SUBTLV(rv, EXT_LOCAL_ADDR);
+ UNSET_SUBTLV(rv, EXT_NEIGH_ADDR);
+ }
+
+ /* Prepare (LAN)-Adjacency Segment Routing ID*/
+ init_item_list(&rv->adj_sid);
+ init_item_list(&rv->lan_sid);
+
+ /* Prepare SRv6 (LAN) End.X SID */
+ init_item_list(&rv->srv6_endx_sid);
+ init_item_list(&rv->srv6_lan_endx_sid);
+
+ UNSET_SUBTLV(rv, EXT_ADJ_SID);
+ UNSET_SUBTLV(rv, EXT_LAN_ADJ_SID);
+
+ UNSET_SUBTLV(rv, EXT_SRV6_ENDX_SID);
+ UNSET_SUBTLV(rv, EXT_SRV6_LAN_ENDX_SID);
+
+ /* Copy Adj SID list for IPv4 & IPv6 in function of MT ID */
+ for (adj = (struct isis_adj_sid *)exts->adj_sid.head; adj != NULL;
+ adj = adj->next) {
+ if ((mtid != ISIS_MT_DISABLE)
+ && (((mtid == ISIS_MT_IPV4_UNICAST)
+ && (adj->family != AF_INET))
+ || ((mtid == ISIS_MT_IPV6_UNICAST)
+ && (adj->family != AF_INET6))))
+ continue;
+
+ struct isis_adj_sid *new;
+
+ new = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(struct isis_adj_sid));
+ new->family = adj->family;
+ new->flags = adj->flags;
+ new->weight = adj->weight;
+ new->sid = adj->sid;
+ append_item(&rv->adj_sid, (struct isis_item *)new);
+ SET_SUBTLV(rv, EXT_ADJ_SID);
+ }
+
+ /* Same for LAN Adj SID */
+ for (lan = (struct isis_lan_adj_sid *)exts->lan_sid.head; lan != NULL;
+ lan = lan->next) {
+ if ((mtid != ISIS_MT_DISABLE)
+ && (((mtid == ISIS_MT_IPV4_UNICAST)
+ && (lan->family != AF_INET))
+ || ((mtid == ISIS_MT_IPV6_UNICAST)
+ && (lan->family != AF_INET6))))
+ continue;
+
+ struct isis_lan_adj_sid *new;
+
+ new = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(struct isis_lan_adj_sid));
+ new->family = lan->family;
+ new->flags = lan->flags;
+ new->weight = lan->weight;
+ memcpy(new->neighbor_id, lan->neighbor_id, 6);
+ new->sid = lan->sid;
+ append_item(&rv->lan_sid, (struct isis_item *)new);
+ SET_SUBTLV(rv, EXT_LAN_ADJ_SID);
+ }
+
+ /* Copy SRv6 End.X SID list for IPv4 & IPv6 in function of MT ID */
+ for (srv6_adj = (struct isis_srv6_endx_sid_subtlv *)
+ exts->srv6_endx_sid.head;
+ srv6_adj != NULL; srv6_adj = srv6_adj->next) {
+ if ((mtid != 65535) && (mtid != ISIS_MT_DISABLE) &&
+ ((mtid != ISIS_MT_IPV6_UNICAST)))
+ continue;
+
+ struct isis_srv6_endx_sid_subtlv *new;
+
+ new = XCALLOC(MTYPE_ISIS_SUBTLV,
+ sizeof(struct isis_srv6_endx_sid_subtlv));
+ new->flags = srv6_adj->flags;
+ new->algorithm = srv6_adj->algorithm;
+ new->weight = srv6_adj->weight;
+ new->behavior = srv6_adj->behavior;
+ new->sid = srv6_adj->sid;
+ new->subsubtlvs = isis_copy_subsubtlvs(srv6_adj->subsubtlvs);
+ append_item(&rv->srv6_endx_sid, (struct isis_item *)new);
+ SET_SUBTLV(rv, EXT_SRV6_ENDX_SID);
+ }
+ /* Same for SRv6 LAN End.X SID */
+ for (srv6_lan = (struct isis_srv6_lan_endx_sid_subtlv *)
+ exts->srv6_lan_endx_sid.head;
+ srv6_lan != NULL; srv6_lan = srv6_lan->next) {
+ if ((mtid != 65535) && (mtid != ISIS_MT_DISABLE) &&
+ ((mtid != ISIS_MT_IPV6_UNICAST)))
+ continue;
+
+ struct isis_srv6_lan_endx_sid_subtlv *new;
+
+ new = XCALLOC(MTYPE_ISIS_SUBTLV,
+ sizeof(struct isis_srv6_lan_endx_sid_subtlv));
+ memcpy(new->neighbor_id, srv6_lan->neighbor_id, 6);
+ new->flags = srv6_lan->flags;
+ new->algorithm = srv6_lan->algorithm;
+ new->weight = srv6_lan->weight;
+ new->behavior = srv6_lan->behavior;
+ new->sid = srv6_lan->sid;
+ new->subsubtlvs = isis_copy_subsubtlvs(srv6_lan->subsubtlvs);
+ append_item(&rv->srv6_lan_endx_sid, (struct isis_item *)new);
+ SET_SUBTLV(rv, EXT_SRV6_LAN_ENDX_SID);
+ }
+
+ rv->aslas = list_new();
+
+ for (ALL_LIST_ELEMENTS(exts->aslas, node, nnode, asla)) {
+ new_asla = XCALLOC(MTYPE_ISIS_SUBTLV,
+ sizeof(struct isis_asla_subtlvs));
+ memcpy(new_asla, asla, sizeof(struct isis_asla_subtlvs));
+
+ new_asla->ext_admin_group.bitmap.data = NULL;
+ admin_group_copy(&new_asla->ext_admin_group,
+ &asla->ext_admin_group);
+
+ listnode_add(rv->aslas, new_asla);
+ }
+
+ rv->ext_admin_group.bitmap.data = NULL;
+ admin_group_copy(&rv->ext_admin_group, &exts->ext_admin_group);
+
+ return rv;
+}
+
+static void format_item_asla_subtlvs(struct isis_asla_subtlvs *asla,
+ struct sbuf *buf, int indent)
+{
+ char admin_group_buf[ADMIN_GROUP_PRINT_MAX_SIZE];
+
+ sbuf_push(buf, indent, "Application Specific Link Attributes:\n");
+ sbuf_push(buf, indent + 2,
+ "L flag: %u, SA-Length: %u, UDA-Length: %u\n", asla->legacy,
+ asla->standard_apps_length, asla->user_def_apps_length);
+ sbuf_push(buf, indent + 2, "Standard Applications: 0x%02x",
+ asla->standard_apps);
+ if (asla->standard_apps) {
+ uint8_t bit = asla->standard_apps;
+ if (bit & ISIS_SABM_FLAG_R)
+ sbuf_push(buf, 0, " RSVP-TE");
+ if (bit & ISIS_SABM_FLAG_S)
+ sbuf_push(buf, 0, " SR-Policy");
+ if (bit & ISIS_SABM_FLAG_L)
+ sbuf_push(buf, 0, " Loop-Free-Alternate");
+ if (bit & ISIS_SABM_FLAG_X)
+ sbuf_push(buf, 0, " Flex-Algo");
+ }
+ sbuf_push(buf, 0, "\n");
+ sbuf_push(buf, indent + 2, "User Defined Applications: 0x%02x\n",
+ asla->user_def_apps);
+
+ if (IS_SUBTLV(asla, EXT_ADM_GRP)) {
+ sbuf_push(buf, indent + 2, "Admin Group: 0x%08x\n",
+ asla->admin_group);
+ sbuf_push(buf, indent + 4, "Bit positions: %s\n",
+ admin_group_standard_print(
+ admin_group_buf,
+ indent + 2 + strlen("Admin Group: "),
+ asla->admin_group));
+ }
+ if (IS_SUBTLV(asla, EXT_EXTEND_ADM_GRP) &&
+ admin_group_nb_words(&asla->ext_admin_group) != 0) {
+ sbuf_push(buf, indent + 2, "Ext Admin Group: %s\n",
+ admin_group_string(
+ admin_group_buf, ADMIN_GROUP_PRINT_MAX_SIZE,
+ indent + 2 + strlen("Ext Admin Group: "),
+ &asla->ext_admin_group));
+ admin_group_print(admin_group_buf,
+ indent + 2 + strlen("Ext Admin Group: "),
+ &asla->ext_admin_group);
+ if (admin_group_buf[0] != '\0' &&
+ (buf->pos + strlen(admin_group_buf) +
+ SBUF_DEFAULT_SIZE / 2) < buf->size)
+ sbuf_push(buf, indent + 4, "Bit positions: %s\n",
+ admin_group_buf);
+ }
+ if (IS_SUBTLV(asla, EXT_MAX_BW))
+ sbuf_push(buf, indent + 2,
+ "Maximum Bandwidth: %g (Bytes/sec)\n", asla->max_bw);
+ if (IS_SUBTLV(asla, EXT_MAX_RSV_BW))
+ sbuf_push(buf, indent + 2,
+ "Maximum Reservable Bandwidth: %g (Bytes/sec)\n",
+ asla->max_rsv_bw);
+ if (IS_SUBTLV(asla, EXT_UNRSV_BW)) {
+ sbuf_push(buf, indent + 2, "Unreserved Bandwidth:\n");
+ for (int j = 0; j < MAX_CLASS_TYPE; j += 2) {
+ sbuf_push(
+ buf, indent + 2,
+ "[%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)\n",
+ j, asla->unrsv_bw[j], j + 1,
+ asla->unrsv_bw[j + 1]);
+ }
+ }
+ if (IS_SUBTLV(asla, EXT_TE_METRIC))
+ sbuf_push(buf, indent + 2, "Traffic Engineering Metric: %u\n",
+ asla->te_metric);
+ /* Extended metrics */
+ if (IS_SUBTLV(asla, EXT_DELAY))
+ sbuf_push(buf, indent + 2,
+ "%s Average Link Delay: %u (micro-sec)\n",
+ IS_ANORMAL(asla->delay) ? "Anomalous" : "Normal",
+ asla->delay);
+ if (IS_SUBTLV(asla, EXT_MM_DELAY)) {
+ sbuf_push(buf, indent + 2,
+ "%s Min/Max Link Delay: %u / %u (micro-sec)\n",
+ IS_ANORMAL(asla->min_delay) ? "Anomalous" : "Normal",
+ asla->min_delay & TE_EXT_MASK,
+ asla->max_delay & TE_EXT_MASK);
+ }
+ if (IS_SUBTLV(asla, EXT_DELAY_VAR)) {
+ sbuf_push(buf, indent + 2, "Delay Variation: %u (micro-sec)\n",
+ asla->delay_var & TE_EXT_MASK);
+ }
+ if (IS_SUBTLV(asla, EXT_PKT_LOSS))
+ sbuf_push(buf, indent + 2, "%s Link Packet Loss: %g (%%)\n",
+ IS_ANORMAL(asla->pkt_loss) ? "Anomalous" : "Normal",
+ (float)((asla->pkt_loss & TE_EXT_MASK) *
+ LOSS_PRECISION));
+ if (IS_SUBTLV(asla, EXT_RES_BW))
+ sbuf_push(buf, indent + 2,
+ "Unidir. Residual Bandwidth: %g (Bytes/sec)\n",
+ asla->res_bw);
+ if (IS_SUBTLV(asla, EXT_AVA_BW))
+ sbuf_push(buf, indent + 2,
+ "Unidir. Available Bandwidth: %g (Bytes/sec)\n",
+ asla->ava_bw);
+ if (IS_SUBTLV(asla, EXT_USE_BW))
+ sbuf_push(buf, indent + 2,
+ "Unidir. Utilized Bandwidth: %g (Bytes/sec)\n",
+ asla->use_bw);
+}
+
+/* mtid parameter is used to manage multi-topology i.e. IPv4 / IPv6 */
+static void format_item_ext_subtlvs(struct isis_ext_subtlvs *exts,
+ struct sbuf *buf, struct json_object *json,
+ int indent, uint16_t mtid)
+{
+ char admin_group_buf[ADMIN_GROUP_PRINT_MAX_SIZE];
+ char aux_buf[255];
+ char cnt_buf[255];
+ struct isis_asla_subtlvs *asla;
+ struct listnode *node;
+
+ /* Standard metrics */
+ if (IS_SUBTLV(exts, EXT_ADM_GRP)) {
+ if (json) {
+ snprintfrr(aux_buf, sizeof(aux_buf), "0x%x",
+ exts->adm_group);
+ json_object_string_add(json, "adm-group", aux_buf);
+ } else {
+ sbuf_push(buf, indent, "Admin Group: 0x%08x\n",
+ exts->adm_group);
+ sbuf_push(buf, indent + 2, "Bit positions: %s\n",
+ admin_group_standard_print(
+ admin_group_buf,
+ indent + strlen("Admin Group: "),
+ exts->adm_group));
+ }
+ }
+
+ if (IS_SUBTLV(exts, EXT_EXTEND_ADM_GRP) &&
+ admin_group_nb_words(&exts->ext_admin_group) != 0) {
+ if (!json) {
+ /* TODO json after fix show database detail json */
+ sbuf_push(buf, indent, "Ext Admin Group: %s\n",
+ admin_group_string(
+ admin_group_buf,
+ ADMIN_GROUP_PRINT_MAX_SIZE,
+ indent + strlen("Ext Admin Group: "),
+ &exts->ext_admin_group));
+ admin_group_print(admin_group_buf,
+ indent + strlen("Ext Admin Group: "),
+ &exts->ext_admin_group);
+ if (admin_group_buf[0] != '\0' &&
+ (buf->pos + strlen(admin_group_buf) +
+ SBUF_DEFAULT_SIZE / 2) < buf->size)
+ sbuf_push(buf, indent + 2,
+ "Bit positions: %s\n",
+ admin_group_buf);
+ }
+ }
+ if (IS_SUBTLV(exts, EXT_LLRI)) {
+ if (json) {
+ json_object_int_add(json, "link-local-id",
+ exts->local_llri);
+ json_object_int_add(json, "link-remote-id",
+ exts->remote_llri);
+ } else {
+ sbuf_push(buf, indent, "Link Local ID: %u\n",
+ exts->local_llri);
+ sbuf_push(buf, indent, "Link Remote ID: %u\n",
+ exts->remote_llri);
+ }
+ }
+ if (IS_SUBTLV(exts, EXT_LOCAL_ADDR)) {
+ if (json) {
+ inet_ntop(AF_INET, &exts->local_addr, aux_buf,
+ sizeof(aux_buf));
+ json_object_string_add(json, "local-iface-ip", aux_buf);
+ } else
+ sbuf_push(buf, indent,
+ "Local Interface IP Address(es): %pI4\n",
+ &exts->local_addr);
+ }
+ if (IS_SUBTLV(exts, EXT_NEIGH_ADDR)) {
+ if (json) {
+ inet_ntop(AF_INET, &exts->neigh_addr, aux_buf,
+ sizeof(aux_buf));
+ json_object_string_add(json, "remote-iface-ip",
+ aux_buf);
+ } else
+ sbuf_push(buf, indent,
+ "Remote Interface IP Address(es): %pI4\n",
+ &exts->neigh_addr);
+ }
+ if (IS_SUBTLV(exts, EXT_LOCAL_ADDR6)) {
+ if (json) {
+ inet_ntop(AF_INET6, &exts->local_addr6, aux_buf,
+ sizeof(aux_buf));
+ json_object_string_add(json, "local-iface-ipv6",
+ aux_buf);
+ } else
+ sbuf_push(buf, indent,
+ "Local Interface IPv6 Address(es): %pI6\n",
+ &exts->local_addr6);
+ }
+ if (IS_SUBTLV(exts, EXT_NEIGH_ADDR6)) {
+ if (json) {
+ inet_ntop(AF_INET6, &exts->neigh_addr6, aux_buf,
+ sizeof(aux_buf));
+ json_object_string_add(json, "remote-iface-ipv6",
+ aux_buf);
+ } else
+ sbuf_push(buf, indent,
+ "Remote Interface IPv6 Address(es): %pI6\n",
+ &exts->neigh_addr6);
+ }
+ if (IS_SUBTLV(exts, EXT_MAX_BW)) {
+ if (json) {
+ snprintfrr(aux_buf, sizeof(aux_buf), "%g",
+ exts->max_bw);
+ json_object_string_add(json, "max-bandwith-bytes-sec",
+ aux_buf);
+ } else
+ sbuf_push(buf, indent,
+ "Maximum Bandwidth: %g (Bytes/sec)\n",
+ exts->max_bw);
+ }
+ if (IS_SUBTLV(exts, EXT_MAX_RSV_BW)) {
+ if (json) {
+ snprintfrr(aux_buf, sizeof(aux_buf), "%g",
+ exts->max_rsv_bw);
+ json_object_string_add(
+ json, "max-res-bandwith-bytes-sec", aux_buf);
+ } else
+ sbuf_push(
+ buf, indent,
+ "Maximum Reservable Bandwidth: %g (Bytes/sec)\n",
+ exts->max_rsv_bw);
+ }
+ if (IS_SUBTLV(exts, EXT_UNRSV_BW)) {
+ if (json) {
+ struct json_object *unrsv_json;
+ unrsv_json = json_object_new_object();
+ json_object_object_add(json, "unrsv-bandwith-bytes-sec",
+ unrsv_json);
+ for (int j = 0; j < MAX_CLASS_TYPE; j += 1) {
+ snprintfrr(cnt_buf, sizeof(cnt_buf), "%d", j);
+ snprintfrr(aux_buf, sizeof(aux_buf), "%g",
+ exts->unrsv_bw[j]);
+ json_object_string_add(unrsv_json, cnt_buf,
+ aux_buf);
+ }
+ } else {
+ sbuf_push(buf, indent, "Unreserved Bandwidth:\n");
+ for (int j = 0; j < MAX_CLASS_TYPE; j += 2) {
+ sbuf_push(
+ buf, indent + 2,
+ "[%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)\n",
+ j, exts->unrsv_bw[j], j + 1,
+ exts->unrsv_bw[j + 1]);
+ }
+ }
+ }
+ if (IS_SUBTLV(exts, EXT_TE_METRIC)) {
+ if (json) {
+ json_object_int_add(json, "te-metric", exts->te_metric);
+ } else
+ sbuf_push(buf, indent,
+ "Traffic Engineering Metric: %u\n",
+ exts->te_metric);
+ }
+ if (IS_SUBTLV(exts, EXT_RMT_AS)) {
+ if (json) {
+ json_object_int_add(json, "inter-as-te-remote-as",
+ exts->remote_as);
+ } else
+ sbuf_push(buf, indent,
+ "Inter-AS TE Remote AS number: %u\n",
+ exts->remote_as);
+ }
+ if (IS_SUBTLV(exts, EXT_RMT_IP)) {
+ if (json) {
+ inet_ntop(AF_INET6, &exts->remote_ip, aux_buf,
+ sizeof(aux_buf));
+ json_object_string_add(
+ json, "inter-as-te-remote-asbr-ip", aux_buf);
+ } else
+ sbuf_push(buf, indent,
+ "Inter-AS TE Remote ASBR IP address: %pI4\n",
+ &exts->remote_ip);
+ }
+ /* Extended metrics */
+ if (IS_SUBTLV(exts, EXT_DELAY)) {
+ if (json) {
+ struct json_object *avg_json;
+ avg_json = json_object_new_object();
+ json_object_object_add(json, "avg-delay", avg_json);
+ json_object_string_add(avg_json, "delay",
+ IS_ANORMAL(exts->delay)
+ ? "Anomalous"
+ : "Normal");
+ json_object_int_add(avg_json, "micro-sec", exts->delay);
+ } else
+ sbuf_push(buf, indent,
+ "%s Average Link Delay: %u (micro-sec)\n",
+ IS_ANORMAL(exts->delay) ? "Anomalous"
+ : "Normal",
+ exts->delay & TE_EXT_MASK);
+ }
+ if (IS_SUBTLV(exts, EXT_MM_DELAY)) {
+ if (json) {
+ struct json_object *avg_json;
+ avg_json = json_object_new_object();
+ json_object_object_add(json, "max-min-delay", avg_json);
+ json_object_string_add(avg_json, "delay",
+ IS_ANORMAL(exts->min_delay)
+ ? "Anomalous"
+ : "Normal");
+ snprintfrr(aux_buf, sizeof(aux_buf), "%u / %u",
+ exts->min_delay & TE_EXT_MASK,
+ exts->max_delay & TE_EXT_MASK);
+ json_object_string_add(avg_json, "micro-sec", aux_buf);
+
+ } else
+ sbuf_push(
+ buf, indent,
+ "%s Min/Max Link Delay: %u / %u (micro-sec)\n",
+ IS_ANORMAL(exts->min_delay) ? "Anomalous"
+ : "Normal",
+ exts->min_delay & TE_EXT_MASK,
+ exts->max_delay & TE_EXT_MASK);
+ }
+ if (IS_SUBTLV(exts, EXT_DELAY_VAR)) {
+ if (json) {
+ json_object_int_add(json, "delay-variation-micro-sec",
+ exts->delay_var & TE_EXT_MASK);
+ } else
+ sbuf_push(buf, indent,
+ "Delay Variation: %u (micro-sec)\n",
+ exts->delay_var & TE_EXT_MASK);
+ }
+ if (IS_SUBTLV(exts, EXT_PKT_LOSS)) {
+ if (json) {
+ snprintfrr(aux_buf, sizeof(aux_buf), "%g",
+ (float)((exts->pkt_loss & TE_EXT_MASK) *
+ LOSS_PRECISION));
+ struct json_object *link_json;
+ link_json = json_object_new_object();
+ json_object_object_add(json, "link-packet-loss",
+ link_json);
+ json_object_string_add(link_json, "loss",
+ IS_ANORMAL(exts->pkt_loss)
+ ? "Anomalous"
+ : "Normal");
+ json_object_string_add(link_json, "percentaje",
+ aux_buf);
+ } else
+ sbuf_push(buf, indent, "%s Link Packet Loss: %g (%%)\n",
+ IS_ANORMAL(exts->pkt_loss) ? "Anomalous"
+ : "Normal",
+ (float)((exts->pkt_loss & TE_EXT_MASK) *
+ LOSS_PRECISION));
+ }
+ if (IS_SUBTLV(exts, EXT_RES_BW)) {
+ if (json) {
+ snprintfrr(aux_buf, sizeof(aux_buf), "%g",
+ (exts->res_bw));
+ json_object_string_add(json,
+ "unidir-residual-band-bytes-sec",
+ aux_buf);
+ } else
+ sbuf_push(
+ buf, indent,
+ "Unidir. Residual Bandwidth: %g (Bytes/sec)\n",
+ exts->res_bw);
+ }
+ if (IS_SUBTLV(exts, EXT_AVA_BW)) {
+ if (json) {
+ snprintfrr(aux_buf, sizeof(aux_buf), "%g",
+ (exts->ava_bw));
+ json_object_string_add(
+ json, "unidir-available-band-bytes-sec",
+ aux_buf);
+ } else
+ sbuf_push(
+ buf, indent,
+ "Unidir. Available Bandwidth: %g (Bytes/sec)\n",
+ exts->ava_bw);
+ }
+ if (IS_SUBTLV(exts, EXT_USE_BW)) {
+ if (json) {
+ snprintfrr(aux_buf, sizeof(aux_buf), "%g",
+ (exts->use_bw));
+ json_object_string_add(json,
+ "unidir-utilized-band-bytes-sec",
+ aux_buf);
+ } else
+ sbuf_push(
+ buf, indent,
+ "Unidir. Utilized Bandwidth: %g (Bytes/sec)\n",
+ exts->use_bw);
+ }
+ /* Segment Routing Adjacency as per RFC8667 section #2.2.1 */
+ if (IS_SUBTLV(exts, EXT_ADJ_SID)) {
+ struct isis_adj_sid *adj;
+
+ if (json) {
+ struct json_object *arr_adj_json, *flags_json;
+ arr_adj_json = json_object_new_array();
+ json_object_object_add(json, "adj-sid", arr_adj_json);
+ for (adj = (struct isis_adj_sid *)exts->adj_sid.head;
+ adj; adj = adj->next) {
+ snprintfrr(cnt_buf, sizeof(cnt_buf), "%d",
+ adj->sid);
+ flags_json = json_object_new_object();
+ json_object_int_add(flags_json, "sid",
+ adj->sid);
+ json_object_int_add(flags_json, "weight",
+ adj->weight);
+ json_object_string_add(
+ flags_json, "flag-f",
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-b",
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-v",
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-l",
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_LFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-s",
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_SFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-p",
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_PFLG
+ ? "1"
+ : "0");
+ json_object_array_add(arr_adj_json, flags_json);
+ }
+ } else
+ for (adj = (struct isis_adj_sid *)exts->adj_sid.head;
+ adj; adj = adj->next) {
+ sbuf_push(
+ buf, indent,
+ "Adjacency-SID: %u, Weight: %hhu, Flags: F:%c B:%c, V:%c, L:%c, S:%c, P:%c\n",
+ adj->sid, adj->weight,
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG
+ ? '1'
+ : '0',
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG
+ ? '1'
+ : '0',
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG
+ ? '1'
+ : '0',
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_LFLG
+ ? '1'
+ : '0',
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_SFLG
+ ? '1'
+ : '0',
+ adj->flags & EXT_SUBTLV_LINK_ADJ_SID_PFLG
+ ? '1'
+ : '0');
+ }
+ }
+ /* Segment Routing LAN-Adjacency as per RFC8667 section #2.2.2 */
+ if (IS_SUBTLV(exts, EXT_LAN_ADJ_SID)) {
+ struct isis_lan_adj_sid *lan;
+ if (json) {
+ struct json_object *arr_adj_json, *flags_json;
+ arr_adj_json = json_object_new_array();
+ json_object_object_add(json, "lan-adj-sid",
+ arr_adj_json);
+ for (lan = (struct isis_lan_adj_sid *)
+ exts->adj_sid.head;
+ lan; lan = lan->next) {
+ if (((mtid == ISIS_MT_IPV4_UNICAST) &&
+ (lan->family != AF_INET)) ||
+ ((mtid == ISIS_MT_IPV6_UNICAST) &&
+ (lan->family != AF_INET6)))
+ continue;
+ snprintfrr(cnt_buf, sizeof(cnt_buf), "%d",
+ lan->sid);
+ flags_json = json_object_new_object();
+ json_object_int_add(flags_json, "sid",
+ lan->sid);
+ json_object_int_add(flags_json, "weight",
+ lan->weight);
+ json_object_string_add(
+ flags_json, "flag-f",
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-b",
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-v",
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-l",
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_LFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-s",
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_SFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-p",
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_PFLG
+ ? "1"
+ : "0");
+ json_object_array_add(arr_adj_json, flags_json);
+ }
+ } else
+
+ for (lan = (struct isis_lan_adj_sid *)
+ exts->lan_sid.head;
+ lan; lan = lan->next) {
+ if (((mtid == ISIS_MT_IPV4_UNICAST) &&
+ (lan->family != AF_INET)) ||
+ ((mtid == ISIS_MT_IPV6_UNICAST) &&
+ (lan->family != AF_INET6)))
+ continue;
+ sbuf_push(
+ buf, indent,
+ "Lan-Adjacency-SID: %u, Weight: %hhu, Flags: F:%c B:%c, V:%c, L:%c, S:%c, P:%c\n"
+ " Neighbor-ID: %pSY\n",
+ lan->sid, lan->weight,
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG
+ ? '1'
+ : '0',
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG
+ ? '1'
+ : '0',
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG
+ ? '1'
+ : '0',
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_LFLG
+ ? '1'
+ : '0',
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_SFLG
+ ? '1'
+ : '0',
+ lan->flags & EXT_SUBTLV_LINK_ADJ_SID_PFLG
+ ? '1'
+ : '0',
+ lan->neighbor_id);
+ }
+ }
+ /* SRv6 End.X SID as per RFC9352 section #8.1 */
+ if (IS_SUBTLV(exts, EXT_SRV6_ENDX_SID)) {
+ struct isis_srv6_endx_sid_subtlv *adj;
+
+ if (json) {
+ struct json_object *arr_adj_json, *flags_json;
+ arr_adj_json = json_object_new_array();
+ json_object_object_add(json, "srv6-endx-sid",
+ arr_adj_json);
+ for (adj = (struct isis_srv6_endx_sid_subtlv *)
+ exts->srv6_endx_sid.head;
+ adj; adj = adj->next) {
+ snprintfrr(cnt_buf, sizeof(cnt_buf), "%pI6",
+ &adj->sid);
+ flags_json = json_object_new_object();
+ json_object_string_addf(flags_json, "sid",
+ "%pI6", &adj->sid);
+ json_object_string_add(
+ flags_json, "algorithm",
+ sr_algorithm_string(adj->algorithm));
+ json_object_int_add(flags_json, "weight",
+ adj->weight);
+ json_object_string_add(
+ flags_json, "behavior",
+ seg6local_action2str(adj->behavior));
+ json_object_string_add(
+ flags_json, "flag-b",
+ adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-s",
+ adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-p",
+ adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG
+ ? "1"
+ : "0");
+ json_object_array_add(arr_adj_json, flags_json);
+ if (adj->subsubtlvs)
+ isis_format_subsubtlvs(adj->subsubtlvs,
+ NULL, json,
+ indent + 4);
+ }
+ } else
+ for (adj = (struct isis_srv6_endx_sid_subtlv *)
+ exts->srv6_endx_sid.head;
+ adj; adj = adj->next) {
+ sbuf_push(
+ buf, indent,
+ "SRv6 End.X SID: %pI6, Algorithm: %s, Weight: %hhu, Endpoint Behavior: %s, Flags: B:%c, S:%c, P:%c\n",
+ &adj->sid,
+ sr_algorithm_string(adj->algorithm),
+ adj->weight,
+ seg6local_action2str(adj->behavior),
+ adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG
+ ? '1'
+ : '0',
+ adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG
+ ? '1'
+ : '0',
+ adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG
+ ? '1'
+ : '0');
+ if (adj->subsubtlvs)
+ isis_format_subsubtlvs(adj->subsubtlvs,
+ buf, NULL,
+ indent + 4);
+ }
+ }
+ /* SRv6 LAN End.X SID as per RFC9352 section #8.2 */
+ if (IS_SUBTLV(exts, EXT_SRV6_LAN_ENDX_SID)) {
+ struct isis_srv6_lan_endx_sid_subtlv *lan;
+ if (json) {
+ struct json_object *arr_adj_json, *flags_json;
+ arr_adj_json = json_object_new_array();
+ json_object_object_add(json, "srv6-lan-endx-sid",
+ arr_adj_json);
+ for (lan = (struct isis_srv6_lan_endx_sid_subtlv *)
+ exts->srv6_lan_endx_sid.head;
+ lan; lan = lan->next) {
+ snprintfrr(cnt_buf, sizeof(cnt_buf), "%pI6",
+ &lan->sid);
+ flags_json = json_object_new_object();
+ json_object_string_addf(flags_json, "sid",
+ "%pI6", &lan->sid);
+ json_object_int_add(flags_json, "weight",
+ lan->weight);
+ json_object_string_add(
+ flags_json, "algorithm",
+ sr_algorithm_string(lan->algorithm));
+ json_object_int_add(flags_json, "weight",
+ lan->weight);
+ json_object_string_add(
+ flags_json, "behavior",
+ seg6local_action2str(lan->behavior));
+ json_object_string_add(
+ flags_json, "flag-b",
+ lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-s",
+ lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG
+ ? "1"
+ : "0");
+ json_object_string_add(
+ flags_json, "flag-p",
+ lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG
+ ? "1"
+ : "0");
+ json_object_string_addf(flags_json,
+ "neighbor-id", "%pSY",
+ lan->neighbor_id);
+ json_object_array_add(arr_adj_json, flags_json);
+ if (lan->subsubtlvs)
+ isis_format_subsubtlvs(lan->subsubtlvs,
+ NULL, json,
+ indent + 4);
+ }
+ } else
+ for (lan = (struct isis_srv6_lan_endx_sid_subtlv *)
+ exts->srv6_lan_endx_sid.head;
+ lan; lan = lan->next) {
+ sbuf_push(
+ buf, indent,
+ "SRv6 Lan End.X SID: %pI6, Algorithm: %s, Weight: %hhu, Endpoint Behavior: %s, Flags: B:%c, S:%c, P:%c "
+ "Neighbor-ID: %pSY\n",
+ &lan->sid,
+ sr_algorithm_string(lan->algorithm),
+ lan->weight,
+ seg6local_action2str(lan->behavior),
+ lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG
+ ? '1'
+ : '0',
+ lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG
+ ? '1'
+ : '0',
+ lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG
+ ? '1'
+ : '0',
+ lan->neighbor_id);
+ if (lan->subsubtlvs)
+ isis_format_subsubtlvs(lan->subsubtlvs,
+ buf, NULL,
+ indent + 4);
+ }
+ }
+ for (ALL_LIST_ELEMENTS_RO(exts->aslas, node, asla))
+ format_item_asla_subtlvs(asla, buf, indent);
+}
+
+static void free_item_ext_subtlvs(struct isis_ext_subtlvs *exts)
+{
+ isis_del_ext_subtlvs(exts);
+}
+
+static int pack_item_ext_subtlv_asla(struct isis_asla_subtlvs *asla,
+ struct stream *s, size_t *min_len)
+{
+ size_t subtlv_len;
+ size_t subtlv_len_pos;
+
+ /* Sub TLV header */
+ stream_putc(s, ISIS_SUBTLV_ASLA);
+
+ subtlv_len_pos = stream_get_endp(s);
+ stream_putc(s, 0); /* length will be filled later */
+
+ /* SABM Flag/Length */
+ if (asla->legacy)
+ stream_putc(s, ASLA_LEGACY_FLAG | asla->standard_apps_length);
+ else
+ stream_putc(s, asla->standard_apps_length);
+ stream_putc(s, asla->user_def_apps_length); /* UDABM Flag/Length */
+ stream_putc(s, asla->standard_apps);
+ stream_putc(s, asla->user_def_apps);
+
+ /* Administrative Group */
+ if (IS_SUBTLV(asla, EXT_ADM_GRP)) {
+ stream_putc(s, ISIS_SUBTLV_ADMIN_GRP);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putl(s, asla->admin_group);
+ }
+
+ /* Extended Administrative Group */
+ if (IS_SUBTLV(asla, EXT_EXTEND_ADM_GRP) &&
+ admin_group_nb_words(&asla->ext_admin_group) != 0) {
+ size_t ag_length;
+ size_t ag_length_pos;
+ struct admin_group *ag;
+
+ stream_putc(s, ISIS_SUBTLV_EXT_ADMIN_GRP);
+ ag_length_pos = stream_get_endp(s);
+ stream_putc(s, 0); /* length will be filled later*/
+
+ ag = &asla->ext_admin_group;
+ for (size_t i = 0; i < admin_group_nb_words(ag); i++)
+ stream_putl(s, ag->bitmap.data[i]);
+
+ ag_length = stream_get_endp(s) - ag_length_pos - 1;
+ stream_putc_at(s, ag_length_pos, ag_length);
+ }
+
+ if (IS_SUBTLV(asla, EXT_MAX_BW)) {
+ stream_putc(s, ISIS_SUBTLV_MAX_BW);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putf(s, asla->max_bw);
+ }
+ if (IS_SUBTLV(asla, EXT_MAX_RSV_BW)) {
+ stream_putc(s, ISIS_SUBTLV_MAX_RSV_BW);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putf(s, asla->max_rsv_bw);
+ }
+ if (IS_SUBTLV(asla, EXT_UNRSV_BW)) {
+ stream_putc(s, ISIS_SUBTLV_UNRSV_BW);
+ stream_putc(s, ISIS_SUBTLV_UNRSV_BW_SIZE);
+ for (int j = 0; j < MAX_CLASS_TYPE; j++)
+ stream_putf(s, asla->unrsv_bw[j]);
+ }
+ if (IS_SUBTLV(asla, EXT_TE_METRIC)) {
+ stream_putc(s, ISIS_SUBTLV_TE_METRIC);
+ stream_putc(s, ISIS_SUBTLV_TE_METRIC_SIZE);
+ stream_put3(s, asla->te_metric);
+ }
+ if (IS_SUBTLV(asla, EXT_DELAY)) {
+ stream_putc(s, ISIS_SUBTLV_AV_DELAY);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putl(s, asla->delay);
+ }
+ if (IS_SUBTLV(asla, EXT_MM_DELAY)) {
+ stream_putc(s, ISIS_SUBTLV_MM_DELAY);
+ stream_putc(s, ISIS_SUBTLV_MM_DELAY_SIZE);
+ stream_putl(s, asla->min_delay);
+ stream_putl(s, asla->max_delay);
+ }
+ if (IS_SUBTLV(asla, EXT_DELAY_VAR)) {
+ stream_putc(s, ISIS_SUBTLV_DELAY_VAR);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putl(s, asla->delay_var);
+ }
+ if (IS_SUBTLV(asla, EXT_PKT_LOSS)) {
+ stream_putc(s, ISIS_SUBTLV_PKT_LOSS);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putl(s, asla->pkt_loss);
+ }
+ if (IS_SUBTLV(asla, EXT_RES_BW)) {
+ stream_putc(s, ISIS_SUBTLV_RES_BW);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putf(s, asla->res_bw);
+ }
+ if (IS_SUBTLV(asla, EXT_AVA_BW)) {
+ stream_putc(s, ISIS_SUBTLV_AVA_BW);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putf(s, asla->ava_bw);
+ }
+ if (IS_SUBTLV(asla, EXT_USE_BW)) {
+ stream_putc(s, ISIS_SUBTLV_USE_BW);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putf(s, asla->use_bw);
+ }
+
+ subtlv_len = stream_get_endp(s) - subtlv_len_pos - 1;
+ stream_putc_at(s, subtlv_len_pos, subtlv_len);
+
+ return 0;
+}
+
+static int pack_item_ext_subtlvs(struct isis_ext_subtlvs *exts,
+ struct stream *s, size_t *min_len)
+{
+ struct isis_asla_subtlvs *asla;
+ struct listnode *node;
+ uint8_t size;
+ int ret;
+
+ if (STREAM_WRITEABLE(s) < ISIS_SUBTLV_MAX_SIZE) {
+ *min_len = ISIS_SUBTLV_MAX_SIZE;
+ return 1;
+ }
+
+ if (IS_SUBTLV(exts, EXT_ADM_GRP)) {
+ stream_putc(s, ISIS_SUBTLV_ADMIN_GRP);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putl(s, exts->adm_group);
+ }
+ if (IS_SUBTLV(exts, EXT_EXTEND_ADM_GRP) &&
+ admin_group_nb_words(&exts->ext_admin_group) != 0) {
+ /* Extended Administrative Group */
+ size_t ag_length;
+ size_t ag_length_pos;
+ struct admin_group *ag;
+
+ stream_putc(s, ISIS_SUBTLV_EXT_ADMIN_GRP);
+ ag_length_pos = stream_get_endp(s);
+ stream_putc(s, 0); /* length will be filled later*/
+
+ ag = &exts->ext_admin_group;
+ for (size_t i = 0; i < admin_group_nb_words(ag); i++)
+ stream_putl(s, ag->bitmap.data[i]);
+
+ ag_length = stream_get_endp(s) - ag_length_pos - 1;
+ stream_putc_at(s, ag_length_pos, ag_length);
+ }
+ if (IS_SUBTLV(exts, EXT_LLRI)) {
+ stream_putc(s, ISIS_SUBTLV_LLRI);
+ stream_putc(s, ISIS_SUBTLV_LLRI_SIZE);
+ stream_putl(s, exts->local_llri);
+ stream_putl(s, exts->remote_llri);
+ }
+ if (IS_SUBTLV(exts, EXT_LOCAL_ADDR)) {
+ stream_putc(s, ISIS_SUBTLV_LOCAL_IPADDR);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_put(s, &exts->local_addr.s_addr, 4);
+ }
+ if (IS_SUBTLV(exts, EXT_NEIGH_ADDR)) {
+ stream_putc(s, ISIS_SUBTLV_RMT_IPADDR);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_put(s, &exts->neigh_addr.s_addr, 4);
+ }
+ if (IS_SUBTLV(exts, EXT_LOCAL_ADDR6)) {
+ stream_putc(s, ISIS_SUBTLV_LOCAL_IPADDR6);
+ stream_putc(s, ISIS_SUBTLV_IPV6_ADDR_SIZE);
+ stream_put(s, &exts->local_addr6, 16);
+ }
+ if (IS_SUBTLV(exts, EXT_NEIGH_ADDR6)) {
+ stream_putc(s, ISIS_SUBTLV_RMT_IPADDR6);
+ stream_putc(s, ISIS_SUBTLV_IPV6_ADDR_SIZE);
+ stream_put(s, &exts->neigh_addr6, 16);
+ }
+ if (IS_SUBTLV(exts, EXT_MAX_BW)) {
+ stream_putc(s, ISIS_SUBTLV_MAX_BW);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putf(s, exts->max_bw);
+ }
+ if (IS_SUBTLV(exts, EXT_MAX_RSV_BW)) {
+ stream_putc(s, ISIS_SUBTLV_MAX_RSV_BW);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putf(s, exts->max_rsv_bw);
+ }
+ if (IS_SUBTLV(exts, EXT_UNRSV_BW)) {
+ stream_putc(s, ISIS_SUBTLV_UNRSV_BW);
+ stream_putc(s, ISIS_SUBTLV_UNRSV_BW_SIZE);
+ for (int j = 0; j < MAX_CLASS_TYPE; j++)
+ stream_putf(s, exts->unrsv_bw[j]);
+ }
+ if (IS_SUBTLV(exts, EXT_TE_METRIC)) {
+ stream_putc(s, ISIS_SUBTLV_TE_METRIC);
+ stream_putc(s, ISIS_SUBTLV_TE_METRIC_SIZE);
+ stream_put3(s, exts->te_metric);
+ }
+ if (IS_SUBTLV(exts, EXT_RMT_AS)) {
+ stream_putc(s, ISIS_SUBTLV_RAS);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putl(s, exts->remote_as);
+ }
+ if (IS_SUBTLV(exts, EXT_RMT_IP)) {
+ stream_putc(s, ISIS_SUBTLV_RIP);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_put(s, &exts->remote_ip.s_addr, 4);
+ }
+ if (IS_SUBTLV(exts, EXT_DELAY)) {
+ stream_putc(s, ISIS_SUBTLV_AV_DELAY);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putl(s, exts->delay);
+ }
+ if (IS_SUBTLV(exts, EXT_MM_DELAY)) {
+ stream_putc(s, ISIS_SUBTLV_MM_DELAY);
+ stream_putc(s, ISIS_SUBTLV_MM_DELAY_SIZE);
+ stream_putl(s, exts->min_delay);
+ stream_putl(s, exts->max_delay);
+ }
+ if (IS_SUBTLV(exts, EXT_DELAY_VAR)) {
+ stream_putc(s, ISIS_SUBTLV_DELAY_VAR);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putl(s, exts->delay_var);
+ }
+ if (IS_SUBTLV(exts, EXT_PKT_LOSS)) {
+ stream_putc(s, ISIS_SUBTLV_PKT_LOSS);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putl(s, exts->pkt_loss);
+ }
+ if (IS_SUBTLV(exts, EXT_RES_BW)) {
+ stream_putc(s, ISIS_SUBTLV_RES_BW);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putf(s, exts->res_bw);
+ }
+ if (IS_SUBTLV(exts, EXT_AVA_BW)) {
+ stream_putc(s, ISIS_SUBTLV_AVA_BW);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putf(s, exts->ava_bw);
+ }
+ if (IS_SUBTLV(exts, EXT_USE_BW)) {
+ stream_putc(s, ISIS_SUBTLV_USE_BW);
+ stream_putc(s, ISIS_SUBTLV_DEF_SIZE);
+ stream_putf(s, exts->use_bw);
+ }
+ /* Segment Routing Adjacency as per RFC8667 section #2.2.1 */
+ if (IS_SUBTLV(exts, EXT_ADJ_SID)) {
+ struct isis_adj_sid *adj;
+
+ for (adj = (struct isis_adj_sid *)exts->adj_sid.head; adj;
+ adj = adj->next) {
+ stream_putc(s, ISIS_SUBTLV_ADJ_SID);
+ size = ISIS_SUBTLV_ADJ_SID_SIZE;
+ if (!(adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG))
+ size++;
+ stream_putc(s, size);
+ stream_putc(s, adj->flags);
+ stream_putc(s, adj->weight);
+ if (adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG)
+ stream_put3(s, adj->sid);
+ else
+ stream_putl(s, adj->sid);
+
+ }
+ }
+ /* Segment Routing LAN-Adjacency as per RFC8667 section #2.2.2 */
+ if (IS_SUBTLV(exts, EXT_LAN_ADJ_SID)) {
+ struct isis_lan_adj_sid *lan;
+
+ for (lan = (struct isis_lan_adj_sid *)exts->lan_sid.head; lan;
+ lan = lan->next) {
+ stream_putc(s, ISIS_SUBTLV_LAN_ADJ_SID);
+ size = ISIS_SUBTLV_LAN_ADJ_SID_SIZE;
+ if (!(lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG))
+ size++;
+ stream_putc(s, size);
+ stream_putc(s, lan->flags);
+ stream_putc(s, lan->weight);
+ stream_put(s, lan->neighbor_id, 6);
+ if (lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG)
+ stream_put3(s, lan->sid);
+ else
+ stream_putl(s, lan->sid);
+ }
+ }
+ /* SRv6 End.X SID as per RFC9352 section #8.1 */
+ if (IS_SUBTLV(exts, EXT_SRV6_ENDX_SID)) {
+ struct isis_srv6_endx_sid_subtlv *adj;
+ size_t subtlv_len;
+ size_t subtlv_len_pos;
+
+ for (adj = (struct isis_srv6_endx_sid_subtlv *)
+ exts->srv6_endx_sid.head;
+ adj; adj = adj->next) {
+ stream_putc(s, ISIS_SUBTLV_SRV6_ENDX_SID);
+
+ subtlv_len_pos = stream_get_endp(s);
+ stream_putc(s, 0); /* length will be filled later */
+
+ stream_putc(s, adj->flags);
+ stream_putc(s, adj->algorithm);
+ stream_putc(s, adj->weight);
+ stream_putw(s, adj->behavior);
+ stream_put(s, &adj->sid, IPV6_MAX_BYTELEN);
+
+ if (adj->subsubtlvs) {
+ /* Pack Sub-Sub-TLVs */
+ if (isis_pack_subsubtlvs(adj->subsubtlvs, s))
+ return 1;
+ } else {
+ /* No Sub-Sub-TLVs */
+ if (STREAM_WRITEABLE(s) < 1) {
+ *min_len =
+ ISIS_SUBTLV_SRV6_ENDX_SID_SIZE;
+ return 1;
+ }
+
+ /* Put 0 as Sub-Sub-TLV length, because we have
+ * no Sub-Sub-TLVs */
+ stream_putc(s, 0);
+ }
+
+ subtlv_len = stream_get_endp(s) - subtlv_len_pos - 1;
+ stream_putc_at(s, subtlv_len_pos, subtlv_len);
+ }
+ }
+ /* SRv6 LAN End.X SID as per RFC9352 section #8.2 */
+ if (IS_SUBTLV(exts, EXT_SRV6_LAN_ENDX_SID)) {
+ struct isis_srv6_lan_endx_sid_subtlv *lan;
+ size_t subtlv_len;
+ size_t subtlv_len_pos;
+
+ for (lan = (struct isis_srv6_lan_endx_sid_subtlv *)
+ exts->srv6_lan_endx_sid.head;
+ lan; lan = lan->next) {
+ stream_putc(s, ISIS_SUBTLV_SRV6_LAN_ENDX_SID);
+
+ subtlv_len_pos = stream_get_endp(s);
+ stream_putc(s, 0); /* length will be filled later */
+
+ stream_put(s, lan->neighbor_id, 6);
+ stream_putc(s, lan->flags);
+ stream_putc(s, lan->algorithm);
+ stream_putc(s, lan->weight);
+ stream_putw(s, lan->behavior);
+ stream_put(s, &lan->sid, IPV6_MAX_BYTELEN);
+
+ if (lan->subsubtlvs) {
+ /* Pack Sub-Sub-TLVs */
+ if (isis_pack_subsubtlvs(lan->subsubtlvs, s))
+ return 1;
+ } else {
+ /* No Sub-Sub-TLVs */
+ if (STREAM_WRITEABLE(s) < 1) {
+ *min_len =
+ ISIS_SUBTLV_SRV6_LAN_ENDX_SID_SIZE;
+ return 1;
+ }
+
+ /* Put 0 as Sub-Sub-TLV length, because we have
+ * no Sub-Sub-TLVs */
+ stream_putc(s, 0);
+ }
+
+ subtlv_len = stream_get_endp(s) - subtlv_len_pos - 1;
+ stream_putc_at(s, subtlv_len_pos, subtlv_len);
+ }
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(exts->aslas, node, asla)) {
+ ret = pack_item_ext_subtlv_asla(asla, s, min_len);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int unpack_item_ext_subtlv_asla(uint16_t mtid, uint8_t subtlv_len,
+ struct stream *s, struct sbuf *log,
+ int indent,
+ struct isis_ext_subtlvs *exts)
+{
+ /* Standard App Identifier Bit Flags/Length */
+ uint8_t sabm_flag_len;
+ /* User-defined App Identifier Bit Flags/Length */
+ uint8_t uabm_flag_len;
+ uint8_t sabm[ASLA_APP_IDENTIFIER_BIT_LENGTH] = {0};
+ uint8_t uabm[ASLA_APP_IDENTIFIER_BIT_LENGTH] = {0};
+ uint8_t readable = subtlv_len;
+ uint8_t subsubtlv_type;
+ uint8_t subsubtlv_len;
+ size_t nb_groups;
+ struct isis_asla_subtlvs *asla;
+
+ if (subtlv_len < ISIS_SUBSUBTLV_HDR_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent, "ASLA");
+ return -1;
+ }
+
+
+ asla = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*asla));
+
+ admin_group_init(&asla->ext_admin_group);
+
+
+ sabm_flag_len = stream_getc(s);
+ uabm_flag_len = stream_getc(s);
+ asla->legacy = CHECK_FLAG(sabm_flag_len, ASLA_LEGACY_FLAG);
+ asla->standard_apps_length = ASLA_APPS_LENGTH_MASK & sabm_flag_len;
+ asla->user_def_apps_length = ASLA_APPS_LENGTH_MASK & uabm_flag_len;
+
+ readable -= ISIS_SUBSUBTLV_HDR_SIZE;
+ if (readable <
+ asla->standard_apps_length + asla->user_def_apps_length) {
+ TLV_SIZE_MISMATCH(log, indent, "ASLA");
+ return -1;
+ }
+
+ for (int i = 0; i < asla->standard_apps_length; i++)
+ sabm[i] = stream_getc(s);
+ for (int i = 0; i < asla->user_def_apps_length; i++)
+ uabm[i] = stream_getc(s);
+
+ readable -= (asla->standard_apps_length + asla->user_def_apps_length);
+
+ asla->standard_apps = sabm[0];
+ asla->user_def_apps = uabm[0];
+
+ while (readable > 0) {
+ if (readable < ISIS_SUBSUBTLV_HDR_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent, "ASLA Sub TLV");
+ return -1;
+ }
+
+ subsubtlv_type = stream_getc(s);
+ subsubtlv_len = stream_getc(s);
+ readable -= ISIS_SUBSUBTLV_HDR_SIZE;
+
+
+ switch (subsubtlv_type) {
+ case ISIS_SUBTLV_ADMIN_GRP:
+ if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "ASLA Adm Group");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ asla->admin_group = stream_getl(s);
+ SET_SUBTLV(asla, EXT_ADM_GRP);
+ }
+ break;
+
+ case ISIS_SUBTLV_EXT_ADMIN_GRP:
+ nb_groups = subsubtlv_len / sizeof(uint32_t);
+ for (size_t i = 0; i < nb_groups; i++) {
+ uint32_t val = stream_getl(s);
+
+ admin_group_bulk_set(&asla->ext_admin_group,
+ val, i);
+ }
+ SET_SUBTLV(asla, EXT_EXTEND_ADM_GRP);
+ break;
+ case ISIS_SUBTLV_MAX_BW:
+ if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Maximum Bandwidth");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ asla->max_bw = stream_getf(s);
+ SET_SUBTLV(asla, EXT_MAX_BW);
+ }
+ break;
+ case ISIS_SUBTLV_MAX_RSV_BW:
+ if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(
+ log, indent,
+ "Maximum Reservable Bandwidth");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ asla->max_rsv_bw = stream_getf(s);
+ SET_SUBTLV(asla, EXT_MAX_RSV_BW);
+ }
+ break;
+ case ISIS_SUBTLV_UNRSV_BW:
+ if (subsubtlv_len != ISIS_SUBTLV_UNRSV_BW_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Unreserved Bandwidth");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ for (int i = 0; i < MAX_CLASS_TYPE; i++)
+ asla->unrsv_bw[i] = stream_getf(s);
+ SET_SUBTLV(asla, EXT_UNRSV_BW);
+ }
+ break;
+ case ISIS_SUBTLV_TE_METRIC:
+ if (subsubtlv_len != ISIS_SUBTLV_TE_METRIC_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Traffic Engineering Metric");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ asla->te_metric = stream_get3(s);
+ SET_SUBTLV(asla, EXT_TE_METRIC);
+ }
+ break;
+ /* Extended Metrics as defined in RFC 7810 */
+ case ISIS_SUBTLV_AV_DELAY:
+ if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Average Link Delay");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ asla->delay = stream_getl(s);
+ SET_SUBTLV(asla, EXT_DELAY);
+ }
+ break;
+ case ISIS_SUBTLV_MM_DELAY:
+ if (subsubtlv_len != ISIS_SUBTLV_MM_DELAY_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Min/Max Link Delay");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ asla->min_delay = stream_getl(s);
+ asla->max_delay = stream_getl(s);
+ SET_SUBTLV(asla, EXT_MM_DELAY);
+ }
+ break;
+ case ISIS_SUBTLV_DELAY_VAR:
+ if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Delay Variation");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ asla->delay_var = stream_getl(s);
+ SET_SUBTLV(asla, EXT_DELAY_VAR);
+ }
+ break;
+ case ISIS_SUBTLV_PKT_LOSS:
+ if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Link Packet Loss");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ asla->pkt_loss = stream_getl(s);
+ SET_SUBTLV(asla, EXT_PKT_LOSS);
+ }
+ break;
+ case ISIS_SUBTLV_RES_BW:
+ if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(
+ log, indent,
+ "Unidirectional Residual Bandwidth");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ asla->res_bw = stream_getf(s);
+ SET_SUBTLV(asla, EXT_RES_BW);
+ }
+ break;
+ case ISIS_SUBTLV_AVA_BW:
+ if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(
+ log, indent,
+ "Unidirectional Available Bandwidth");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ asla->ava_bw = stream_getf(s);
+ SET_SUBTLV(asla, EXT_AVA_BW);
+ }
+ break;
+ case ISIS_SUBTLV_USE_BW:
+ if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(
+ log, indent,
+ "Unidirectional Utilized Bandwidth");
+ stream_forward_getp(s, subsubtlv_len);
+ } else {
+ asla->use_bw = stream_getf(s);
+ SET_SUBTLV(asla, EXT_USE_BW);
+ }
+ break;
+ default:
+ zlog_debug("unknown (t,l)=(%u,%u)", subsubtlv_type,
+ subsubtlv_len);
+ stream_forward_getp(s, subsubtlv_len);
+ break;
+ }
+ readable -= subsubtlv_len;
+ }
+
+ listnode_add(exts->aslas, asla);
+
+ return 0;
+}
+
+static int unpack_item_ext_subtlvs(uint16_t mtid, uint8_t len, struct stream *s,
+ struct sbuf *log, void *dest, int indent)
+{
+ uint8_t sum = 0;
+ uint8_t subtlv_type;
+ uint8_t subtlv_len;
+ uint8_t subsubtlv_len;
+ size_t nb_groups;
+ uint32_t val;
+
+ struct isis_extended_reach *rv = dest;
+ struct isis_ext_subtlvs *exts = isis_alloc_ext_subtlvs();
+
+ rv->subtlvs = exts;
+
+ /*
+ * Parse subTLVs until reach subTLV length
+ * Check that it remains at least 2 bytes: subTLV Type & Length
+ */
+ while (len > sum + 2) {
+ /* Read SubTLV Type and Length */
+ subtlv_type = stream_getc(s);
+ subtlv_len = stream_getc(s);
+ if (subtlv_len > len - sum - ISIS_SUBTLV_HDR_SIZE) {
+ sbuf_push(
+ log, indent,
+ "TLV %hhu: Available data %u is less than TLV size %u !\n",
+ subtlv_type, len - sum - ISIS_SUBTLV_HDR_SIZE,
+ subtlv_len);
+ return 1;
+ }
+
+ switch (subtlv_type) {
+ /* Standard Metric as defined in RFC5305 */
+ case ISIS_SUBTLV_ADMIN_GRP:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Administrative Group");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->adm_group = stream_getl(s);
+ SET_SUBTLV(exts, EXT_ADM_GRP);
+ }
+ break;
+ case ISIS_SUBTLV_EXT_ADMIN_GRP:
+ nb_groups = subtlv_len / sizeof(uint32_t);
+ for (size_t i = 0; i < nb_groups; i++) {
+ val = stream_getl(s);
+ admin_group_bulk_set(&exts->ext_admin_group,
+ val, i);
+ }
+ SET_SUBTLV(exts, EXT_EXTEND_ADM_GRP);
+ break;
+ case ISIS_SUBTLV_LLRI:
+ if (subtlv_len != ISIS_SUBTLV_LLRI_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent, "Link ID");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->local_llri = stream_getl(s);
+ exts->remote_llri = stream_getl(s);
+ SET_SUBTLV(exts, EXT_LLRI);
+ }
+ break;
+ case ISIS_SUBTLV_LOCAL_IPADDR:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Local IP address");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ stream_get(&exts->local_addr.s_addr, s, 4);
+ SET_SUBTLV(exts, EXT_LOCAL_ADDR);
+ }
+ break;
+ case ISIS_SUBTLV_RMT_IPADDR:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Remote IP address");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ stream_get(&exts->neigh_addr.s_addr, s, 4);
+ SET_SUBTLV(exts, EXT_NEIGH_ADDR);
+ }
+ break;
+ case ISIS_SUBTLV_LOCAL_IPADDR6:
+ if (subtlv_len != ISIS_SUBTLV_IPV6_ADDR_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Local IPv6 address");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ stream_get(&exts->local_addr6, s, 16);
+ SET_SUBTLV(exts, EXT_LOCAL_ADDR6);
+ }
+ break;
+ case ISIS_SUBTLV_RMT_IPADDR6:
+ if (subtlv_len != ISIS_SUBTLV_IPV6_ADDR_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Remote IPv6 address");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ stream_get(&exts->neigh_addr6, s, 16);
+ SET_SUBTLV(exts, EXT_NEIGH_ADDR6);
+ }
+ break;
+ case ISIS_SUBTLV_MAX_BW:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Maximum Bandwidth");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->max_bw = stream_getf(s);
+ SET_SUBTLV(exts, EXT_MAX_BW);
+ }
+ break;
+ case ISIS_SUBTLV_MAX_RSV_BW:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(
+ log, indent,
+ "Maximum Reservable Bandwidth");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->max_rsv_bw = stream_getf(s);
+ SET_SUBTLV(exts, EXT_MAX_RSV_BW);
+ }
+ break;
+ case ISIS_SUBTLV_UNRSV_BW:
+ if (subtlv_len != ISIS_SUBTLV_UNRSV_BW_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Unreserved Bandwidth");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ for (int i = 0; i < MAX_CLASS_TYPE; i++)
+ exts->unrsv_bw[i] = stream_getf(s);
+ SET_SUBTLV(exts, EXT_UNRSV_BW);
+ }
+ break;
+ case ISIS_SUBTLV_TE_METRIC:
+ if (subtlv_len != ISIS_SUBTLV_TE_METRIC_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Traffic Engineering Metric");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->te_metric = stream_get3(s);
+ SET_SUBTLV(exts, EXT_TE_METRIC);
+ }
+ break;
+ case ISIS_SUBTLV_RAS:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Remote AS number");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->remote_as = stream_getl(s);
+ SET_SUBTLV(exts, EXT_RMT_AS);
+ }
+ break;
+ case ISIS_SUBTLV_RIP:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Remote ASBR IP Address");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ stream_get(&exts->remote_ip.s_addr, s, 4);
+ SET_SUBTLV(exts, EXT_RMT_IP);
+ }
+ break;
+ /* Extended Metrics as defined in RFC 7810 */
+ case ISIS_SUBTLV_AV_DELAY:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Average Link Delay");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->delay = stream_getl(s);
+ SET_SUBTLV(exts, EXT_DELAY);
+ }
+ break;
+ case ISIS_SUBTLV_MM_DELAY:
+ if (subtlv_len != ISIS_SUBTLV_MM_DELAY_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Min/Max Link Delay");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->min_delay = stream_getl(s);
+ exts->max_delay = stream_getl(s);
+ SET_SUBTLV(exts, EXT_MM_DELAY);
+ }
+ break;
+ case ISIS_SUBTLV_DELAY_VAR:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Delay Variation");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->delay_var = stream_getl(s);
+ SET_SUBTLV(exts, EXT_DELAY_VAR);
+ }
+ break;
+ case ISIS_SUBTLV_PKT_LOSS:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Link Packet Loss");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->pkt_loss = stream_getl(s);
+ SET_SUBTLV(exts, EXT_PKT_LOSS);
+ }
+ break;
+ case ISIS_SUBTLV_RES_BW:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(
+ log, indent,
+ "Unidirectional Residual Bandwidth");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->res_bw = stream_getf(s);
+ SET_SUBTLV(exts, EXT_RES_BW);
+ }
+ break;
+ case ISIS_SUBTLV_AVA_BW:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(
+ log, indent,
+ "Unidirectional Available Bandwidth");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->ava_bw = stream_getf(s);
+ SET_SUBTLV(exts, EXT_AVA_BW);
+ }
+ break;
+ case ISIS_SUBTLV_USE_BW:
+ if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) {
+ TLV_SIZE_MISMATCH(
+ log, indent,
+ "Unidirectional Utilized Bandwidth");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ exts->use_bw = stream_getf(s);
+ SET_SUBTLV(exts, EXT_USE_BW);
+ }
+ break;
+ /* Segment Routing Adjacency as per RFC8667 section #2.2.1 */
+ case ISIS_SUBTLV_ADJ_SID:
+ if (subtlv_len != ISIS_SUBTLV_ADJ_SID_SIZE
+ && subtlv_len != ISIS_SUBTLV_ADJ_SID_SIZE + 1) {
+ TLV_SIZE_MISMATCH(log, indent, "Adjacency SID");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ struct isis_adj_sid *adj;
+
+ adj = XCALLOC(MTYPE_ISIS_SUBTLV,
+ sizeof(struct isis_adj_sid));
+ adj->flags = stream_getc(s);
+ adj->weight = stream_getc(s);
+ if (adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG
+ && subtlv_len != ISIS_SUBTLV_ADJ_SID_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Adjacency SID");
+ stream_forward_getp(s, subtlv_len - 2);
+ XFREE(MTYPE_ISIS_SUBTLV, adj);
+ break;
+ }
+
+ if (!(adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG)
+ && subtlv_len
+ != ISIS_SUBTLV_ADJ_SID_SIZE
+ + 1) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "Adjacency SID");
+ stream_forward_getp(s, subtlv_len - 2);
+ XFREE(MTYPE_ISIS_SUBTLV, adj);
+ break;
+ }
+
+ if (adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG) {
+ adj->sid = stream_get3(s);
+ adj->sid &= MPLS_LABEL_VALUE_MASK;
+ } else {
+ adj->sid = stream_getl(s);
+ }
+ if (mtid == ISIS_MT_IPV4_UNICAST)
+ adj->family = AF_INET;
+ if (mtid == ISIS_MT_IPV6_UNICAST)
+ adj->family = AF_INET6;
+ append_item(&exts->adj_sid,
+ (struct isis_item *)adj);
+ SET_SUBTLV(exts, EXT_ADJ_SID);
+ }
+ break;
+ /* Segment Routing LAN-Adjacency as per RFC8667 section 2.2.2 */
+ case ISIS_SUBTLV_LAN_ADJ_SID:
+ if (subtlv_len != ISIS_SUBTLV_LAN_ADJ_SID_SIZE
+ && subtlv_len != ISIS_SUBTLV_LAN_ADJ_SID_SIZE + 1) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "LAN-Adjacency SID");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ struct isis_lan_adj_sid *lan;
+
+ lan = XCALLOC(MTYPE_ISIS_SUBTLV,
+ sizeof(struct isis_lan_adj_sid));
+ lan->flags = stream_getc(s);
+ lan->weight = stream_getc(s);
+ stream_get(&(lan->neighbor_id), s,
+ ISIS_SYS_ID_LEN);
+
+ if (lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG
+ && subtlv_len
+ != ISIS_SUBTLV_LAN_ADJ_SID_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "LAN-Adjacency SID");
+ stream_forward_getp(
+ s, subtlv_len - 2
+ - ISIS_SYS_ID_LEN);
+ XFREE(MTYPE_ISIS_SUBTLV, lan);
+ break;
+ }
+
+ if (!(lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG)
+ && subtlv_len
+ != ISIS_SUBTLV_LAN_ADJ_SID_SIZE
+ + 1) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "LAN-Adjacency SID");
+ stream_forward_getp(
+ s, subtlv_len - 2
+ - ISIS_SYS_ID_LEN);
+ XFREE(MTYPE_ISIS_SUBTLV, lan);
+ break;
+ }
+
+ if (lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG) {
+ lan->sid = stream_get3(s);
+ lan->sid &= MPLS_LABEL_VALUE_MASK;
+ } else {
+ lan->sid = stream_getl(s);
+ }
+ if (mtid == ISIS_MT_IPV4_UNICAST)
+ lan->family = AF_INET;
+ if (mtid == ISIS_MT_IPV6_UNICAST)
+ lan->family = AF_INET6;
+ append_item(&exts->lan_sid,
+ (struct isis_item *)lan);
+ SET_SUBTLV(exts, EXT_LAN_ADJ_SID);
+ }
+ break;
+ /* SRv6 End.X SID as per RFC9352 section #8.1 */
+ case ISIS_SUBTLV_SRV6_ENDX_SID:
+ if (subtlv_len < ISIS_SUBTLV_SRV6_ENDX_SID_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "SRv6 End.X SID");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ struct isis_srv6_endx_sid_subtlv *adj;
+
+ adj = XCALLOC(
+ MTYPE_ISIS_SUBTLV,
+ sizeof(struct
+ isis_srv6_endx_sid_subtlv));
+ adj->flags = stream_getc(s);
+ adj->algorithm = stream_getc(s);
+ adj->weight = stream_getc(s);
+ adj->behavior = stream_getw(s);
+ stream_get(&adj->sid, s, IPV6_MAX_BYTELEN);
+ subsubtlv_len = stream_getc(s);
+
+ adj->subsubtlvs = isis_alloc_subsubtlvs(
+ ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID);
+
+ bool unpacked_known_tlvs = false;
+ if (unpack_tlvs(
+ ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID,
+ subsubtlv_len, s, log,
+ adj->subsubtlvs, indent + 4,
+ &unpacked_known_tlvs)) {
+ XFREE(MTYPE_ISIS_SUBTLV, adj);
+ break;
+ }
+ if (!unpacked_known_tlvs) {
+ isis_free_subsubtlvs(adj->subsubtlvs);
+ adj->subsubtlvs = NULL;
+ }
+
+ append_item(&exts->srv6_endx_sid,
+ (struct isis_item *)adj);
+ SET_SUBTLV(exts, EXT_SRV6_ENDX_SID);
+ }
+ break;
+ /* SRv6 LAN End.X SID as per RFC9352 section #8.2 */
+ case ISIS_SUBTLV_SRV6_LAN_ENDX_SID:
+ if (subtlv_len < ISIS_SUBTLV_SRV6_LAN_ENDX_SID_SIZE) {
+ TLV_SIZE_MISMATCH(log, indent,
+ "SRv6 LAN End.X SID");
+ stream_forward_getp(s, subtlv_len);
+ } else {
+ struct isis_srv6_lan_endx_sid_subtlv *lan;
+
+ lan = XCALLOC(
+ MTYPE_ISIS_SUBTLV,
+ sizeof(struct
+ isis_srv6_lan_endx_sid_subtlv));
+ stream_get(&(lan->neighbor_id), s,
+ ISIS_SYS_ID_LEN);
+ lan->flags = stream_getc(s);
+ lan->algorithm = stream_getc(s);
+ lan->weight = stream_getc(s);
+ lan->behavior = stream_getw(s);
+ stream_get(&lan->sid, s, IPV6_MAX_BYTELEN);
+ subsubtlv_len = stream_getc(s);
+
+ lan->subsubtlvs = isis_alloc_subsubtlvs(
+ ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID);
+
+ bool unpacked_known_tlvs = false;
+ if (unpack_tlvs(
+ ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID,
+ subsubtlv_len, s, log,
+ lan->subsubtlvs, indent + 4,
+ &unpacked_known_tlvs)) {
+ XFREE(MTYPE_ISIS_SUBTLV, lan);
+ break;
+ }
+ if (!unpacked_known_tlvs) {
+ isis_free_subsubtlvs(lan->subsubtlvs);
+ lan->subsubtlvs = NULL;
+ }
+
+ append_item(&exts->srv6_lan_endx_sid,
+ (struct isis_item *)lan);
+ SET_SUBTLV(exts, EXT_SRV6_LAN_ENDX_SID);
+ }
+ break;
+ case ISIS_SUBTLV_ASLA:
+ if (unpack_item_ext_subtlv_asla(mtid, subtlv_len, s,
+ log, indent,
+ exts) < 0) {
+ sbuf_push(log, indent, "TLV parse error");
+ }
+ break;
+ default:
+ /* Skip unknown TLV */
+ stream_forward_getp(s, subtlv_len);
+ break;
+ }
+ sum += subtlv_len + ISIS_SUBTLV_HDR_SIZE;
+ }
+
+ return 0;
+}
+
+/* Functions for Sub-TLV 3 SR Prefix-SID as per RFC8667 section 2.1 */
+static struct isis_item *copy_item_prefix_sid(struct isis_item *i)
+{
+ struct isis_prefix_sid *sid = (struct isis_prefix_sid *)i;
+ struct isis_prefix_sid *rv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv));
+
+ rv->flags = sid->flags;
+ rv->algorithm = sid->algorithm;
+ rv->value = sid->value;
+ return (struct isis_item *)rv;
+}
+
+static void format_item_prefix_sid(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ struct isis_prefix_sid *sid = (struct isis_prefix_sid *)i;
+
+ if (json) {
+ struct json_object *sr_json;
+ sr_json = json_object_new_object();
+ json_object_object_add(json, "sr", sr_json);
+ if (sid->flags & ISIS_PREFIX_SID_VALUE) {
+ json_object_int_add(sr_json, "label", sid->value);
+ } else {
+ json_object_int_add(sr_json, "index", sid->value);
+ }
+ json_object_int_add(sr_json, "alg", sid->algorithm);
+ json_object_string_add(
+ sr_json, "readvertised",
+ ((sid->flags & ISIS_PREFIX_SID_READVERTISED) ? "yes"
+ : ""));
+ json_object_string_add(
+ sr_json, "node",
+ ((sid->flags & ISIS_PREFIX_SID_NODE) ? "yes" : ""));
+ json_object_string_add(sr_json, "php",
+ ((sid->flags & ISIS_PREFIX_SID_NO_PHP)
+ ? "no-php"
+ : "php"));
+ json_object_string_add(
+ sr_json, "explicit-null",
+ ((sid->flags & ISIS_PREFIX_SID_EXPLICIT_NULL) ? "yes"
+ : ""));
+ json_object_string_add(
+ sr_json, "value",
+ ((sid->flags & ISIS_PREFIX_SID_VALUE) ? "yes" : ""));
+ json_object_string_add(
+ sr_json, "local",
+ ((sid->flags & ISIS_PREFIX_SID_LOCAL) ? "yes" : ""));
+
+ } else {
+ sbuf_push(buf, indent, "SR Prefix-SID ");
+ if (sid->flags & ISIS_PREFIX_SID_VALUE) {
+ sbuf_push(buf, 0, "Label: %u, ", sid->value);
+ } else {
+ sbuf_push(buf, 0, "Index: %u, ", sid->value);
+ }
+ sbuf_push(buf, 0, "Algorithm: %hhu, ", sid->algorithm);
+ sbuf_push(buf, 0, "Flags:%s%s%s%s%s%s\n",
+ sid->flags & ISIS_PREFIX_SID_READVERTISED
+ ? " READVERTISED"
+ : "",
+ sid->flags & ISIS_PREFIX_SID_NODE ? " NODE" : "",
+ sid->flags & ISIS_PREFIX_SID_NO_PHP ? " NO-PHP"
+ : " PHP",
+ sid->flags & ISIS_PREFIX_SID_EXPLICIT_NULL
+ ? " EXPLICIT-NULL"
+ : "",
+ sid->flags & ISIS_PREFIX_SID_VALUE ? " VALUE" : "",
+ sid->flags & ISIS_PREFIX_SID_LOCAL ? " LOCAL" : "");
+ }
+}
+
+static void free_item_prefix_sid(struct isis_item *i)
+{
+ XFREE(MTYPE_ISIS_SUBTLV, i);
+}
+
+static int pack_item_prefix_sid(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_prefix_sid *sid = (struct isis_prefix_sid *)i;
+
+ uint8_t size = (sid->flags & ISIS_PREFIX_SID_VALUE) ? 5 : 6;
+
+ if (STREAM_WRITEABLE(s) < size) {
+ *min_len = size;
+ return 1;
+ }
+
+ stream_putc(s, sid->flags);
+ stream_putc(s, sid->algorithm);
+
+ if (sid->flags & ISIS_PREFIX_SID_VALUE) {
+ stream_put3(s, sid->value);
+ } else {
+ stream_putl(s, sid->value);
+ }
+
+ return 0;
+}
+
+static int unpack_item_prefix_sid(uint16_t mtid, uint8_t len, struct stream *s,
+ struct sbuf *log, void *dest, int indent)
+{
+ struct isis_subtlvs *subtlvs = dest;
+ struct isis_prefix_sid sid = {
+ };
+
+ sbuf_push(log, indent, "Unpacking SR Prefix-SID...\n");
+
+ if (len < 5) {
+ sbuf_push(log, indent,
+ "Not enough data left. (expected 5 or more bytes, got %hhu)\n",
+ len);
+ return 1;
+ }
+
+ sid.flags = stream_getc(s);
+ if (!!(sid.flags & ISIS_PREFIX_SID_VALUE)
+ != !!(sid.flags & ISIS_PREFIX_SID_LOCAL)) {
+ sbuf_push(log, indent, "Flags implausible: Local Flag needs to match Value Flag\n");
+ return 1;
+ }
+
+ sid.algorithm = stream_getc(s);
+
+ uint8_t expected_size = (sid.flags & ISIS_PREFIX_SID_VALUE)
+ ? ISIS_SUBTLV_PREFIX_SID_SIZE
+ : ISIS_SUBTLV_PREFIX_SID_SIZE + 1;
+ if (len != expected_size) {
+ sbuf_push(log, indent,
+ "TLV size differs from expected size. (expected %u but got %hhu)\n",
+ expected_size, len);
+ return 1;
+ }
+
+ if (sid.flags & ISIS_PREFIX_SID_VALUE) {
+ sid.value = stream_get3(s);
+ if (!IS_MPLS_UNRESERVED_LABEL(sid.value)) {
+ sbuf_push(log, indent, "Invalid absolute SID %u\n",
+ sid.value);
+ return 1;
+ }
+ } else {
+ sid.value = stream_getl(s);
+ }
+
+ format_item_prefix_sid(mtid, (struct isis_item *)&sid, log, NULL, indent + 2);
+ append_item(&subtlvs->prefix_sids, copy_item_prefix_sid((struct isis_item *)&sid));
+ return 0;
+}
+
+/* Functions for Sub-TVL ??? IPv6 Source Prefix */
+
+static struct prefix_ipv6 *copy_subtlv_ipv6_source_prefix(struct prefix_ipv6 *p)
+{
+ if (!p)
+ return NULL;
+
+ struct prefix_ipv6 *rv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv));
+ rv->family = p->family;
+ rv->prefixlen = p->prefixlen;
+ memcpy(&rv->prefix, &p->prefix, sizeof(rv->prefix));
+ return rv;
+}
+
+static void format_subtlv_ipv6_source_prefix(struct prefix_ipv6 *p,
+ struct sbuf *buf,
+ struct json_object *json,
+ int indent)
+{
+ if (!p)
+ return;
+
+ char prefixbuf[PREFIX2STR_BUFFER];
+ if (json) {
+ prefix2str(p, prefixbuf, sizeof(prefixbuf));
+ json_object_string_add(json, "ipv6-src-prefix", prefixbuf);
+ } else {
+ sbuf_push(buf, indent, "IPv6 Source Prefix: %s\n",
+ prefix2str(p, prefixbuf, sizeof(prefixbuf)));
+ }
+}
+
+static int pack_subtlv_ipv6_source_prefix(struct prefix_ipv6 *p,
+ struct stream *s)
+{
+ if (!p)
+ return 0;
+
+ if (STREAM_WRITEABLE(s) < 3 + (unsigned)PSIZE(p->prefixlen))
+ return 1;
+
+ stream_putc(s, ISIS_SUBTLV_IPV6_SOURCE_PREFIX);
+ stream_putc(s, 1 + PSIZE(p->prefixlen));
+ stream_putc(s, p->prefixlen);
+ stream_put(s, &p->prefix, PSIZE(p->prefixlen));
+ return 0;
+}
+
+static int unpack_subtlv_ipv6_source_prefix(enum isis_tlv_context context,
+ uint8_t tlv_type, uint8_t tlv_len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_subtlvs *subtlvs = dest;
+ struct prefix_ipv6 p = {
+ .family = AF_INET6,
+ };
+
+ sbuf_push(log, indent, "Unpacking IPv6 Source Prefix Sub-TLV...\n");
+
+ if (tlv_len < 1) {
+ sbuf_push(log, indent,
+ "Not enough data left. (expected 1 or more bytes, got %hhu)\n",
+ tlv_len);
+ return 1;
+ }
+
+ p.prefixlen = stream_getc(s);
+ if (p.prefixlen > IPV6_MAX_BITLEN) {
+ sbuf_push(log, indent, "Prefixlen %u is implausible for IPv6\n",
+ p.prefixlen);
+ return 1;
+ }
+
+ if (tlv_len != 1 + PSIZE(p.prefixlen)) {
+ sbuf_push(
+ log, indent,
+ "TLV size differs from expected size for the prefixlen. (expected %u but got %hhu)\n",
+ 1 + PSIZE(p.prefixlen), tlv_len);
+ return 1;
+ }
+
+ stream_get(&p.prefix, s, PSIZE(p.prefixlen));
+
+ if (subtlvs->source_prefix) {
+ sbuf_push(
+ log, indent,
+ "WARNING: source prefix Sub-TLV present multiple times.\n");
+ /* Ignore all but first occurrence of the source prefix Sub-TLV
+ */
+ return 0;
+ }
+
+ subtlvs->source_prefix = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(p));
+ memcpy(subtlvs->source_prefix, &p, sizeof(p));
+ return 0;
+}
+
+/* Functions related to Sub-Sub-TLV 1 SRv6 SID Structure
+ * as per RFC 9352 section #9 */
+static struct isis_srv6_sid_structure_subsubtlv *
+copy_subsubtlv_srv6_sid_structure(
+ struct isis_srv6_sid_structure_subsubtlv *sid_struct)
+{
+ if (!sid_struct)
+ return NULL;
+
+ struct isis_srv6_sid_structure_subsubtlv *rv =
+ XCALLOC(MTYPE_ISIS_SUBSUBTLV, sizeof(*rv));
+
+ rv->loc_block_len = sid_struct->loc_block_len;
+ rv->loc_node_len = sid_struct->loc_node_len;
+ rv->func_len = sid_struct->func_len;
+ rv->arg_len = sid_struct->arg_len;
+
+ return rv;
+}
+
+static void format_subsubtlv_srv6_sid_structure(
+ struct isis_srv6_sid_structure_subsubtlv *sid_struct, struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ if (!sid_struct)
+ return;
+
+ if (json) {
+ struct json_object *sid_struct_json;
+ sid_struct_json = json_object_new_object();
+ json_object_object_add(json, "srv6-sid-structure",
+ sid_struct_json);
+ json_object_int_add(sid_struct_json, "loc-block-len",
+ sid_struct->loc_block_len);
+ json_object_int_add(sid_struct_json, "loc-node-len",
+ sid_struct->loc_node_len);
+ json_object_int_add(sid_struct_json, "func-len",
+ sid_struct->func_len);
+ json_object_int_add(sid_struct_json, "arg-len",
+ sid_struct->arg_len);
+ } else {
+ sbuf_push(buf, indent, "SRv6 SID Structure ");
+ sbuf_push(buf, 0, "Locator Block length: %hhu, ",
+ sid_struct->loc_block_len);
+ sbuf_push(buf, 0, "Locator Node length: %hhu, ",
+ sid_struct->loc_node_len);
+ sbuf_push(buf, 0, "Function length: %hhu, ",
+ sid_struct->func_len);
+ sbuf_push(buf, 0, "Argument length: %hhu, ",
+ sid_struct->arg_len);
+ sbuf_push(buf, 0, "\n");
+ }
+}
+
+static void free_subsubtlv_srv6_sid_structure(
+ struct isis_srv6_sid_structure_subsubtlv *sid_struct)
+{
+ XFREE(MTYPE_ISIS_SUBSUBTLV, sid_struct);
+}
+
+static int pack_subsubtlv_srv6_sid_structure(
+ struct isis_srv6_sid_structure_subsubtlv *sid_struct, struct stream *s)
+{
+ if (!sid_struct)
+ return 0;
+
+ if (STREAM_WRITEABLE(s) < 6) {
+ return 1;
+ }
+
+ stream_putc(s, ISIS_SUBSUBTLV_SRV6_SID_STRUCTURE);
+ stream_putc(s, 4);
+ stream_putc(s, sid_struct->loc_block_len);
+ stream_putc(s, sid_struct->loc_node_len);
+ stream_putc(s, sid_struct->func_len);
+ stream_putc(s, sid_struct->arg_len);
+
+ return 0;
+}
+
+static int unpack_subsubtlv_srv6_sid_structure(
+ enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len,
+ struct stream *s, struct sbuf *log, void *dest, int indent)
+{
+ struct isis_subsubtlvs *subsubtlvs = dest;
+ struct isis_srv6_sid_structure_subsubtlv sid_struct = {};
+
+ sbuf_push(log, indent, "Unpacking SRv6 SID Structure...\n");
+ if (tlv_len != 4) {
+ sbuf_push(
+ log, indent,
+ "Invalid SRv6 SID Structure Sub-Sub-TLV size. (Expected 4 bytes, got %hhu)\n",
+ tlv_len);
+ return 1;
+ }
+
+ sid_struct.loc_block_len = stream_getc(s);
+ sid_struct.loc_node_len = stream_getc(s);
+ sid_struct.func_len = stream_getc(s);
+ sid_struct.arg_len = stream_getc(s);
+
+ subsubtlvs->srv6_sid_structure =
+ copy_subsubtlv_srv6_sid_structure(&sid_struct);
+
+ return 0;
+}
+
+static struct isis_item *copy_item(enum isis_tlv_context context,
+ enum isis_tlv_type type,
+ struct isis_item *item);
+static void copy_items(enum isis_tlv_context context, enum isis_tlv_type type,
+ struct isis_item_list *src, struct isis_item_list *dest);
+static void format_items_(uint16_t mtid, enum isis_tlv_context context,
+ enum isis_tlv_type type, struct isis_item_list *items,
+ struct sbuf *buf, struct json_object *json,
+ int indent);
+#define format_items(...) format_items_(ISIS_MT_IPV4_UNICAST, __VA_ARGS__)
+static void free_items(enum isis_tlv_context context, enum isis_tlv_type type,
+ struct isis_item_list *items);
+static int pack_items_(uint16_t mtid, enum isis_tlv_context context,
+ enum isis_tlv_type type, struct isis_item_list *items,
+ struct stream *s, struct isis_tlvs **fragment_tlvs,
+ const struct pack_order_entry *pe,
+ struct isis_tlvs *(*new_fragment)(struct list *l),
+ struct list *new_fragment_arg);
+#define pack_items(...) pack_items_(ISIS_MT_IPV4_UNICAST, __VA_ARGS__)
+
+/* Functions related to Sub-Sub-TLVs in general */
+
+struct isis_subsubtlvs *isis_alloc_subsubtlvs(enum isis_tlv_context context)
+{
+ struct isis_subsubtlvs *result;
+
+ result = XCALLOC(MTYPE_ISIS_SUBSUBTLV, sizeof(*result));
+ result->context = context;
+
+ return result;
+}
+
+static struct isis_subsubtlvs *
+isis_copy_subsubtlvs(struct isis_subsubtlvs *subsubtlvs)
+{
+ if (!subsubtlvs)
+ return NULL;
+
+ struct isis_subsubtlvs *rv = XCALLOC(MTYPE_ISIS_SUBSUBTLV, sizeof(*rv));
+
+ rv->context = subsubtlvs->context;
+
+ rv->srv6_sid_structure = copy_subsubtlv_srv6_sid_structure(
+ subsubtlvs->srv6_sid_structure);
+
+ return rv;
+}
+
+static void isis_format_subsubtlvs(struct isis_subsubtlvs *subsubtlvs,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ format_subsubtlv_srv6_sid_structure(subsubtlvs->srv6_sid_structure, buf,
+ json, indent);
+}
+
+static void isis_free_subsubtlvs(struct isis_subsubtlvs *subsubtlvs)
+{
+ if (!subsubtlvs)
+ return;
+
+ free_subsubtlv_srv6_sid_structure(subsubtlvs->srv6_sid_structure);
+
+ XFREE(MTYPE_ISIS_SUBSUBTLV, subsubtlvs);
+}
+
+static int isis_pack_subsubtlvs(struct isis_subsubtlvs *subsubtlvs,
+ struct stream *s)
+{
+ int rv;
+ size_t subsubtlv_len_pos = stream_get_endp(s);
+
+ if (STREAM_WRITEABLE(s) < 1)
+ return 1;
+
+ stream_putc(s, 0); /* Put 0 as Sub-Sub-TLVs length, filled in later */
+
+ rv = pack_subsubtlv_srv6_sid_structure(subsubtlvs->srv6_sid_structure,
+ s);
+ if (rv)
+ return rv;
+
+ size_t subsubtlv_len = stream_get_endp(s) - subsubtlv_len_pos - 1;
+ if (subsubtlv_len > 255)
+ return 1;
+
+ stream_putc_at(s, subsubtlv_len_pos, subsubtlv_len);
+ return 0;
+}
+
+/* Functions related to subtlvs */
+
+static struct isis_subtlvs *isis_alloc_subtlvs(enum isis_tlv_context context)
+{
+ struct isis_subtlvs *result;
+
+ result = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*result));
+ result->context = context;
+
+ init_item_list(&result->prefix_sids);
+ init_item_list(&result->srv6_end_sids);
+
+ return result;
+}
+
+static struct isis_subtlvs *copy_subtlvs(struct isis_subtlvs *subtlvs)
+{
+ if (!subtlvs)
+ return NULL;
+
+ struct isis_subtlvs *rv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv));
+
+ rv->context = subtlvs->context;
+
+ copy_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID,
+ &subtlvs->prefix_sids, &rv->prefix_sids);
+
+ rv->source_prefix =
+ copy_subtlv_ipv6_source_prefix(subtlvs->source_prefix);
+
+ copy_items(subtlvs->context, ISIS_SUBTLV_SRV6_END_SID,
+ &subtlvs->srv6_end_sids, &rv->srv6_end_sids);
+
+ return rv;
+}
+
+static void format_subtlvs(struct isis_subtlvs *subtlvs, struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ format_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID,
+ &subtlvs->prefix_sids, buf, json, indent);
+
+ format_subtlv_ipv6_source_prefix(subtlvs->source_prefix, buf, json, indent);
+
+ format_items(subtlvs->context, ISIS_SUBTLV_SRV6_END_SID,
+ &subtlvs->srv6_end_sids, buf, json, indent);
+}
+
+static void isis_free_subtlvs(struct isis_subtlvs *subtlvs)
+{
+ if (!subtlvs)
+ return;
+
+ free_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID,
+ &subtlvs->prefix_sids);
+
+ XFREE(MTYPE_ISIS_SUBTLV, subtlvs->source_prefix);
+
+ free_items(subtlvs->context, ISIS_SUBTLV_SRV6_END_SID,
+ &subtlvs->srv6_end_sids);
+
+ XFREE(MTYPE_ISIS_SUBTLV, subtlvs);
+}
+
+static int pack_subtlvs(struct isis_subtlvs *subtlvs, struct stream *s)
+{
+ int rv;
+ size_t subtlv_len_pos = stream_get_endp(s);
+
+ if (STREAM_WRITEABLE(s) < 1)
+ return 1;
+
+ stream_putc(s, 0); /* Put 0 as subtlvs length, filled in later */
+
+ rv = pack_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID,
+ &subtlvs->prefix_sids, s, NULL, NULL, NULL, NULL);
+ if (rv)
+ return rv;
+
+ rv = pack_subtlv_ipv6_source_prefix(subtlvs->source_prefix, s);
+ if (rv)
+ return rv;
+
+ rv = pack_items(subtlvs->context, ISIS_SUBTLV_SRV6_END_SID,
+ &subtlvs->srv6_end_sids, s, NULL, NULL, NULL, NULL);
+ if (rv)
+ return rv;
+
+ size_t subtlv_len = stream_get_endp(s) - subtlv_len_pos - 1;
+ if (subtlv_len > 255)
+ return 1;
+
+ stream_putc_at(s, subtlv_len_pos, subtlv_len);
+ return 0;
+}
+
+static int unpack_tlvs(enum isis_tlv_context context, size_t avail_len,
+ struct stream *stream, struct sbuf *log, void *dest,
+ int indent, bool *unpacked_known_tlvs);
+
+/* Functions for Sub-TLV 5 SRv6 End SID as per RFC 9352 section #7.2 */
+static struct isis_item *copy_item_srv6_end_sid(struct isis_item *i)
+{
+ struct isis_srv6_end_sid_subtlv *sid =
+ (struct isis_srv6_end_sid_subtlv *)i;
+ struct isis_srv6_end_sid_subtlv *rv =
+ XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv));
+
+ rv->behavior = sid->behavior;
+ rv->sid = sid->sid;
+ rv->subsubtlvs = isis_copy_subsubtlvs(sid->subsubtlvs);
+
+ return (struct isis_item *)rv;
+}
+
+static void format_item_srv6_end_sid(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ struct isis_srv6_end_sid_subtlv *sid =
+ (struct isis_srv6_end_sid_subtlv *)i;
+
+ if (json) {
+ struct json_object *sid_json;
+ sid_json = json_object_new_object();
+ json_object_object_add(json, "srv6-end-sid", sid_json);
+ json_object_string_add(sid_json, "endpoint-behavior",
+ seg6local_action2str(sid->behavior));
+ json_object_string_addf(sid_json, "sid-value", "%pI6",
+ &sid->sid);
+ if (sid->subsubtlvs) {
+ struct json_object *subtlvs_json;
+ subtlvs_json = json_object_new_object();
+ json_object_object_add(sid_json, "subsubtlvs",
+ subtlvs_json);
+ isis_format_subsubtlvs(sid->subsubtlvs, NULL,
+ subtlvs_json, 0);
+ }
+ } else {
+ sbuf_push(buf, indent, "SRv6 End SID ");
+ sbuf_push(buf, 0, "Endpoint Behavior: %s, ",
+ seg6local_action2str(sid->behavior));
+ sbuf_push(buf, 0, "SID value: %pI6\n", &sid->sid);
+
+ if (sid->subsubtlvs) {
+ sbuf_push(buf, indent, " Sub-Sub-TLVs:\n");
+ isis_format_subsubtlvs(sid->subsubtlvs, buf, NULL,
+ indent + 4);
+ }
+ }
+}
+
+static void free_item_srv6_end_sid(struct isis_item *i)
+{
+ struct isis_srv6_end_sid_subtlv *item =
+ (struct isis_srv6_end_sid_subtlv *)i;
+
+ isis_free_subsubtlvs(item->subsubtlvs);
+ XFREE(MTYPE_ISIS_SUBTLV, i);
+}
+
+static int pack_item_srv6_end_sid(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_srv6_end_sid_subtlv *sid =
+ (struct isis_srv6_end_sid_subtlv *)i;
+
+ if (STREAM_WRITEABLE(s) < 19) {
+ *min_len = 19;
+ return 1;
+ }
+
+ stream_putc(s, sid->flags);
+ stream_putw(s, sid->behavior);
+ stream_put(s, &sid->sid, IPV6_MAX_BYTELEN);
+
+ if (sid->subsubtlvs) {
+ /* Pack Sub-Sub-TLVs */
+ if (isis_pack_subsubtlvs(sid->subsubtlvs, s))
+ return 1;
+ } else {
+ /* No Sub-Sub-TLVs */
+ if (STREAM_WRITEABLE(s) < 1) {
+ *min_len = 20;
+ return 1;
+ }
+
+ /* Put 0 as Sub-Sub-TLV length, because we have no Sub-Sub-TLVs
+ */
+ stream_putc(s, 0);
+ }
+
+ return 0;
+}
+
+static int unpack_item_srv6_end_sid(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_subtlvs *subtlvs = dest;
+ struct isis_srv6_end_sid_subtlv *sid = NULL;
+ size_t consume;
+ uint8_t subsubtlv_len;
+
+ sbuf_push(log, indent, "Unpacking SRv6 End SID...\n");
+
+ consume = 19;
+ if (len < consume) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left. (expected 19 or more bytes, got %hhu)\n",
+ len);
+ goto out;
+ }
+
+ sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*sid));
+
+ sid->flags = stream_getc(s);
+ sid->behavior = stream_getw(s);
+ stream_get(&sid->sid, s, IPV6_MAX_BYTELEN);
+
+ format_item_srv6_end_sid(mtid, (struct isis_item *)sid, log, NULL,
+ indent + 2);
+
+ /* Process Sub-Sub-TLVs */
+ consume += 1;
+ if (len < consume) {
+ sbuf_push(
+ log, indent,
+ "Expected 1 byte of Sub-Sub-TLV len, but no more data persent.\n");
+ goto out;
+ }
+ subsubtlv_len = stream_getc(s);
+
+ consume += subsubtlv_len;
+ if (len < consume) {
+ sbuf_push(log, indent,
+ "Expected %hhu bytes of Sub-Sub-TLVs, but only %u bytes available.\n",
+ subsubtlv_len, len - ((uint8_t)consume - subsubtlv_len));
+ goto out;
+ }
+
+ sid->subsubtlvs =
+ isis_alloc_subsubtlvs(ISIS_CONTEXT_SUBSUBTLV_SRV6_END_SID);
+
+ bool unpacked_known_tlvs = false;
+ if (unpack_tlvs(ISIS_CONTEXT_SUBSUBTLV_SRV6_END_SID, subsubtlv_len, s,
+ log, sid->subsubtlvs, indent + 4,
+ &unpacked_known_tlvs)) {
+ goto out;
+ }
+ if (!unpacked_known_tlvs) {
+ isis_free_subsubtlvs(sid->subsubtlvs);
+ sid->subsubtlvs = NULL;
+ }
+
+ append_item(&subtlvs->srv6_end_sids, (struct isis_item *)sid);
+ return 0;
+out:
+ if (sid)
+ free_item_srv6_end_sid((struct isis_item *)sid);
+ return 1;
+}
+
+/* Functions related to TLVs 1 Area Addresses */
+
+static struct isis_item *copy_item_area_address(struct isis_item *i)
+{
+ struct isis_area_address *addr = (struct isis_area_address *)i;
+ struct isis_area_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->len = addr->len;
+ memcpy(rv->addr, addr->addr, addr->len);
+ return (struct isis_item *)rv;
+}
+
+static void format_item_area_address(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ struct isis_area_address *addr = (struct isis_area_address *)i;
+ struct iso_address iso_addr;
+
+ memcpy(iso_addr.area_addr, addr->addr, ISO_ADDR_SIZE);
+ iso_addr.addr_len = addr->len;
+ if (json)
+ json_object_string_addf(json, "area-addr", "%pIS", &iso_addr);
+ else
+ sbuf_push(buf, indent, "Area Address: %pIS\n", &iso_addr);
+}
+
+static void free_item_area_address(struct isis_item *i)
+{
+ XFREE(MTYPE_ISIS_TLV, i);
+}
+
+static int pack_item_area_address(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_area_address *addr = (struct isis_area_address *)i;
+
+ if (STREAM_WRITEABLE(s) < (unsigned)1 + addr->len) {
+ *min_len = (unsigned)1 + addr->len;
+ return 1;
+ }
+ stream_putc(s, addr->len);
+ stream_put(s, addr->addr, addr->len);
+ return 0;
+}
+
+static int unpack_item_area_address(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+ struct isis_area_address *rv = NULL;
+
+ sbuf_push(log, indent, "Unpack area address...\n");
+ if (len < 1) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left. (Expected 1 byte of address length, got %hhu)\n",
+ len);
+ goto out;
+ }
+
+ rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ rv->len = stream_getc(s);
+
+ if (len < 1 + rv->len) {
+ sbuf_push(log, indent, "Not enough data left. (Expected %hhu bytes of address, got %u)\n",
+ rv->len, len - 1);
+ goto out;
+ }
+
+ if (rv->len < 1 || rv->len > 20) {
+ sbuf_push(log, indent,
+ "Implausible area address length %hhu\n",
+ rv->len);
+ goto out;
+ }
+
+ stream_get(rv->addr, s, rv->len);
+
+ format_item_area_address(ISIS_MT_IPV4_UNICAST, (struct isis_item *)rv,
+ log, NULL, indent + 2);
+ append_item(&tlvs->area_addresses, (struct isis_item *)rv);
+ return 0;
+out:
+ XFREE(MTYPE_ISIS_TLV, rv);
+ return 1;
+}
+
+/* Functions related to TLV 2 (Old-Style) IS Reach */
+static struct isis_item *copy_item_oldstyle_reach(struct isis_item *i)
+{
+ struct isis_oldstyle_reach *r = (struct isis_oldstyle_reach *)i;
+ struct isis_oldstyle_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ memcpy(rv->id, r->id, 7);
+ rv->metric = r->metric;
+ return (struct isis_item *)rv;
+}
+
+static void format_item_oldstyle_reach(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ struct isis_oldstyle_reach *r = (struct isis_oldstyle_reach *)i;
+ char sys_id[ISO_SYSID_STRLEN];
+
+ snprintfrr(sys_id, ISO_SYSID_STRLEN, "%pPN", r->id);
+ if (json) {
+ struct json_object *old_json;
+ old_json = json_object_new_object();
+ json_object_object_add(json, "old-reach-style", old_json);
+ json_object_string_add(old_json, "is-reach", sys_id);
+ json_object_int_add(old_json, "metric", r->metric);
+ } else
+ sbuf_push(buf, indent, "IS Reachability: %s (Metric: %hhu)\n",
+ sys_id, r->metric);
+}
+
+static void free_item_oldstyle_reach(struct isis_item *i)
+{
+ XFREE(MTYPE_ISIS_TLV, i);
+}
+
+static int pack_item_oldstyle_reach(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_oldstyle_reach *r = (struct isis_oldstyle_reach *)i;
+
+ if (STREAM_WRITEABLE(s) < 11) {
+ *min_len = 11;
+ return 1;
+ }
+
+ stream_putc(s, r->metric);
+ stream_putc(s, 0x80); /* delay metric - unsupported */
+ stream_putc(s, 0x80); /* expense metric - unsupported */
+ stream_putc(s, 0x80); /* error metric - unsupported */
+ stream_put(s, r->id, 7);
+
+ return 0;
+}
+
+static int unpack_item_oldstyle_reach(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpack oldstyle reach...\n");
+ if (len < 11) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left.(Expected 11 bytes of reach information, got %hhu)\n",
+ len);
+ return 1;
+ }
+
+ struct isis_oldstyle_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ rv->metric = stream_getc(s);
+ if ((rv->metric & 0x3f) != rv->metric) {
+ sbuf_push(log, indent, "Metric has unplausible format\n");
+ rv->metric &= 0x3f;
+ }
+ stream_forward_getp(s, 3); /* Skip other metrics */
+ stream_get(rv->id, s, 7);
+
+ format_item_oldstyle_reach(mtid, (struct isis_item *)rv, log, NULL,
+ indent + 2);
+ append_item(&tlvs->oldstyle_reach, (struct isis_item *)rv);
+ return 0;
+}
+
+/* Functions related to TLV 6 LAN Neighbors */
+static struct isis_item *copy_item_lan_neighbor(struct isis_item *i)
+{
+ struct isis_lan_neighbor *n = (struct isis_lan_neighbor *)i;
+ struct isis_lan_neighbor *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ memcpy(rv->mac, n->mac, 6);
+ return (struct isis_item *)rv;
+}
+
+static void format_item_lan_neighbor(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ struct isis_lan_neighbor *n = (struct isis_lan_neighbor *)i;
+ char sys_id[ISO_SYSID_STRLEN];
+
+ snprintfrr(sys_id, ISO_SYSID_STRLEN, "%pSY", n->mac);
+ if (json)
+ json_object_string_add(json, "lan-neighbor", sys_id);
+ else
+ sbuf_push(buf, indent, "LAN Neighbor: %s\n", sys_id);
+}
+
+static void free_item_lan_neighbor(struct isis_item *i)
+{
+ XFREE(MTYPE_ISIS_TLV, i);
+}
+
+static int pack_item_lan_neighbor(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_lan_neighbor *n = (struct isis_lan_neighbor *)i;
+
+ if (STREAM_WRITEABLE(s) < 6) {
+ *min_len = 6;
+ return 1;
+ }
+
+ stream_put(s, n->mac, 6);
+
+ return 0;
+}
+
+static int unpack_item_lan_neighbor(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpack LAN neighbor...\n");
+ if (len < 6) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left.(Expected 6 bytes of mac, got %hhu)\n",
+ len);
+ return 1;
+ }
+
+ struct isis_lan_neighbor *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ stream_get(rv->mac, s, 6);
+
+ format_item_lan_neighbor(mtid, (struct isis_item *)rv, log, NULL, indent + 2);
+ append_item(&tlvs->lan_neighbor, (struct isis_item *)rv);
+ return 0;
+}
+
+/* Functions related to TLV 9 LSP Entry */
+static struct isis_item *copy_item_lsp_entry(struct isis_item *i)
+{
+ struct isis_lsp_entry *e = (struct isis_lsp_entry *)i;
+ struct isis_lsp_entry *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->rem_lifetime = e->rem_lifetime;
+ memcpy(rv->id, e->id, sizeof(rv->id));
+ rv->seqno = e->seqno;
+ rv->checksum = e->checksum;
+
+ return (struct isis_item *)rv;
+}
+
+static void format_item_lsp_entry(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ struct isis_lsp_entry *e = (struct isis_lsp_entry *)i;
+ char sys_id[ISO_SYSID_STRLEN];
+
+ snprintfrr(sys_id, ISO_SYSID_STRLEN, "%pLS", e->id);
+ if (json) {
+ char buf[255];
+ struct json_object *lsp_json;
+ lsp_json = json_object_new_object();
+ json_object_object_add(json, "lsp-entry", lsp_json);
+ json_object_string_add(lsp_json, "id", sys_id);
+ snprintfrr(buf,sizeof(buf),"0x%08x",e->seqno);
+ json_object_string_add(lsp_json, "seq", buf);
+ snprintfrr(buf,sizeof(buf),"0x%04hx",e->checksum);
+ json_object_string_add(lsp_json, "chksum", buf);
+ json_object_int_add(lsp_json, "lifetime", e->checksum);
+ } else
+ sbuf_push(
+ buf, indent,
+ "LSP Entry: %s, seq 0x%08x, cksum 0x%04hx, lifetime %hus\n",
+ sys_id, e->seqno, e->checksum, e->rem_lifetime);
+}
+
+static void free_item_lsp_entry(struct isis_item *i)
+{
+ XFREE(MTYPE_ISIS_TLV, i);
+}
+
+static int pack_item_lsp_entry(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_lsp_entry *e = (struct isis_lsp_entry *)i;
+
+ if (STREAM_WRITEABLE(s) < 16) {
+ *min_len = 16;
+ return 1;
+ }
+
+ stream_putw(s, e->rem_lifetime);
+ stream_put(s, e->id, 8);
+ stream_putl(s, e->seqno);
+ stream_putw(s, e->checksum);
+
+ return 0;
+}
+
+static int unpack_item_lsp_entry(uint16_t mtid, uint8_t len, struct stream *s,
+ struct sbuf *log, void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpack LSP entry...\n");
+ if (len < 16) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left. (Expected 16 bytes of LSP info, got %hhu",
+ len);
+ return 1;
+ }
+
+ struct isis_lsp_entry *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ rv->rem_lifetime = stream_getw(s);
+ stream_get(rv->id, s, 8);
+ rv->seqno = stream_getl(s);
+ rv->checksum = stream_getw(s);
+
+ format_item_lsp_entry(mtid, (struct isis_item *)rv, log, NULL, indent + 2);
+ append_item(&tlvs->lsp_entries, (struct isis_item *)rv);
+ return 0;
+}
+
+/* Functions related to TLVs 22/222 Extended Reach/MT Reach */
+
+static struct isis_item *copy_item_extended_reach(struct isis_item *i)
+{
+ struct isis_extended_reach *r = (struct isis_extended_reach *)i;
+ struct isis_extended_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ memcpy(rv->id, r->id, 7);
+ rv->metric = r->metric;
+
+ if (r->subtlvs)
+ rv->subtlvs = copy_item_ext_subtlvs(r->subtlvs, -1);
+
+ return (struct isis_item *)rv;
+}
+
+static void format_item_extended_reach(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ struct isis_extended_reach *r = (struct isis_extended_reach *)i;
+ char sys_id[ISO_SYSID_STRLEN];
+
+ snprintfrr(sys_id, ISO_SYSID_STRLEN, "%pPN", r->id);
+ if (json) {
+ struct json_object *reach_json;
+ reach_json = json_object_new_object();
+ json_object_object_add(json, "ext-reach", reach_json);
+ json_object_string_add(
+ reach_json, "mt-id",
+ (mtid == ISIS_MT_IPV4_UNICAST) ? "Extended" : "MT");
+ json_object_string_add(reach_json, "id", sys_id);
+ json_object_int_add(reach_json, "metric", r->metric);
+ if (mtid != ISIS_MT_IPV4_UNICAST)
+ json_object_string_add(reach_json, "mt-name",
+ isis_mtid2str(mtid));
+
+ if (r->subtlvs)
+ format_item_ext_subtlvs(r->subtlvs, NULL, json,
+ indent + 2, mtid);
+ } else {
+ sbuf_push(buf, indent, "%s Reachability: %s (Metric: %u)",
+ (mtid == ISIS_MT_IPV4_UNICAST) ? "Extended" : "MT",
+ sys_id, r->metric);
+ if (mtid != ISIS_MT_IPV4_UNICAST)
+ sbuf_push(buf, 0, " %s", isis_mtid2str(mtid));
+ sbuf_push(buf, 0, "\n");
+
+ if (r->subtlvs)
+ format_item_ext_subtlvs(r->subtlvs, buf, NULL,
+ indent + 2, mtid);
+ }
+}
+
+static void free_item_extended_reach(struct isis_item *i)
+{
+ struct isis_extended_reach *item = (struct isis_extended_reach *)i;
+
+ if (item->subtlvs != NULL)
+ free_item_ext_subtlvs(item->subtlvs);
+ XFREE(MTYPE_ISIS_TLV, item);
+}
+
+static int pack_item_extended_reach(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_extended_reach *r = (struct isis_extended_reach *)i;
+ size_t len;
+ size_t len_pos;
+
+ if (STREAM_WRITEABLE(s) < 11 + ISIS_SUBTLV_MAX_SIZE) {
+ *min_len = 11 + ISIS_SUBTLV_MAX_SIZE;
+ return 1;
+ }
+
+ stream_put(s, r->id, sizeof(r->id));
+ stream_put3(s, r->metric);
+ len_pos = stream_get_endp(s);
+ /* Real length will be adjust after adding subTLVs */
+ stream_putc(s, 11);
+ if (r->subtlvs)
+ pack_item_ext_subtlvs(r->subtlvs, s, min_len);
+ /* Adjust length */
+ len = stream_get_endp(s) - len_pos - 1;
+ stream_putc_at(s, len_pos, len);
+ return 0;
+}
+
+static int unpack_item_extended_reach(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+ struct isis_extended_reach *rv = NULL;
+ uint8_t subtlv_len;
+ struct isis_item_list *items;
+
+ if (mtid == ISIS_MT_IPV4_UNICAST) {
+ items = &tlvs->extended_reach;
+ } else {
+ items = isis_get_mt_items(&tlvs->mt_reach, mtid);
+ }
+
+ sbuf_push(log, indent, "Unpacking %s reachability...\n",
+ (mtid == ISIS_MT_IPV4_UNICAST) ? "extended" : "mt");
+
+ if (len < 11) {
+ sbuf_push(log, indent,
+ "Not enough data left. (expected 11 or more bytes, got %hhu)\n",
+ len);
+ goto out;
+ }
+
+ rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ stream_get(rv->id, s, 7);
+ rv->metric = stream_get3(s);
+ subtlv_len = stream_getc(s);
+
+ if ((size_t)len < ((size_t)11) + subtlv_len) {
+ sbuf_push(log, indent,
+ "Not enough data left for subtlv size %hhu, there are only %u bytes left.\n",
+ subtlv_len, len - 11);
+ goto out;
+ }
+
+ sbuf_push(log, indent, "Storing %hhu bytes of subtlvs\n",
+ subtlv_len);
+
+ if (subtlv_len) {
+ if (unpack_item_ext_subtlvs(mtid, subtlv_len, s, log, rv,
+ indent + 4)) {
+ goto out;
+ }
+ }
+
+ format_item_extended_reach(mtid, (struct isis_item *)rv, log, NULL,
+ indent + 2);
+ append_item(items, (struct isis_item *)rv);
+ return 0;
+out:
+ if (rv)
+ free_item_extended_reach((struct isis_item *)rv);
+
+ return 1;
+}
+
+/* Functions related to TLV 128 (Old-Style) IP Reach */
+static struct isis_item *copy_item_oldstyle_ip_reach(struct isis_item *i)
+{
+ struct isis_oldstyle_ip_reach *r = (struct isis_oldstyle_ip_reach *)i;
+ struct isis_oldstyle_ip_reach *rv =
+ XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->metric = r->metric;
+ rv->prefix = r->prefix;
+ return (struct isis_item *)rv;
+}
+
+static void format_item_oldstyle_ip_reach(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ struct isis_oldstyle_ip_reach *r = (struct isis_oldstyle_ip_reach *)i;
+ char prefixbuf[PREFIX2STR_BUFFER];
+
+ if (json) {
+ struct json_object *old_json;
+ old_json = json_object_new_object();
+ json_object_object_add(json, "old-ip-reach-style", old_json);
+ json_object_string_add(old_json, "prefix",
+ prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)));
+ json_object_int_add(old_json, "metric", r->metric);
+ } else
+ sbuf_push(buf, indent, "IP Reachability: %s (Metric: %hhu)\n",
+ prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)),
+ r->metric);
+}
+
+static void free_item_oldstyle_ip_reach(struct isis_item *i)
+{
+ XFREE(MTYPE_ISIS_TLV, i);
+}
+
+static int pack_item_oldstyle_ip_reach(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_oldstyle_ip_reach *r = (struct isis_oldstyle_ip_reach *)i;
+
+ if (STREAM_WRITEABLE(s) < 12) {
+ *min_len = 12;
+ return 1;
+ }
+
+ stream_putc(s, r->metric);
+ stream_putc(s, 0x80); /* delay metric - unsupported */
+ stream_putc(s, 0x80); /* expense metric - unsupported */
+ stream_putc(s, 0x80); /* error metric - unsupported */
+ stream_put(s, &r->prefix.prefix, 4);
+
+ struct in_addr mask;
+ masklen2ip(r->prefix.prefixlen, &mask);
+ stream_put(s, &mask, sizeof(mask));
+
+ return 0;
+}
+
+static int unpack_item_oldstyle_ip_reach(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ sbuf_push(log, indent, "Unpack oldstyle ip reach...\n");
+ if (len < 12) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left.(Expected 12 bytes of reach information, got %hhu)\n",
+ len);
+ return 1;
+ }
+
+ struct isis_oldstyle_ip_reach *rv =
+ XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ rv->metric = stream_getc(s);
+ if ((rv->metric & 0x7f) != rv->metric) {
+ sbuf_push(log, indent, "Metric has unplausible format\n");
+ rv->metric &= 0x7f;
+ }
+ stream_forward_getp(s, 3); /* Skip other metrics */
+ rv->prefix.family = AF_INET;
+ stream_get(&rv->prefix.prefix, s, 4);
+
+ struct in_addr mask;
+ stream_get(&mask, s, 4);
+ rv->prefix.prefixlen = ip_masklen(mask);
+
+ format_item_oldstyle_ip_reach(mtid, (struct isis_item *)rv, log, NULL,
+ indent + 2);
+ append_item(dest, (struct isis_item *)rv);
+ return 0;
+}
+
+
+/* Functions related to TLV 129 protocols supported */
+
+static void copy_tlv_protocols_supported(struct isis_protocols_supported *src,
+ struct isis_protocols_supported *dest)
+{
+ if (!src->protocols || !src->count)
+ return;
+ dest->count = src->count;
+ dest->protocols = XCALLOC(MTYPE_ISIS_TLV, src->count);
+ memcpy(dest->protocols, src->protocols, src->count);
+}
+
+static void format_tlv_protocols_supported(struct isis_protocols_supported *p,
+ struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ if (!p || !p->count || !p->protocols)
+ return;
+
+ if (json) {
+ struct json_object *protocol_json;
+ char buf[255];
+
+ protocol_json = json_object_new_object();
+ json_object_object_add(json, "protocols-supported",
+ protocol_json);
+ for (uint8_t i = 0; i < p->count; i++) {
+ snprintfrr(buf, sizeof(buf), "%d", i);
+ json_object_string_add(protocol_json, buf,
+ nlpid2str(p->protocols[i]));
+ }
+ } else {
+ sbuf_push(buf, indent, "Protocols Supported: ");
+ for (uint8_t i = 0; i < p->count; i++) {
+ sbuf_push(buf, 0, "%s%s", nlpid2str(p->protocols[i]),
+ (i + 1 < p->count) ? ", " : "");
+ }
+ sbuf_push(buf, 0, "\n");
+ }
+}
+
+static void free_tlv_protocols_supported(struct isis_protocols_supported *p)
+{
+ XFREE(MTYPE_ISIS_TLV, p->protocols);
+}
+
+static int pack_tlv_protocols_supported(struct isis_protocols_supported *p,
+ struct stream *s)
+{
+ if (!p || !p->count || !p->protocols)
+ return 0;
+
+ if (STREAM_WRITEABLE(s) < (unsigned)(p->count + 2))
+ return 1;
+
+ stream_putc(s, ISIS_TLV_PROTOCOLS_SUPPORTED);
+ stream_putc(s, p->count);
+ stream_put(s, p->protocols, p->count);
+ return 0;
+}
+
+static int unpack_tlv_protocols_supported(enum isis_tlv_context context,
+ uint8_t tlv_type, uint8_t tlv_len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpacking Protocols Supported TLV...\n");
+ if (!tlv_len) {
+ sbuf_push(log, indent, "WARNING: No protocols included\n");
+ return 0;
+ }
+ if (tlvs->protocols_supported.protocols) {
+ sbuf_push(
+ log, indent,
+ "WARNING: protocols supported TLV present multiple times.\n");
+ stream_forward_getp(s, tlv_len);
+ return 0;
+ }
+
+ tlvs->protocols_supported.count = tlv_len;
+ tlvs->protocols_supported.protocols = XCALLOC(MTYPE_ISIS_TLV, tlv_len);
+ stream_get(tlvs->protocols_supported.protocols, s, tlv_len);
+
+ format_tlv_protocols_supported(&tlvs->protocols_supported, log, NULL,
+ indent + 2);
+ return 0;
+}
+
+/* Functions related to TLV 132 IPv4 Interface addresses */
+static struct isis_item *copy_item_ipv4_address(struct isis_item *i)
+{
+ struct isis_ipv4_address *a = (struct isis_ipv4_address *)i;
+ struct isis_ipv4_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->addr = a->addr;
+ return (struct isis_item *)rv;
+}
+
+static void format_item_ipv4_address(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ struct isis_ipv4_address *a = (struct isis_ipv4_address *)i;
+ char addrbuf[INET_ADDRSTRLEN];
+
+ inet_ntop(AF_INET, &a->addr, addrbuf, sizeof(addrbuf));
+ if (json) {
+ json_object_string_add(json, "ipv4", addrbuf);
+ } else {
+ sbuf_push(buf, indent, "IPv4 Interface Address: %s\n", addrbuf);
+ }
+}
+
+static void free_item_ipv4_address(struct isis_item *i)
+{
+ XFREE(MTYPE_ISIS_TLV, i);
+}
+
+static int pack_item_ipv4_address(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_ipv4_address *a = (struct isis_ipv4_address *)i;
+
+ if (STREAM_WRITEABLE(s) < 4) {
+ *min_len = 4;
+ return 1;
+ }
+
+ stream_put(s, &a->addr, 4);
+
+ return 0;
+}
+
+static int unpack_item_ipv4_address(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpack IPv4 Interface address...\n");
+ if (len < 4) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left.(Expected 4 bytes of IPv4 address, got %hhu)\n",
+ len);
+ return 1;
+ }
+
+ struct isis_ipv4_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ stream_get(&rv->addr, s, 4);
+
+ format_item_ipv4_address(mtid, (struct isis_item *)rv, log, NULL, indent + 2);
+ append_item(&tlvs->ipv4_address, (struct isis_item *)rv);
+ return 0;
+}
+
+
+/* Functions related to TLV 232 IPv6 Interface addresses */
+static struct isis_item *copy_item_ipv6_address(struct isis_item *i)
+{
+ struct isis_ipv6_address *a = (struct isis_ipv6_address *)i;
+ struct isis_ipv6_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->addr = a->addr;
+ return (struct isis_item *)rv;
+}
+
+static void format_item_ipv6_address(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ struct isis_ipv6_address *a = (struct isis_ipv6_address *)i;
+ char addrbuf[INET6_ADDRSTRLEN];
+
+ inet_ntop(AF_INET6, &a->addr, addrbuf, sizeof(addrbuf));
+ if (json)
+ json_object_string_add(json, "ipv6", addrbuf);
+ else
+ sbuf_push(buf, indent, "IPv6 Interface Address: %s\n", addrbuf);
+}
+
+static void free_item_ipv6_address(struct isis_item *i)
+{
+ XFREE(MTYPE_ISIS_TLV, i);
+}
+
+static int pack_item_ipv6_address(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_ipv6_address *a = (struct isis_ipv6_address *)i;
+
+ if (STREAM_WRITEABLE(s) < IPV6_MAX_BYTELEN) {
+ *min_len = IPV6_MAX_BYTELEN;
+ return 1;
+ }
+
+ stream_put(s, &a->addr, IPV6_MAX_BYTELEN);
+
+ return 0;
+}
+
+static int unpack_item_ipv6_address(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpack IPv6 Interface address...\n");
+ if (len < 16) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left.(Expected 16 bytes of IPv6 address, got %hhu)\n",
+ len);
+ return 1;
+ }
+
+ struct isis_ipv6_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ stream_get(&rv->addr, s, IPV6_MAX_BYTELEN);
+
+ format_item_ipv6_address(mtid, (struct isis_item *)rv, log, NULL, indent + 2);
+ append_item(&tlvs->ipv6_address, (struct isis_item *)rv);
+ return 0;
+}
+
+
+/* Functions related to TLV 233 Global IPv6 Interface addresses */
+static struct isis_item *copy_item_global_ipv6_address(struct isis_item *i)
+{
+ struct isis_ipv6_address *a = (struct isis_ipv6_address *)i;
+ struct isis_ipv6_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->addr = a->addr;
+ return (struct isis_item *)rv;
+}
+
+static void format_item_global_ipv6_address(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf,
+ struct json_object *json,
+ int indent)
+{
+ struct isis_ipv6_address *a = (struct isis_ipv6_address *)i;
+ char addrbuf[INET6_ADDRSTRLEN];
+
+ inet_ntop(AF_INET6, &a->addr, addrbuf, sizeof(addrbuf));
+ if (json)
+ json_object_string_add(json, "global-ipv6", addrbuf);
+ else
+ sbuf_push(buf, indent, "Global IPv6 Interface Address: %s\n",
+ addrbuf);
+}
+
+static void free_item_global_ipv6_address(struct isis_item *i)
+{
+ XFREE(MTYPE_ISIS_TLV, i);
+}
+
+static int pack_item_global_ipv6_address(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_ipv6_address *a = (struct isis_ipv6_address *)i;
+
+ if (STREAM_WRITEABLE(s) < IPV6_MAX_BYTELEN) {
+ *min_len = IPV6_MAX_BYTELEN;
+ return 1;
+ }
+
+ stream_put(s, &a->addr, IPV6_MAX_BYTELEN);
+
+ return 0;
+}
+
+static int unpack_item_global_ipv6_address(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpack Global IPv6 Interface address...\n");
+ if (len < IPV6_MAX_BYTELEN) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left.(Expected 16 bytes of IPv6 address, got %hhu)\n",
+ len);
+ return 1;
+ }
+
+ struct isis_ipv6_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ stream_get(&rv->addr, s, IPV6_MAX_BYTELEN);
+
+ format_item_global_ipv6_address(mtid, (struct isis_item *)rv, log, NULL,
+ indent + 2);
+ append_item(&tlvs->global_ipv6_address, (struct isis_item *)rv);
+ return 0;
+}
+
+/* Functions related to TLV 229 MT Router information */
+static struct isis_item *copy_item_mt_router_info(struct isis_item *i)
+{
+ struct isis_mt_router_info *info = (struct isis_mt_router_info *)i;
+ struct isis_mt_router_info *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->overload = info->overload;
+ rv->attached = info->attached;
+ rv->mtid = info->mtid;
+ return (struct isis_item *)rv;
+}
+
+static void format_item_mt_router_info(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ struct isis_mt_router_info *info = (struct isis_mt_router_info *)i;
+
+ if (json) {
+ struct json_object *mt_json;
+ mt_json = json_object_new_object();
+ json_object_object_add(json, "mt", mt_json);
+ json_object_int_add(mt_json, "mtid", info->mtid);
+ json_object_string_add(mt_json, "overload", info->overload?"true":"false");
+ json_object_string_add(mt_json, "attached", info->attached?"true":"false");
+ } else
+ sbuf_push(buf, indent, "MT Router Info: %s%s%s\n",
+ isis_mtid2str_fake(info->mtid),
+ info->overload ? " Overload" : "",
+ info->attached ? " Attached" : "");
+}
+
+static void free_item_mt_router_info(struct isis_item *i)
+{
+ XFREE(MTYPE_ISIS_TLV, i);
+}
+
+static int pack_item_mt_router_info(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_mt_router_info *info = (struct isis_mt_router_info *)i;
+
+ if (STREAM_WRITEABLE(s) < 2) {
+ *min_len = 2;
+ return 1;
+ }
+
+ uint16_t entry = info->mtid;
+
+ if (info->overload)
+ entry |= ISIS_MT_OL_MASK;
+ if (info->attached)
+ entry |= ISIS_MT_AT_MASK;
+
+ stream_putw(s, entry);
+
+ return 0;
+}
+
+static int unpack_item_mt_router_info(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpack MT Router info...\n");
+ if (len < 2) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left.(Expected 2 bytes of MT info, got %hhu)\n",
+ len);
+ return 1;
+ }
+
+ struct isis_mt_router_info *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ uint16_t entry = stream_getw(s);
+ rv->overload = entry & ISIS_MT_OL_MASK;
+ rv->attached = entry & ISIS_MT_AT_MASK;
+ rv->mtid = entry & ISIS_MT_MASK;
+
+ format_item_mt_router_info(mtid, (struct isis_item *)rv, log, NULL,
+ indent + 2);
+ append_item(&tlvs->mt_router_info, (struct isis_item *)rv);
+ return 0;
+}
+
+/* Functions related to TLV 134 TE Router ID */
+
+static struct in_addr *copy_tlv_te_router_id(const struct in_addr *id)
+{
+ if (!id)
+ return NULL;
+
+ struct in_addr *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ memcpy(rv, id, sizeof(*rv));
+ return rv;
+}
+
+static void format_tlv_te_router_id(const struct in_addr *id, struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ if (!id)
+ return;
+
+ char addrbuf[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, id, addrbuf, sizeof(addrbuf));
+ if (json)
+ json_object_string_add(json, "te-router-id", addrbuf);
+ else
+ sbuf_push(buf, indent, "TE Router ID: %s\n", addrbuf);
+}
+
+static void free_tlv_te_router_id(struct in_addr *id)
+{
+ XFREE(MTYPE_ISIS_TLV, id);
+}
+
+static int pack_tlv_te_router_id(const struct in_addr *id, struct stream *s)
+{
+ if (!id)
+ return 0;
+
+ if (STREAM_WRITEABLE(s) < (unsigned)(2 + sizeof(*id)))
+ return 1;
+
+ stream_putc(s, ISIS_TLV_TE_ROUTER_ID);
+ stream_putc(s, 4);
+ stream_put(s, id, 4);
+ return 0;
+}
+
+static int unpack_tlv_te_router_id(enum isis_tlv_context context,
+ uint8_t tlv_type, uint8_t tlv_len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpacking TE Router ID TLV...\n");
+ if (tlv_len != 4) {
+ sbuf_push(log, indent, "WARNING: Length invalid\n");
+ return 1;
+ }
+
+ if (tlvs->te_router_id) {
+ sbuf_push(log, indent,
+ "WARNING: TE Router ID present multiple times.\n");
+ stream_forward_getp(s, tlv_len);
+ return 0;
+ }
+
+ tlvs->te_router_id = XCALLOC(MTYPE_ISIS_TLV, 4);
+ stream_get(tlvs->te_router_id, s, 4);
+ format_tlv_te_router_id(tlvs->te_router_id, log, NULL, indent + 2);
+ return 0;
+}
+
+
+/* Functions related to TLVs 135/235 extended IP reach/MT IP Reach */
+
+static struct isis_item *copy_item_extended_ip_reach(struct isis_item *i)
+{
+ struct isis_extended_ip_reach *r = (struct isis_extended_ip_reach *)i;
+ struct isis_extended_ip_reach *rv =
+ XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->metric = r->metric;
+ rv->down = r->down;
+ rv->prefix = r->prefix;
+ rv->subtlvs = copy_subtlvs(r->subtlvs);
+
+ return (struct isis_item *)rv;
+}
+
+static void format_item_extended_ip_reach(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ struct isis_extended_ip_reach *r = (struct isis_extended_ip_reach *)i;
+ char prefixbuf[PREFIX2STR_BUFFER];
+
+ if (json) {
+ struct json_object *ext_json;
+ ext_json = json_object_new_object();
+ json_object_object_add(json, "ext-ip-reach", ext_json);
+ json_object_string_add(
+ json, "mt-id",
+ (mtid == ISIS_MT_IPV4_UNICAST) ? "Extended" : "MT");
+ json_object_string_add(
+ json, "ip-reach",
+ prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)));
+ json_object_int_add(json, "ip-reach-metric", r->metric);
+ json_object_string_add(json, "down", r->down ? "yes" : "");
+ if (mtid != ISIS_MT_IPV4_UNICAST)
+ json_object_string_add(json, "mt-name",
+ isis_mtid2str(mtid));
+ if (r->subtlvs) {
+ struct json_object *subtlv_json;
+ subtlv_json = json_object_new_object();
+ json_object_object_add(json, "subtlvs", subtlv_json);
+ format_subtlvs(r->subtlvs, NULL, subtlv_json, 0);
+ }
+ } else {
+ sbuf_push(buf, indent, "%s IP Reachability: %s (Metric: %u)%s",
+ (mtid == ISIS_MT_IPV4_UNICAST) ? "Extended" : "MT",
+ prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)),
+ r->metric, r->down ? " Down" : "");
+ if (mtid != ISIS_MT_IPV4_UNICAST)
+ sbuf_push(buf, 0, " %s", isis_mtid2str(mtid));
+ sbuf_push(buf, 0, "\n");
+
+ if (r->subtlvs) {
+ sbuf_push(buf, indent, " Subtlvs:\n");
+ format_subtlvs(r->subtlvs, buf, NULL, indent + 4);
+ }
+ }
+}
+
+static void free_item_extended_ip_reach(struct isis_item *i)
+{
+ struct isis_extended_ip_reach *item =
+ (struct isis_extended_ip_reach *)i;
+ isis_free_subtlvs(item->subtlvs);
+ XFREE(MTYPE_ISIS_TLV, item);
+}
+
+static int pack_item_extended_ip_reach(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_extended_ip_reach *r = (struct isis_extended_ip_reach *)i;
+ uint8_t control;
+
+ if (STREAM_WRITEABLE(s) < 5) {
+ *min_len = 5;
+ return 1;
+ }
+ stream_putl(s, r->metric);
+
+ control = r->down ? ISIS_EXTENDED_IP_REACH_DOWN : 0;
+ control |= r->prefix.prefixlen;
+ control |= r->subtlvs ? ISIS_EXTENDED_IP_REACH_SUBTLV : 0;
+
+ stream_putc(s, control);
+
+ if (STREAM_WRITEABLE(s) < (unsigned)PSIZE(r->prefix.prefixlen)) {
+ *min_len = 5 + (unsigned)PSIZE(r->prefix.prefixlen);
+ return 1;
+ }
+ stream_put(s, &r->prefix.prefix.s_addr, PSIZE(r->prefix.prefixlen));
+
+ if (r->subtlvs)
+ return pack_subtlvs(r->subtlvs, s);
+ return 0;
+}
+
+static int unpack_item_extended_ip_reach(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+ struct isis_extended_ip_reach *rv = NULL;
+ size_t consume;
+ uint8_t control, subtlv_len;
+ struct isis_item_list *items;
+
+ if (mtid == ISIS_MT_IPV4_UNICAST) {
+ items = &tlvs->extended_ip_reach;
+ } else {
+ items = isis_get_mt_items(&tlvs->mt_ip_reach, mtid);
+ }
+
+ sbuf_push(log, indent, "Unpacking %s IPv4 reachability...\n",
+ (mtid == ISIS_MT_IPV4_UNICAST) ? "extended" : "mt");
+
+ consume = 5;
+ if (len < consume) {
+ sbuf_push(log, indent,
+ "Not enough data left. (expected 5 or more bytes, got %hhu)\n",
+ len);
+ goto out;
+ }
+
+ rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->metric = stream_getl(s);
+ control = stream_getc(s);
+ rv->down = (control & ISIS_EXTENDED_IP_REACH_DOWN);
+ rv->prefix.family = AF_INET;
+ rv->prefix.prefixlen = control & 0x3f;
+ if (rv->prefix.prefixlen > IPV4_MAX_BITLEN) {
+ sbuf_push(log, indent, "Prefixlen %u is implausible for IPv4\n",
+ rv->prefix.prefixlen);
+ goto out;
+ }
+
+ consume += PSIZE(rv->prefix.prefixlen);
+ if (len < consume) {
+ sbuf_push(log, indent,
+ "Expected %u bytes of prefix, but only %u bytes available.\n",
+ PSIZE(rv->prefix.prefixlen), len - 5);
+ goto out;
+ }
+ stream_get(&rv->prefix.prefix.s_addr, s, PSIZE(rv->prefix.prefixlen));
+ in_addr_t orig_prefix = rv->prefix.prefix.s_addr;
+ apply_mask_ipv4(&rv->prefix);
+ if (orig_prefix != rv->prefix.prefix.s_addr)
+ sbuf_push(log, indent + 2,
+ "WARNING: Prefix had hostbits set.\n");
+ format_item_extended_ip_reach(mtid, (struct isis_item *)rv, log, NULL,
+ indent + 2);
+
+ if (control & ISIS_EXTENDED_IP_REACH_SUBTLV) {
+ consume += 1;
+ if (len < consume) {
+ sbuf_push(log, indent,
+ "Expected 1 byte of subtlv len, but no more data present.\n");
+ goto out;
+ }
+ subtlv_len = stream_getc(s);
+
+ if (!subtlv_len) {
+ sbuf_push(log, indent + 2,
+ " WARNING: subtlv bit is set, but there are no subtlvs.\n");
+ }
+ consume += subtlv_len;
+ if (len < consume) {
+ sbuf_push(log, indent,
+ "Expected %hhu bytes of subtlvs, but only %u bytes available.\n",
+ subtlv_len,
+ len - 6 - PSIZE(rv->prefix.prefixlen));
+ goto out;
+ }
+
+ rv->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IP_REACH);
+ bool unpacked_known_tlvs = false;
+
+ if (unpack_tlvs(ISIS_CONTEXT_SUBTLV_IP_REACH, subtlv_len, s,
+ log, rv->subtlvs, indent + 4, &unpacked_known_tlvs)) {
+ goto out;
+ }
+ if (!unpacked_known_tlvs) {
+ isis_free_subtlvs(rv->subtlvs);
+ rv->subtlvs = NULL;
+ }
+ }
+
+ append_item(items, (struct isis_item *)rv);
+ return 0;
+out:
+ if (rv)
+ free_item_extended_ip_reach((struct isis_item *)rv);
+ return 1;
+}
+
+/* Functions related to TLV 137 Dynamic Hostname */
+
+static char *copy_tlv_dynamic_hostname(const char *hostname)
+{
+ if (!hostname)
+ return NULL;
+
+ return XSTRDUP(MTYPE_ISIS_TLV, hostname);
+}
+
+static void format_tlv_dynamic_hostname(const char *hostname, struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ if (!hostname)
+ return;
+
+ if (json)
+ json_object_string_add(json, "hostname", hostname);
+ else
+ sbuf_push(buf, indent, "Hostname: %s\n", hostname);
+}
+
+static void free_tlv_dynamic_hostname(char *hostname)
+{
+ XFREE(MTYPE_ISIS_TLV, hostname);
+}
+
+static int pack_tlv_dynamic_hostname(const char *hostname, struct stream *s)
+{
+ if (!hostname)
+ return 0;
+
+ uint8_t name_len = strlen(hostname);
+
+ if (STREAM_WRITEABLE(s) < (unsigned)(2 + name_len))
+ return 1;
+
+ stream_putc(s, ISIS_TLV_DYNAMIC_HOSTNAME);
+ stream_putc(s, name_len);
+ stream_put(s, hostname, name_len);
+ return 0;
+}
+
+static int unpack_tlv_dynamic_hostname(enum isis_tlv_context context,
+ uint8_t tlv_type, uint8_t tlv_len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpacking Dynamic Hostname TLV...\n");
+ if (!tlv_len) {
+ sbuf_push(log, indent, "WARNING: No hostname included\n");
+ return 0;
+ }
+
+ if (tlvs->hostname) {
+ sbuf_push(log, indent,
+ "WARNING: Hostname present multiple times.\n");
+ stream_forward_getp(s, tlv_len);
+ return 0;
+ }
+
+ tlvs->hostname = XCALLOC(MTYPE_ISIS_TLV, tlv_len + 1);
+ stream_get(tlvs->hostname, s, tlv_len);
+ tlvs->hostname[tlv_len] = '\0';
+
+ bool sane = true;
+ for (uint8_t i = 0; i < tlv_len; i++) {
+ if ((unsigned char)tlvs->hostname[i] > 127
+ || !isprint((unsigned char)tlvs->hostname[i])) {
+ sane = false;
+ tlvs->hostname[i] = '?';
+ }
+ }
+ if (!sane) {
+ sbuf_push(
+ log, indent,
+ "WARNING: Hostname contained non-printable/non-ascii characters.\n");
+ }
+
+ return 0;
+}
+
+/* Functions related to TLV 140 IPv6 TE Router ID */
+
+static struct in6_addr *copy_tlv_te_router_id_ipv6(const struct in6_addr *id)
+{
+ if (!id)
+ return NULL;
+
+ struct in6_addr *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ memcpy(rv, id, sizeof(*rv));
+ return rv;
+}
+
+static void format_tlv_te_router_id_ipv6(const struct in6_addr *id,
+ struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ if (!id)
+ return;
+
+ char addrbuf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, id, addrbuf, sizeof(addrbuf));
+ if (json)
+ json_object_string_add(json, "ipv6-te-router-id", addrbuf);
+ else
+ sbuf_push(buf, indent, "IPv6 TE Router ID: %s\n", addrbuf);
+}
+
+static void free_tlv_te_router_id_ipv6(struct in6_addr *id)
+{
+ XFREE(MTYPE_ISIS_TLV, id);
+}
+
+static int pack_tlv_te_router_id_ipv6(const struct in6_addr *id,
+ struct stream *s)
+{
+ if (!id)
+ return 0;
+
+ if (STREAM_WRITEABLE(s) < (unsigned)(2 + sizeof(*id)))
+ return 1;
+
+ stream_putc(s, ISIS_TLV_TE_ROUTER_ID_IPV6);
+ stream_putc(s, IPV6_MAX_BYTELEN);
+ stream_put(s, id, IPV6_MAX_BYTELEN);
+ return 0;
+}
+
+static int unpack_tlv_te_router_id_ipv6(enum isis_tlv_context context,
+ uint8_t tlv_type, uint8_t tlv_len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpacking IPv6 TE Router ID TLV...\n");
+ if (tlv_len != IPV6_MAX_BYTELEN) {
+ sbuf_push(log, indent, "WARNING: Length invalid\n");
+ return 1;
+ }
+
+ if (tlvs->te_router_id_ipv6) {
+ sbuf_push(
+ log, indent,
+ "WARNING: IPv6 TE Router ID present multiple times.\n");
+ stream_forward_getp(s, tlv_len);
+ return 0;
+ }
+
+ tlvs->te_router_id_ipv6 = XCALLOC(MTYPE_ISIS_TLV, IPV6_MAX_BYTELEN);
+ stream_get(tlvs->te_router_id_ipv6, s, IPV6_MAX_BYTELEN);
+ format_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6, log, NULL, indent + 2);
+ return 0;
+}
+
+
+/* Functions related to TLV 150 Spine-Leaf-Extension */
+
+static struct isis_spine_leaf *copy_tlv_spine_leaf(
+ const struct isis_spine_leaf *spine_leaf)
+{
+ if (!spine_leaf)
+ return NULL;
+
+ struct isis_spine_leaf *rv = XMALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ memcpy(rv, spine_leaf, sizeof(*rv));
+
+ return rv;
+}
+
+static void format_tlv_spine_leaf(const struct isis_spine_leaf *spine_leaf,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ if (!spine_leaf)
+ return;
+
+ char aux_buf[255];
+
+ if (json) {
+ struct json_object *spine_json;
+ spine_json = json_object_new_object();
+ json_object_object_add(json, "spine-leaf-extension",
+ spine_json);
+ if (spine_leaf->has_tier) {
+ snprintfrr(aux_buf, sizeof(aux_buf), "%hhu",
+ spine_leaf->tier);
+ json_object_string_add(
+ spine_json, "tier",
+ (spine_leaf->tier == ISIS_TIER_UNDEFINED)
+ ? "undefined"
+ : aux_buf);
+ }
+ json_object_string_add(spine_json, "flag-leaf",
+ spine_leaf->is_leaf ? "yes" : "");
+ json_object_string_add(spine_json, "flag-spine",
+ spine_leaf->is_spine ? "yes" : "");
+ json_object_string_add(spine_json, "flag-backup",
+ spine_leaf->is_backup ? "yes" : "");
+ } else {
+ sbuf_push(buf, indent, "Spine-Leaf-Extension:\n");
+ if (spine_leaf->has_tier) {
+ if (spine_leaf->tier == ISIS_TIER_UNDEFINED) {
+ sbuf_push(buf, indent, " Tier: undefined\n");
+ } else {
+ sbuf_push(buf, indent, " Tier: %hhu\n",
+ spine_leaf->tier);
+ }
+ }
+
+ sbuf_push(buf, indent, " Flags:%s%s%s\n",
+ spine_leaf->is_leaf ? " LEAF" : "",
+ spine_leaf->is_spine ? " SPINE" : "",
+ spine_leaf->is_backup ? " BACKUP" : "");
+ }
+}
+
+static void free_tlv_spine_leaf(struct isis_spine_leaf *spine_leaf)
+{
+ XFREE(MTYPE_ISIS_TLV, spine_leaf);
+}
+
+#define ISIS_SPINE_LEAF_FLAG_TIER 0x08
+#define ISIS_SPINE_LEAF_FLAG_BACKUP 0x04
+#define ISIS_SPINE_LEAF_FLAG_SPINE 0x02
+#define ISIS_SPINE_LEAF_FLAG_LEAF 0x01
+
+static int pack_tlv_spine_leaf(const struct isis_spine_leaf *spine_leaf,
+ struct stream *s)
+{
+ if (!spine_leaf)
+ return 0;
+
+ uint8_t tlv_len = 2;
+
+ if (STREAM_WRITEABLE(s) < (unsigned)(2 + tlv_len))
+ return 1;
+
+ stream_putc(s, ISIS_TLV_SPINE_LEAF_EXT);
+ stream_putc(s, tlv_len);
+
+ uint16_t spine_leaf_flags = 0;
+
+ if (spine_leaf->has_tier) {
+ spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_TIER;
+ spine_leaf_flags |= spine_leaf->tier << 12;
+ }
+
+ if (spine_leaf->is_leaf)
+ spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_LEAF;
+
+ if (spine_leaf->is_spine)
+ spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_SPINE;
+
+ if (spine_leaf->is_backup)
+ spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_BACKUP;
+
+ stream_putw(s, spine_leaf_flags);
+
+ return 0;
+}
+
+static int unpack_tlv_spine_leaf(enum isis_tlv_context context,
+ uint8_t tlv_type, uint8_t tlv_len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpacking Spine Leaf Extension TLV...\n");
+ if (tlv_len < 2) {
+ sbuf_push(log, indent, "WARNING: Unexpected TLV size\n");
+ stream_forward_getp(s, tlv_len);
+ return 0;
+ }
+
+ if (tlvs->spine_leaf) {
+ sbuf_push(log, indent,
+ "WARNING: Spine Leaf Extension TLV present multiple times.\n");
+ stream_forward_getp(s, tlv_len);
+ return 0;
+ }
+
+ tlvs->spine_leaf = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->spine_leaf));
+
+ uint16_t spine_leaf_flags = stream_getw(s);
+
+ if (spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_TIER) {
+ tlvs->spine_leaf->has_tier = true;
+ tlvs->spine_leaf->tier = spine_leaf_flags >> 12;
+ }
+
+ tlvs->spine_leaf->is_leaf = spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_LEAF;
+ tlvs->spine_leaf->is_spine = spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_SPINE;
+ tlvs->spine_leaf->is_backup = spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_BACKUP;
+
+ stream_forward_getp(s, tlv_len - 2);
+ return 0;
+}
+
+/* Functions related to TLV 240 P2P Three-Way Adjacency */
+
+const char *isis_threeway_state_name(enum isis_threeway_state state)
+{
+ switch (state) {
+ case ISIS_THREEWAY_DOWN:
+ return "Down";
+ case ISIS_THREEWAY_INITIALIZING:
+ return "Initializing";
+ case ISIS_THREEWAY_UP:
+ return "Up";
+ default:
+ return "Invalid!";
+ }
+}
+
+static struct isis_threeway_adj *copy_tlv_threeway_adj(
+ const struct isis_threeway_adj *threeway_adj)
+{
+ if (!threeway_adj)
+ return NULL;
+
+ struct isis_threeway_adj *rv = XMALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ memcpy(rv, threeway_adj, sizeof(*rv));
+
+ return rv;
+}
+
+static void
+format_tlv_threeway_adj(const struct isis_threeway_adj *threeway_adj,
+ struct sbuf *buf, struct json_object *json, int indent)
+{
+ char sys_id[ISO_SYSID_STRLEN];
+
+ if (!threeway_adj)
+ return;
+
+ snprintfrr(sys_id, ISO_SYSID_STRLEN, "%pSY", threeway_adj->neighbor_id);
+ if (json) {
+ struct json_object *three_json;
+ three_json = json_object_new_object();
+ json_object_object_add(json, "p2p-three-way-adj", three_json);
+ json_object_string_add(
+ three_json, "state-name",
+ isis_threeway_state_name(threeway_adj->state));
+ json_object_int_add(three_json, "state", threeway_adj->state);
+ json_object_int_add(three_json, "ext-local-circuit-id",
+ threeway_adj->local_circuit_id);
+ if (!threeway_adj->neighbor_set)
+ return;
+ json_object_string_add(three_json, "neigh-system-id", sys_id);
+ json_object_int_add(three_json, "neigh-ext-circuit-id",
+ threeway_adj->neighbor_circuit_id);
+ } else {
+ sbuf_push(buf, indent, "P2P Three-Way Adjacency:\n");
+ sbuf_push(buf, indent, " State: %s (%d)\n",
+ isis_threeway_state_name(threeway_adj->state),
+ threeway_adj->state);
+ sbuf_push(buf, indent, " Extended Local Circuit ID: %u\n",
+ threeway_adj->local_circuit_id);
+ if (!threeway_adj->neighbor_set)
+ return;
+
+ sbuf_push(buf, indent, " Neighbor System ID: %s\n", sys_id);
+ sbuf_push(buf, indent, " Neighbor Extended Circuit ID: %u\n",
+ threeway_adj->neighbor_circuit_id);
+ }
+}
+
+static void free_tlv_threeway_adj(struct isis_threeway_adj *threeway_adj)
+{
+ XFREE(MTYPE_ISIS_TLV, threeway_adj);
+}
+
+static int pack_tlv_threeway_adj(const struct isis_threeway_adj *threeway_adj,
+ struct stream *s)
+{
+ if (!threeway_adj)
+ return 0;
+
+ uint8_t tlv_len = (threeway_adj->neighbor_set) ? 15 : 5;
+
+ if (STREAM_WRITEABLE(s) < (unsigned)(2 + tlv_len))
+ return 1;
+
+ stream_putc(s, ISIS_TLV_THREE_WAY_ADJ);
+ stream_putc(s, tlv_len);
+ stream_putc(s, threeway_adj->state);
+ stream_putl(s, threeway_adj->local_circuit_id);
+
+ if (threeway_adj->neighbor_set) {
+ stream_put(s, threeway_adj->neighbor_id, 6);
+ stream_putl(s, threeway_adj->neighbor_circuit_id);
+ }
+
+ return 0;
+}
+
+static int unpack_tlv_threeway_adj(enum isis_tlv_context context,
+ uint8_t tlv_type, uint8_t tlv_len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpacking P2P Three-Way Adjacency TLV...\n");
+ if (tlv_len != 5 && tlv_len != 15) {
+ sbuf_push(log, indent, "WARNING: Unexpected TLV size\n");
+ stream_forward_getp(s, tlv_len);
+ return 0;
+ }
+
+ if (tlvs->threeway_adj) {
+ sbuf_push(log, indent,
+ "WARNING: P2P Three-Way Adjacency TLV present multiple times.\n");
+ stream_forward_getp(s, tlv_len);
+ return 0;
+ }
+
+ tlvs->threeway_adj = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->threeway_adj));
+
+ tlvs->threeway_adj->state = stream_getc(s);
+ tlvs->threeway_adj->local_circuit_id = stream_getl(s);
+
+ if (tlv_len == 15) {
+ tlvs->threeway_adj->neighbor_set = true;
+ stream_get(tlvs->threeway_adj->neighbor_id, s, 6);
+ tlvs->threeway_adj->neighbor_circuit_id = stream_getl(s);
+ }
+
+ return 0;
+}
+
+/* Functions related to TLVs 236/237 IPv6/MT-IPv6 reach */
+static struct isis_item *copy_item_ipv6_reach(struct isis_item *i)
+{
+ struct isis_ipv6_reach *r = (struct isis_ipv6_reach *)i;
+ struct isis_ipv6_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->metric = r->metric;
+ rv->down = r->down;
+ rv->external = r->external;
+ rv->prefix = r->prefix;
+ rv->subtlvs = copy_subtlvs(r->subtlvs);
+
+ return (struct isis_item *)rv;
+}
+
+static void format_item_ipv6_reach(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ struct isis_ipv6_reach *r = (struct isis_ipv6_reach *)i;
+ char prefixbuf[PREFIX2STR_BUFFER];
+
+ if (json) {
+ struct json_object *reach_json;
+ reach_json = json_object_new_object();
+ json_object_object_add(json, "ipv6-reach", reach_json);
+ json_object_string_add(reach_json, "mt-id",
+ (mtid == ISIS_MT_IPV4_UNICAST) ? ""
+ : "mt");
+ json_object_string_add(
+ reach_json, "prefix",
+ prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)));
+ json_object_int_add(reach_json, "metric", r->metric);
+ json_object_string_add(reach_json, "down",
+ r->down ? "yes" : "");
+ json_object_string_add(reach_json, "external",
+ r->external ? "yes" : "");
+ if (mtid != ISIS_MT_IPV4_UNICAST)
+ json_object_string_add(reach_json, "mt-name",
+ isis_mtid2str(mtid));
+ if (r->subtlvs) {
+ struct json_object *subtlvs_json;
+ subtlvs_json = json_object_new_object();
+ json_object_object_add(json, "subtlvs", subtlvs_json);
+ format_subtlvs(r->subtlvs, NULL, subtlvs_json, 0);
+ }
+ } else {
+ sbuf_push(buf, indent,
+ "%sIPv6 Reachability: %s (Metric: %u)%s%s",
+ (mtid == ISIS_MT_IPV4_UNICAST) ? "" : "MT ",
+ prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)),
+ r->metric, r->down ? " Down" : "",
+ r->external ? " External" : "");
+ if (mtid != ISIS_MT_IPV4_UNICAST)
+ sbuf_push(buf, 0, " %s", isis_mtid2str(mtid));
+ sbuf_push(buf, 0, "\n");
+
+ if (r->subtlvs) {
+ sbuf_push(buf, indent, " Subtlvs:\n");
+ format_subtlvs(r->subtlvs, buf, NULL, indent + 4);
+ }
+ }
+}
+
+static void free_item_ipv6_reach(struct isis_item *i)
+{
+ struct isis_ipv6_reach *item = (struct isis_ipv6_reach *)i;
+
+ isis_free_subtlvs(item->subtlvs);
+ XFREE(MTYPE_ISIS_TLV, item);
+}
+
+static int pack_item_ipv6_reach(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_ipv6_reach *r = (struct isis_ipv6_reach *)i;
+ uint8_t control;
+
+ if (STREAM_WRITEABLE(s) < 6 + (unsigned)PSIZE(r->prefix.prefixlen)) {
+ *min_len = 6 + (unsigned)PSIZE(r->prefix.prefixlen);
+ return 1;
+ }
+ stream_putl(s, r->metric);
+
+ control = r->down ? ISIS_IPV6_REACH_DOWN : 0;
+ control |= r->external ? ISIS_IPV6_REACH_EXTERNAL : 0;
+ control |= r->subtlvs ? ISIS_IPV6_REACH_SUBTLV : 0;
+
+ stream_putc(s, control);
+ stream_putc(s, r->prefix.prefixlen);
+
+ stream_put(s, &r->prefix.prefix.s6_addr, PSIZE(r->prefix.prefixlen));
+
+ if (r->subtlvs)
+ return pack_subtlvs(r->subtlvs, s);
+
+ return 0;
+}
+
+static int unpack_item_ipv6_reach(uint16_t mtid, uint8_t len, struct stream *s,
+ struct sbuf *log, void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+ struct isis_ipv6_reach *rv = NULL;
+ size_t consume;
+ uint8_t control, subtlv_len;
+ struct isis_item_list *items;
+
+ if (mtid == ISIS_MT_IPV4_UNICAST) {
+ items = &tlvs->ipv6_reach;
+ } else {
+ items = isis_get_mt_items(&tlvs->mt_ipv6_reach, mtid);
+ }
+
+ sbuf_push(log, indent, "Unpacking %sIPv6 reachability...\n",
+ (mtid == ISIS_MT_IPV4_UNICAST) ? "" : "mt ");
+ consume = 6;
+ if (len < consume) {
+ sbuf_push(log, indent,
+ "Not enough data left. (expected 6 or more bytes, got %hhu)\n",
+ len);
+ goto out;
+ }
+
+ rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->metric = stream_getl(s);
+ control = stream_getc(s);
+ rv->down = (control & ISIS_IPV6_REACH_DOWN);
+ rv->external = (control & ISIS_IPV6_REACH_EXTERNAL);
+
+ rv->prefix.family = AF_INET6;
+ rv->prefix.prefixlen = stream_getc(s);
+ if (rv->prefix.prefixlen > IPV6_MAX_BITLEN) {
+ sbuf_push(log, indent, "Prefixlen %u is implausible for IPv6\n",
+ rv->prefix.prefixlen);
+ goto out;
+ }
+
+ consume += PSIZE(rv->prefix.prefixlen);
+ if (len < consume) {
+ sbuf_push(log, indent,
+ "Expected %u bytes of prefix, but only %u bytes available.\n",
+ PSIZE(rv->prefix.prefixlen), len - 6);
+ goto out;
+ }
+ stream_get(&rv->prefix.prefix.s6_addr, s, PSIZE(rv->prefix.prefixlen));
+ struct in6_addr orig_prefix = rv->prefix.prefix;
+
+ apply_mask_ipv6(&rv->prefix);
+ if (memcmp(&orig_prefix, &rv->prefix.prefix, sizeof(orig_prefix)))
+ sbuf_push(log, indent + 2,
+ "WARNING: Prefix had hostbits set.\n");
+ format_item_ipv6_reach(mtid, (struct isis_item *)rv, log, NULL, indent + 2);
+
+ if (control & ISIS_IPV6_REACH_SUBTLV) {
+ consume += 1;
+ if (len < consume) {
+ sbuf_push(log, indent,
+ "Expected 1 byte of subtlv len, but no more data persent.\n");
+ goto out;
+ }
+ subtlv_len = stream_getc(s);
+
+ if (!subtlv_len) {
+ sbuf_push(log, indent + 2,
+ " WARNING: subtlv bit set, but there are no subtlvs.\n");
+ }
+ consume += subtlv_len;
+ if (len < consume) {
+ sbuf_push(log, indent,
+ "Expected %hhu bytes of subtlvs, but only %u bytes available.\n",
+ subtlv_len,
+ len - 6 - PSIZE(rv->prefix.prefixlen));
+ goto out;
+ }
+
+ rv->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IPV6_REACH);
+ bool unpacked_known_tlvs = false;
+
+ if (unpack_tlvs(ISIS_CONTEXT_SUBTLV_IPV6_REACH, subtlv_len, s,
+ log, rv->subtlvs, indent + 4, &unpacked_known_tlvs)) {
+ goto out;
+ }
+ if (!unpacked_known_tlvs) {
+ isis_free_subtlvs(rv->subtlvs);
+ rv->subtlvs = NULL;
+ }
+ }
+
+ append_item(items, (struct isis_item *)rv);
+ return 0;
+out:
+ if (rv)
+ free_item_ipv6_reach((struct isis_item *)rv);
+ return 1;
+}
+
+/* Functions related to TLV 242 Router Capability as per RFC7981 */
+static struct isis_router_cap *copy_tlv_router_cap(
+ const struct isis_router_cap *router_cap)
+{
+ struct isis_router_cap *rv;
+
+ if (!router_cap)
+ return NULL;
+
+ rv = XMALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ memcpy(rv, router_cap, sizeof(*rv));
+
+#ifndef FABRICD
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ struct isis_router_cap_fad *sc_fad;
+ struct isis_router_cap_fad *rv_fad;
+
+ sc_fad = router_cap->fads[i];
+ if (!sc_fad)
+ continue;
+ rv_fad = XMALLOC(MTYPE_ISIS_TLV,
+ sizeof(struct isis_router_cap_fad));
+ *rv_fad = *sc_fad;
+ rv_fad->fad.admin_group_exclude_any.bitmap.data = NULL;
+ rv_fad->fad.admin_group_include_any.bitmap.data = NULL;
+ rv_fad->fad.admin_group_include_all.bitmap.data = NULL;
+
+ assert(bf_is_inited(
+ sc_fad->fad.admin_group_exclude_any.bitmap));
+ assert(bf_is_inited(
+ sc_fad->fad.admin_group_include_any.bitmap));
+ assert(bf_is_inited(
+ sc_fad->fad.admin_group_include_all.bitmap));
+
+ admin_group_copy(&rv_fad->fad.admin_group_exclude_any,
+ &sc_fad->fad.admin_group_exclude_any);
+ admin_group_copy(&rv_fad->fad.admin_group_include_any,
+ &sc_fad->fad.admin_group_include_any);
+ admin_group_copy(&rv_fad->fad.admin_group_include_all,
+ &sc_fad->fad.admin_group_include_all);
+
+ rv->fads[i] = rv_fad;
+ }
+#endif /* ifndef FABRICD */
+
+ return rv;
+}
+
+static void format_tlv_router_cap_json(const struct isis_router_cap *router_cap,
+ struct json_object *json)
+{
+ char addrbuf[INET_ADDRSTRLEN];
+
+ if (!router_cap)
+ return;
+
+ /* Router ID and Flags */
+ struct json_object *cap_json;
+ cap_json = json_object_new_object();
+ json_object_object_add(json, "router-capability", cap_json);
+ inet_ntop(AF_INET, &router_cap->router_id, addrbuf, sizeof(addrbuf));
+ json_object_string_add(cap_json, "id", addrbuf);
+ json_object_string_add(
+ cap_json, "flag-d",
+ router_cap->flags & ISIS_ROUTER_CAP_FLAG_D ? "1" : "0");
+ json_object_string_add(
+ cap_json, "flag-s",
+ router_cap->flags & ISIS_ROUTER_CAP_FLAG_S ? "1" : "0");
+
+ /* Segment Routing Global Block as per RFC8667 section #3.1 */
+ if (router_cap->srgb.range_size != 0) {
+ struct json_object *gb_json;
+ gb_json = json_object_new_object();
+ json_object_object_add(json, "segment-routing-gb", gb_json);
+ json_object_string_add(gb_json, "ipv4",
+ IS_SR_IPV4(&router_cap->srgb) ? "1"
+ : "0");
+ json_object_string_add(gb_json, "ipv6",
+ IS_SR_IPV6(&router_cap->srgb) ? "1"
+ : "0");
+ json_object_int_add(gb_json, "global-block-base",
+ router_cap->srgb.lower_bound);
+ json_object_int_add(gb_json, "global-block-range",
+ router_cap->srgb.range_size);
+ }
+
+ /* Segment Routing Local Block as per RFC8667 section #3.3 */
+ if (router_cap->srlb.range_size != 0) {
+ struct json_object *lb_json;
+ lb_json = json_object_new_object();
+ json_object_object_add(json, "segment-routing-lb", lb_json);
+ json_object_int_add(lb_json, "global-block-base",
+ router_cap->srlb.lower_bound);
+ json_object_int_add(lb_json, "global-block-range",
+ router_cap->srlb.range_size);
+ }
+
+ /* Segment Routing Algorithms as per RFC8667 section #3.2 */
+ if (router_cap->algo[0] != SR_ALGORITHM_UNSET) {
+ char buf[255];
+ struct json_object *alg_json;
+ alg_json = json_object_new_object();
+ json_object_object_add(json, "segment-routing-algorithm",
+ alg_json);
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
+ if (router_cap->algo[i] != SR_ALGORITHM_UNSET) {
+ snprintfrr(buf, sizeof(buf), "%d", i);
+ json_object_string_add(alg_json, buf,
+ router_cap->algo[i] == 0
+ ? "SPF"
+ : "Strict SPF");
+ }
+ }
+
+ /* Segment Routing Node MSD as per RFC8491 section #2 */
+ if (router_cap->msd != 0)
+ json_object_int_add(json, "msd", router_cap->msd);
+}
+
+static void format_tlv_router_cap(const struct isis_router_cap *router_cap,
+ struct sbuf *buf, int indent)
+{
+ char addrbuf[INET_ADDRSTRLEN];
+
+ if (!router_cap)
+ return;
+
+ /* Router ID and Flags */
+ inet_ntop(AF_INET, &router_cap->router_id, addrbuf, sizeof(addrbuf));
+ sbuf_push(buf, indent, "Router Capability:");
+ sbuf_push(buf, indent, " %s , D:%c, S:%c\n", addrbuf,
+ router_cap->flags & ISIS_ROUTER_CAP_FLAG_D ? '1' : '0',
+ router_cap->flags & ISIS_ROUTER_CAP_FLAG_S ? '1' : '0');
+
+ /* Segment Routing Global Block as per RFC8667 section #3.1 */
+ if (router_cap->srgb.range_size != 0)
+ sbuf_push(
+ buf, indent,
+ " Segment Routing: I:%s V:%s, Global Block Base: %u Range: %u\n",
+ IS_SR_IPV4(&router_cap->srgb) ? "1" : "0",
+ IS_SR_IPV6(&router_cap->srgb) ? "1" : "0",
+ router_cap->srgb.lower_bound,
+ router_cap->srgb.range_size);
+
+ /* Segment Routing Local Block as per RFC8667 section #3.3 */
+ if (router_cap->srlb.range_size != 0)
+ sbuf_push(buf, indent, " SR Local Block Base: %u Range: %u\n",
+ router_cap->srlb.lower_bound,
+ router_cap->srlb.range_size);
+
+ /* Segment Routing Algorithms as per RFC8667 section #3.2 */
+ if (router_cap->algo[0] != SR_ALGORITHM_UNSET) {
+ sbuf_push(buf, indent, " SR Algorithm:\n");
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
+ if (router_cap->algo[i] != SR_ALGORITHM_UNSET)
+ sbuf_push(buf, indent, " %u: %s\n", i,
+ sr_algorithm_string(
+ router_cap->algo[i]));
+ }
+
+ /* Segment Routing Node MSD as per RFC8491 section #2 */
+ if (router_cap->msd != 0)
+ sbuf_push(buf, indent, " Node Maximum SID Depth: %u\n",
+ router_cap->msd);
+
+#ifndef FABRICD
+ /* Flex-Algo */
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ char admin_group_buf[ADMIN_GROUP_PRINT_MAX_SIZE];
+ int indent2;
+ struct admin_group *admin_group;
+ struct isis_router_cap_fad *fad;
+
+ fad = router_cap->fads[i];
+ if (!fad)
+ continue;
+
+ sbuf_push(buf, indent, " Flex-Algo Definition: %d\n",
+ fad->fad.algorithm);
+ sbuf_push(buf, indent, " Metric-Type: %d\n",
+ fad->fad.metric_type);
+ sbuf_push(buf, indent, " Calc-Type: %d\n",
+ fad->fad.calc_type);
+ sbuf_push(buf, indent, " Priority: %d\n", fad->fad.priority);
+
+ indent2 = indent + strlen(" Exclude-Any: ");
+ admin_group = &fad->fad.admin_group_exclude_any;
+ sbuf_push(buf, indent, " Exclude-Any: ");
+ sbuf_push(buf, 0, "%s\n",
+ admin_group_string(admin_group_buf,
+ ADMIN_GROUP_PRINT_MAX_SIZE,
+ indent2, admin_group));
+
+ indent2 = indent + strlen(" Include-Any: ");
+ admin_group = &fad->fad.admin_group_include_any;
+ sbuf_push(buf, indent, " Include-Any: ");
+ sbuf_push(buf, 0, "%s\n",
+ admin_group_string(admin_group_buf,
+ ADMIN_GROUP_PRINT_MAX_SIZE,
+ indent2, admin_group));
+
+ indent2 = indent + strlen(" Include-All: ");
+ admin_group = &fad->fad.admin_group_include_all;
+ sbuf_push(buf, indent, " Include-All: ");
+ sbuf_push(buf, 0, "%s\n",
+ admin_group_string(admin_group_buf,
+ ADMIN_GROUP_PRINT_MAX_SIZE,
+ indent2, admin_group));
+
+ sbuf_push(buf, indent, " M-Flag: %c\n",
+ CHECK_FLAG(fad->fad.flags, FAD_FLAG_M) ? '1' : '0');
+
+ if (fad->fad.flags != 0 && fad->fad.flags != FAD_FLAG_M)
+ sbuf_push(buf, indent, " Flags: 0x%x\n",
+ fad->fad.flags);
+ if (fad->fad.exclude_srlg)
+ sbuf_push(buf, indent, " Exclude SRLG: Enabled\n");
+ if (fad->fad.unsupported_subtlv)
+ sbuf_push(buf, indent,
+ " Got an unsupported sub-TLV: Yes\n");
+ }
+#endif /* ifndef FABRICD */
+
+ /* SRv6 Flags as per RFC 9352 section #2 */
+ if (router_cap->srv6_cap.is_srv6_capable)
+ sbuf_push(buf, indent, " SRv6: O:%s\n",
+ SUPPORTS_SRV6_OAM(&router_cap->srv6_cap) ? "1" : "0");
+}
+
+static void free_tlv_router_cap(struct isis_router_cap *router_cap)
+{
+ if (!router_cap)
+ return;
+
+#ifndef FABRICD
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ struct isis_router_cap_fad *fad;
+
+ fad = router_cap->fads[i];
+ if (!fad)
+ continue;
+ admin_group_term(&fad->fad.admin_group_exclude_any);
+ admin_group_term(&fad->fad.admin_group_include_any);
+ admin_group_term(&fad->fad.admin_group_include_all);
+ XFREE(MTYPE_ISIS_TLV, fad);
+ }
+#endif /* ifndef FABRICD */
+
+ XFREE(MTYPE_ISIS_TLV, router_cap);
+}
+
+#ifndef FABRICD
+static size_t
+isis_router_cap_fad_sub_tlv_len(const struct isis_router_cap_fad *fad)
+{
+ size_t sz = ISIS_SUBTLV_FAD_MIN_SIZE;
+ uint32_t admin_group_length;
+
+ admin_group_length =
+ admin_group_nb_words(&fad->fad.admin_group_exclude_any);
+ if (admin_group_length)
+ sz += sizeof(uint32_t) * admin_group_length + 2;
+
+ admin_group_length =
+ admin_group_nb_words(&fad->fad.admin_group_include_any);
+ if (admin_group_length)
+ sz += sizeof(uint32_t) * admin_group_length + 2;
+
+ admin_group_length =
+ admin_group_nb_words(&fad->fad.admin_group_include_all);
+ if (admin_group_length)
+ sz += sizeof(uint32_t) * admin_group_length + 2;
+
+ if (fad->fad.flags != 0)
+ sz += ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS_SIZE + 2;
+
+ /* TODO: add exclude SRLG sub-sub-TLV length when supported */
+
+ return sz;
+}
+#endif /* ifndef FABRICD */
+
+static size_t isis_router_cap_tlv_size(const struct isis_router_cap *router_cap)
+{
+ size_t sz = 2 + ISIS_ROUTER_CAP_SIZE;
+#ifndef FABRICD
+ size_t fad_sz;
+#endif /* ifndef FABRICD */
+ int nb_algo, nb_msd;
+
+ if ((router_cap->srgb.range_size != 0) &&
+ (router_cap->srgb.lower_bound != 0)) {
+ sz += 2 + ISIS_SUBTLV_SID_LABEL_RANGE_SIZE;
+ sz += 2 + ISIS_SUBTLV_SID_LABEL_SIZE;
+
+ nb_algo = isis_tlvs_sr_algo_count(router_cap);
+ if (nb_algo != 0)
+ sz += 2 + nb_algo;
+
+ if ((router_cap->srlb.range_size != 0) &&
+ (router_cap->srlb.lower_bound != 0)) {
+ sz += 2 + ISIS_SUBTLV_SID_LABEL_RANGE_SIZE;
+ sz += 2 + ISIS_SUBTLV_SID_LABEL_SIZE;
+ }
+
+ if (router_cap->msd != 0)
+ sz += 2 + ISIS_SUBTLV_NODE_MSD_SIZE;
+ }
+
+#ifndef FABRICD
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ if (!router_cap->fads[i])
+ continue;
+ fad_sz = 2 +
+ isis_router_cap_fad_sub_tlv_len(router_cap->fads[i]);
+ if (((sz + fad_sz) % 256) < (sz % 256))
+ sz += 2 + ISIS_ROUTER_CAP_SIZE + fad_sz;
+ else
+ sz += fad_sz;
+ }
+#endif /* ifndef FABRICD */
+
+ if (router_cap->srv6_cap.is_srv6_capable) {
+ sz += ISIS_SUBTLV_TYPE_FIELD_SIZE +
+ ISIS_SUBTLV_LENGTH_FIELD_SIZE +
+ ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE;
+
+ nb_algo = isis_tlvs_sr_algo_count(router_cap);
+ if (nb_algo != 0)
+ sz += ISIS_SUBTLV_TYPE_FIELD_SIZE +
+ ISIS_SUBTLV_LENGTH_FIELD_SIZE + nb_algo;
+
+ nb_msd = router_cap->srv6_msd.max_seg_left_msd +
+ router_cap->srv6_msd.max_end_pop_msd +
+ router_cap->srv6_msd.max_h_encaps_msd +
+ router_cap->srv6_msd.max_end_d_msd;
+ if (nb_msd != 0)
+ sz += ISIS_SUBTLV_TYPE_FIELD_SIZE +
+ ISIS_SUBTLV_LENGTH_FIELD_SIZE +
+ (ISIS_SUBTLV_NODE_MSD_TYPE_SIZE +
+ ISIS_SUBTLV_NODE_MSD_VALUE_SIZE) *
+ nb_msd;
+ }
+
+ return sz;
+}
+
+static int pack_tlv_router_cap(const struct isis_router_cap *router_cap,
+ struct stream *s)
+{
+ size_t tlv_len, len_pos;
+ uint8_t nb_algo;
+ size_t subtlv_len, subtlv_len_pos;
+ bool sr_algo_subtlv_present = false;
+
+ if (!router_cap)
+ return 0;
+
+ if (STREAM_WRITEABLE(s) < isis_router_cap_tlv_size(router_cap))
+ return 1;
+
+ /* Add Router Capability TLV 242 with Router ID and Flags */
+ stream_putc(s, ISIS_TLV_ROUTER_CAPABILITY);
+ len_pos = stream_get_endp(s);
+ stream_putc(s, 0); /* Real length will be adjusted later */
+ stream_put_ipv4(s, router_cap->router_id.s_addr);
+ stream_putc(s, router_cap->flags);
+
+ /* Add SRGB if set as per RFC8667 section #3.1 */
+ if ((router_cap->srgb.range_size != 0)
+ && (router_cap->srgb.lower_bound != 0)) {
+ stream_putc(s, ISIS_SUBTLV_SID_LABEL_RANGE);
+ stream_putc(s, ISIS_SUBTLV_SID_LABEL_RANGE_SIZE);
+ stream_putc(s, router_cap->srgb.flags);
+ stream_put3(s, router_cap->srgb.range_size);
+ stream_putc(s, ISIS_SUBTLV_SID_LABEL);
+ stream_putc(s, ISIS_SUBTLV_SID_LABEL_SIZE);
+ stream_put3(s, router_cap->srgb.lower_bound);
+
+ /* Then SR Algorithm if set as per RFC8667 section #3.2 */
+ nb_algo = isis_tlvs_sr_algo_count(router_cap);
+ if (nb_algo > 0) {
+ stream_putc(s, ISIS_SUBTLV_ALGORITHM);
+ stream_putc(s, nb_algo);
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
+ if (router_cap->algo[i] != SR_ALGORITHM_UNSET)
+ stream_putc(s, router_cap->algo[i]);
+ sr_algo_subtlv_present = true;
+ }
+
+ /* Local Block if defined as per RFC8667 section #3.3 */
+ if ((router_cap->srlb.range_size != 0)
+ && (router_cap->srlb.lower_bound != 0)) {
+ stream_putc(s, ISIS_SUBTLV_SRLB);
+ stream_putc(s, ISIS_SUBTLV_SID_LABEL_RANGE_SIZE);
+ /* No Flags are defined for SRLB */
+ stream_putc(s, 0);
+ stream_put3(s, router_cap->srlb.range_size);
+ stream_putc(s, ISIS_SUBTLV_SID_LABEL);
+ stream_putc(s, ISIS_SUBTLV_SID_LABEL_SIZE);
+ stream_put3(s, router_cap->srlb.lower_bound);
+ }
+
+ /* And finish with MSD if set as per RFC8491 section #2 */
+ if (router_cap->msd != 0) {
+ stream_putc(s, ISIS_SUBTLV_NODE_MSD);
+ stream_putc(s, ISIS_SUBTLV_NODE_MSD_SIZE);
+ stream_putc(s, MSD_TYPE_BASE_MPLS_IMPOSITION);
+ stream_putc(s, router_cap->msd);
+ }
+ }
+
+#ifndef FABRICD
+ /* Flex Algo Definitions */
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ struct isis_router_cap_fad *fad;
+ size_t subtlv_len;
+ struct admin_group *ag;
+ uint32_t admin_group_length;
+
+ fad = router_cap->fads[i];
+ if (!fad)
+ continue;
+
+ subtlv_len = isis_router_cap_fad_sub_tlv_len(fad);
+
+ if ((stream_get_endp(s) - len_pos - 1) > 250) {
+ /* Adjust TLV length which depends on subTLVs presence
+ */
+ tlv_len = stream_get_endp(s) - len_pos - 1;
+ stream_putc_at(s, len_pos, tlv_len);
+
+ /* Add Router Capability TLV 242 with Router ID and
+ * Flags
+ */
+ stream_putc(s, ISIS_TLV_ROUTER_CAPABILITY);
+ /* Real length will be adjusted later */
+ len_pos = stream_get_endp(s);
+ stream_putc(s, 0);
+ stream_put_ipv4(s, router_cap->router_id.s_addr);
+ stream_putc(s, router_cap->flags);
+ }
+
+ stream_putc(s, ISIS_SUBTLV_FAD);
+ stream_putc(s, subtlv_len); /* length will be filled later */
+
+ stream_putc(s, fad->fad.algorithm);
+ stream_putc(s, fad->fad.metric_type);
+ stream_putc(s, fad->fad.calc_type);
+ stream_putc(s, fad->fad.priority);
+
+ ag = &fad->fad.admin_group_exclude_any;
+ admin_group_length = admin_group_nb_words(ag);
+ if (admin_group_length) {
+ stream_putc(s, ISIS_SUBTLV_FAD_SUBSUBTLV_EXCAG);
+ stream_putc(s, sizeof(uint32_t) * admin_group_length);
+ for (size_t i = 0; i < admin_group_length; i++)
+ stream_putl(s, admin_group_get_offset(ag, i));
+ }
+
+ ag = &fad->fad.admin_group_include_any;
+ admin_group_length = admin_group_nb_words(ag);
+ if (admin_group_length) {
+ stream_putc(s, ISIS_SUBTLV_FAD_SUBSUBTLV_INCANYAG);
+ stream_putc(s, sizeof(uint32_t) * admin_group_length);
+ for (size_t i = 0; i < admin_group_length; i++)
+ stream_putl(s, admin_group_get_offset(ag, i));
+ }
+
+ ag = &fad->fad.admin_group_include_all;
+ admin_group_length = admin_group_nb_words(ag);
+ if (admin_group_length) {
+ stream_putc(s, ISIS_SUBTLV_FAD_SUBSUBTLV_INCALLAG);
+ stream_putc(s, sizeof(uint32_t) * admin_group_length);
+ for (size_t i = 0; i < admin_group_length; i++)
+ stream_putl(s, admin_group_get_offset(ag, i));
+ }
+
+ if (fad->fad.flags != 0) {
+ stream_putc(s, ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS);
+ stream_putc(s, ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS_SIZE);
+ stream_putc(s, fad->fad.flags);
+ }
+ }
+#endif /* ifndef FABRICD */
+
+ /* Add SRv6 capabilities if set as per RFC 9352 section #2 */
+ if (router_cap->srv6_cap.is_srv6_capable) {
+ stream_putc(s, ISIS_SUBTLV_SRV6_CAPABILITIES);
+ stream_putc(s, ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE);
+ stream_putw(s, router_cap->srv6_cap.flags);
+
+ /*
+ * Then add SR Algorithm if set and if we haven't already
+ * added it when we processed SR-MPLS related Sub-TLVs as
+ * per RFC 9352 section #3
+ */
+ if (!sr_algo_subtlv_present) {
+ nb_algo = isis_tlvs_sr_algo_count(router_cap);
+ if (nb_algo > 0) {
+ stream_putc(s, ISIS_SUBTLV_ALGORITHM);
+ stream_putc(s, nb_algo);
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
+ if (router_cap->algo[i] !=
+ SR_ALGORITHM_UNSET)
+ stream_putc(s,
+ router_cap->algo[i]);
+ }
+ }
+
+ /* And finish with MSDs if set as per RFC 9352 section #4 */
+ if (router_cap->srv6_msd.max_seg_left_msd +
+ router_cap->srv6_msd.max_end_pop_msd +
+ router_cap->srv6_msd.max_h_encaps_msd +
+ router_cap->srv6_msd.max_end_d_msd !=
+ 0) {
+ stream_putc(s, ISIS_SUBTLV_NODE_MSD);
+
+ subtlv_len_pos = stream_get_endp(s);
+ /* Put 0 as Sub-TLV length for now, real length will be
+ * adjusted later */
+ stream_putc(s, 0);
+
+ /* RFC 9352 section #4.1 */
+ if (router_cap->srv6_msd.max_seg_left_msd != 0) {
+ stream_putc(s, ISIS_SUBTLV_SRV6_MAX_SL_MSD);
+ stream_putc(
+ s,
+ router_cap->srv6_msd.max_seg_left_msd);
+ }
+
+ /* RFC 9352 section #4.2 */
+ if (router_cap->srv6_msd.max_end_pop_msd != 0) {
+ stream_putc(s,
+ ISIS_SUBTLV_SRV6_MAX_END_POP_MSD);
+ stream_putc(
+ s,
+ router_cap->srv6_msd.max_end_pop_msd);
+ }
+
+ /* RFC 9352 section #4.3 */
+ if (router_cap->srv6_msd.max_h_encaps_msd != 0) {
+ stream_putc(s,
+ ISIS_SUBTLV_SRV6_MAX_H_ENCAPS_MSD);
+ stream_putc(
+ s,
+ router_cap->srv6_msd.max_h_encaps_msd);
+ }
+
+ /* RFC 9352 section #4.4 */
+ if (router_cap->srv6_msd.max_end_d_msd != 0) {
+ stream_putc(s, ISIS_SUBTLV_SRV6_MAX_END_D_MSD);
+ stream_putc(s,
+ router_cap->srv6_msd.max_end_d_msd);
+ }
+
+ /* Adjust Node MSD Sub-TLV length which depends on MSDs
+ * presence */
+ subtlv_len = stream_get_endp(s) - subtlv_len_pos - 1;
+ stream_putc_at(s, subtlv_len_pos, subtlv_len);
+ }
+ }
+
+ /* Adjust TLV length which depends on subTLVs presence */
+ tlv_len = stream_get_endp(s) - len_pos - 1;
+ stream_putc_at(s, len_pos, tlv_len);
+
+ return 0;
+}
+
+static int unpack_tlv_router_cap(enum isis_tlv_context context,
+ uint8_t tlv_type, uint8_t tlv_len,
+ struct stream *s, struct sbuf *log, void *dest,
+ int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+ struct isis_router_cap *rcap;
+ uint8_t type;
+ uint8_t length;
+ uint8_t subtlv_len;
+ uint8_t size;
+ int num_msd;
+
+ sbuf_push(log, indent, "Unpacking Router Capability TLV...\n");
+ if (tlv_len < ISIS_ROUTER_CAP_SIZE) {
+ sbuf_push(log, indent, "WARNING: Unexpected TLV size\n");
+ stream_forward_getp(s, tlv_len);
+ return 0;
+ }
+
+ if (tlvs->router_cap)
+ /* Multiple Router Capability found */
+ rcap = tlvs->router_cap;
+ else {
+ /* Allocate router cap structure and initialize SR Algorithms */
+ rcap = XCALLOC(MTYPE_ISIS_TLV, sizeof(struct isis_router_cap));
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
+ rcap->algo[i] = SR_ALGORITHM_UNSET;
+ }
+
+ /* Get Router ID and Flags */
+ rcap->router_id.s_addr = stream_get_ipv4(s);
+ rcap->flags = stream_getc(s);
+
+ /* Parse remaining part of the TLV if present */
+ subtlv_len = tlv_len - ISIS_ROUTER_CAP_SIZE;
+ while (subtlv_len > 2) {
+#ifndef FABRICD
+ struct isis_router_cap_fad *fad;
+ uint8_t subsubtlvs_len;
+#endif /* ifndef FABRICD */
+ uint8_t msd_type;
+
+ type = stream_getc(s);
+ length = stream_getc(s);
+
+ if (length > STREAM_READABLE(s) || length > subtlv_len - 2) {
+ sbuf_push(
+ log, indent,
+ "WARNING: Router Capability subTLV length too large compared to expected size\n");
+ stream_forward_getp(s, STREAM_READABLE(s));
+ XFREE(MTYPE_ISIS_TLV, rcap);
+ return 0;
+ }
+
+ switch (type) {
+ case ISIS_SUBTLV_SID_LABEL_RANGE:
+ /* Check that SRGB is correctly formated */
+ if (length < SUBTLV_RANGE_LABEL_SIZE
+ || length > SUBTLV_RANGE_INDEX_SIZE) {
+ stream_forward_getp(s, length);
+ break;
+ }
+ /* Only one SRGB is supported. Skip subsequent one */
+ if (rcap->srgb.range_size != 0) {
+ stream_forward_getp(s, length);
+ break;
+ }
+ rcap->srgb.flags = stream_getc(s);
+ rcap->srgb.range_size = stream_get3(s);
+ /* Skip Type and get Length of SID Label */
+ stream_getc(s);
+ size = stream_getc(s);
+
+ if (size == ISIS_SUBTLV_SID_LABEL_SIZE
+ && length != SUBTLV_RANGE_LABEL_SIZE) {
+ stream_forward_getp(s, length - 6);
+ break;
+ }
+
+ if (size == ISIS_SUBTLV_SID_INDEX_SIZE
+ && length != SUBTLV_RANGE_INDEX_SIZE) {
+ stream_forward_getp(s, length - 6);
+ break;
+ }
+
+ if (size == ISIS_SUBTLV_SID_LABEL_SIZE) {
+ rcap->srgb.lower_bound = stream_get3(s);
+ } else if (size == ISIS_SUBTLV_SID_INDEX_SIZE) {
+ rcap->srgb.lower_bound = stream_getl(s);
+ } else {
+ stream_forward_getp(s, length - 6);
+ break;
+ }
+
+ /* SRGB sanity checks. */
+ if (rcap->srgb.range_size == 0
+ || (rcap->srgb.lower_bound <= MPLS_LABEL_RESERVED_MAX)
+ || ((rcap->srgb.lower_bound + rcap->srgb.range_size - 1)
+ > MPLS_LABEL_UNRESERVED_MAX)) {
+ sbuf_push(log, indent, "Invalid label range. Reset SRGB\n");
+ rcap->srgb.lower_bound = 0;
+ rcap->srgb.range_size = 0;
+ }
+ /* Only one range is supported. Skip subsequent one */
+ size = length - (size + SUBTLV_SR_BLOCK_SIZE);
+ if (size > 0)
+ stream_forward_getp(s, size);
+
+ break;
+ case ISIS_SUBTLV_ALGORITHM:
+ if (length == 0)
+ break;
+
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
+ rcap->algo[i] = SR_ALGORITHM_UNSET;
+
+ for (int i = 0; i < length; i++) {
+ uint8_t algo;
+
+ algo = stream_getc(s);
+ rcap->algo[algo] = algo;
+ }
+ break;
+ case ISIS_SUBTLV_SRLB:
+ /* Check that SRLB is correctly formated */
+ if (length < SUBTLV_RANGE_LABEL_SIZE
+ || length > SUBTLV_RANGE_INDEX_SIZE) {
+ stream_forward_getp(s, length);
+ break;
+ }
+ /* RFC 8667 section #3.3: Only one SRLB is authorized */
+ if (rcap->srlb.range_size != 0) {
+ stream_forward_getp(s, length);
+ break;
+ }
+ /* Ignore Flags which are not defined */
+ stream_getc(s);
+ rcap->srlb.range_size = stream_get3(s);
+ /* Skip Type and get Length of SID Label */
+ stream_getc(s);
+ size = stream_getc(s);
+
+ if (size == ISIS_SUBTLV_SID_LABEL_SIZE
+ && length != SUBTLV_RANGE_LABEL_SIZE) {
+ stream_forward_getp(s, length - 6);
+ break;
+ }
+
+ if (size == ISIS_SUBTLV_SID_INDEX_SIZE
+ && length != SUBTLV_RANGE_INDEX_SIZE) {
+ stream_forward_getp(s, length - 6);
+ break;
+ }
+
+ if (size == ISIS_SUBTLV_SID_LABEL_SIZE) {
+ rcap->srlb.lower_bound = stream_get3(s);
+ } else if (size == ISIS_SUBTLV_SID_INDEX_SIZE) {
+ rcap->srlb.lower_bound = stream_getl(s);
+ } else {
+ stream_forward_getp(s, length - 6);
+ break;
+ }
+
+ /* SRLB sanity checks. */
+ if (rcap->srlb.range_size == 0
+ || (rcap->srlb.lower_bound <= MPLS_LABEL_RESERVED_MAX)
+ || ((rcap->srlb.lower_bound + rcap->srlb.range_size - 1)
+ > MPLS_LABEL_UNRESERVED_MAX)) {
+ sbuf_push(log, indent, "Invalid label range. Reset SRLB\n");
+ rcap->srlb.lower_bound = 0;
+ rcap->srlb.range_size = 0;
+ }
+ /* Only one range is supported. Skip subsequent one */
+ size = length - (size + SUBTLV_SR_BLOCK_SIZE);
+ if (size > 0)
+ stream_forward_getp(s, size);
+
+ break;
+ case ISIS_SUBTLV_NODE_MSD:
+ sbuf_push(log, indent,
+ "Unpacking Node MSD sub-TLV...\n");
+
+ /* Check that MSD is correctly formated */
+ if (length % 2) {
+ sbuf_push(
+ log, indent,
+ "WARNING: Unexpected MSD sub-TLV length\n");
+ stream_forward_getp(s, length);
+ break;
+ }
+
+ /* Get the number of MSDs carried in the value field of
+ * the Node MSD sub-TLV. The value field consists of one
+ * or more pairs of a 1-octet MSD-Type and 1-octet
+ * MSD-Value */
+ num_msd = length / 2;
+
+ /* Unpack MSDs */
+ for (int i = 0; i < num_msd; i++) {
+ msd_type = stream_getc(s);
+
+ switch (msd_type) {
+ case MSD_TYPE_BASE_MPLS_IMPOSITION:
+ /* BMI-MSD type as per RFC 8491 */
+ rcap->msd = stream_getc(s);
+ break;
+ case ISIS_SUBTLV_SRV6_MAX_SL_MSD:
+ /* SRv6 Maximum Segments Left MSD Type
+ * as per RFC 9352 section #4.1 */
+ rcap->srv6_msd.max_seg_left_msd =
+ stream_getc(s);
+ break;
+ case ISIS_SUBTLV_SRV6_MAX_END_POP_MSD:
+ /* SRv6 Maximum End Pop MSD Type as per
+ * RFC 9352 section #4.2 */
+ rcap->srv6_msd.max_end_pop_msd =
+ stream_getc(s);
+ break;
+ case ISIS_SUBTLV_SRV6_MAX_H_ENCAPS_MSD:
+ /* SRv6 Maximum H.Encaps MSD Type as per
+ * RFC 9352 section #4.3 */
+ rcap->srv6_msd.max_h_encaps_msd =
+ stream_getc(s);
+ break;
+ case ISIS_SUBTLV_SRV6_MAX_END_D_MSD:
+ /* SRv6 Maximum End D MSD Type as per
+ * RFC 9352 section #4.4 */
+ rcap->srv6_msd.max_end_d_msd =
+ stream_getc(s);
+ break;
+ default:
+ /* Unknown MSD, let's skip it */
+ sbuf_push(
+ log, indent,
+ "WARNING: Skipping unknown MSD Type %hhu (1 byte)\n",
+ msd_type);
+ stream_forward_getp(s, 1);
+ }
+ }
+ break;
+#ifndef FABRICD
+ case ISIS_SUBTLV_FAD:
+ fad = XCALLOC(MTYPE_ISIS_TLV,
+ sizeof(struct isis_router_cap_fad));
+ fad->fad.algorithm = stream_getc(s);
+ fad->fad.metric_type = stream_getc(s);
+ fad->fad.calc_type = stream_getc(s);
+ fad->fad.priority = stream_getc(s);
+ rcap->fads[fad->fad.algorithm] = fad;
+ admin_group_init(&fad->fad.admin_group_exclude_any);
+ admin_group_init(&fad->fad.admin_group_include_any);
+ admin_group_init(&fad->fad.admin_group_include_all);
+
+ subsubtlvs_len = length - 4;
+ while (subsubtlvs_len > 2) {
+ struct admin_group *ag;
+ uint8_t subsubtlv_type;
+ uint8_t subsubtlv_len;
+ uint32_t v;
+ int n_ag, i;
+
+ subsubtlv_type = stream_getc(s);
+ subsubtlv_len = stream_getc(s);
+
+ switch (subsubtlv_type) {
+ case ISIS_SUBTLV_FAD_SUBSUBTLV_EXCAG:
+ ag = &fad->fad.admin_group_exclude_any;
+ n_ag = subsubtlv_len / sizeof(uint32_t);
+ for (i = 0; i < n_ag; i++) {
+ v = stream_getl(s);
+ admin_group_bulk_set(ag, v, i);
+ }
+ break;
+ case ISIS_SUBTLV_FAD_SUBSUBTLV_INCANYAG:
+ ag = &fad->fad.admin_group_include_any;
+ n_ag = subsubtlv_len / sizeof(uint32_t);
+ for (i = 0; i < n_ag; i++) {
+ v = stream_getl(s);
+ admin_group_bulk_set(ag, v, i);
+ }
+ break;
+ case ISIS_SUBTLV_FAD_SUBSUBTLV_INCALLAG:
+ ag = &fad->fad.admin_group_include_all;
+ n_ag = subsubtlv_len / sizeof(uint32_t);
+ for (i = 0; i < n_ag; i++) {
+ v = stream_getl(s);
+ admin_group_bulk_set(ag, v, i);
+ }
+ break;
+ case ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS:
+ if (subsubtlv_len == 0)
+ break;
+
+ fad->fad.flags = stream_getc(s);
+ for (i = subsubtlv_len - 1; i > 0; --i)
+ stream_getc(s);
+ break;
+ case ISIS_SUBTLV_FAD_SUBSUBTLV_ESRLG:
+ fad->fad.exclude_srlg = true;
+ stream_forward_getp(s, subsubtlv_len);
+ break;
+ default:
+ sbuf_push(
+ log, indent,
+ "Received an unsupported Flex-Algo sub-TLV type %u\n",
+ subsubtlv_type);
+ fad->fad.unsupported_subtlv = true;
+ stream_forward_getp(s, subsubtlv_len);
+ break;
+ }
+ subsubtlvs_len -= 2 + subsubtlv_len;
+ }
+ break;
+#endif /* ifndef FABRICD */
+ case ISIS_SUBTLV_SRV6_CAPABILITIES:
+ sbuf_push(log, indent,
+ "Unpacking SRv6 Capabilities sub-TLV...\n");
+ /* Check that SRv6 capabilities sub-TLV is correctly
+ * formated */
+ if (length < ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE) {
+ sbuf_push(
+ log, indent,
+ "WARNING: Unexpected SRv6 Capabilities sub-TLV size (expected %d or more bytes, got %hhu)\n",
+ ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE,
+ length);
+ stream_forward_getp(s, length);
+ break;
+ }
+ /* Only one SRv6 capabilities is supported. Skip
+ * subsequent one */
+ if (rcap->srv6_cap.is_srv6_capable) {
+ sbuf_push(
+ log, indent,
+ "WARNING: SRv6 Capabilities sub-TLV present multiple times, ignoring.\n");
+ stream_forward_getp(s, length);
+ break;
+ }
+ rcap->srv6_cap.is_srv6_capable = true;
+ rcap->srv6_cap.flags = stream_getw(s);
+
+ /* The SRv6 Capabilities Sub-TLV may contain optional
+ * Sub-Sub-TLVs, as per RFC 9352 section #2.
+ * Skip any Sub-Sub-TLV contained in the SRv6
+ * Capabilities Sub-TLV that is not currently supported
+ * by IS-IS.
+ */
+ if (length > ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE)
+ sbuf_push(
+ log, indent,
+ "Skipping unknown sub-TLV (%hhu bytes)\n",
+ length);
+ stream_forward_getp(
+ s, length - ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE);
+
+ break;
+ default:
+ stream_forward_getp(s, length);
+ break;
+ }
+ subtlv_len = subtlv_len - length - 2;
+ }
+ tlvs->router_cap = rcap;
+ return 0;
+}
+
+/* Functions related to TLV 10 Authentication */
+static struct isis_item *copy_item_auth(struct isis_item *i)
+{
+ struct isis_auth *auth = (struct isis_auth *)i;
+ struct isis_auth *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->type = auth->type;
+ rv->length = auth->length;
+ memcpy(rv->value, auth->value, sizeof(rv->value));
+ return (struct isis_item *)rv;
+}
+
+static void format_item_auth(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ struct isis_auth *auth = (struct isis_auth *)i;
+ char obuf[768];
+
+ if (json)
+ json_object_string_add(json, "test-auth", "ok");
+ else
+ sbuf_push(buf, indent, "Authentication:\n");
+ switch (auth->type) {
+ case ISIS_PASSWD_TYPE_CLEARTXT:
+ zlog_sanitize(obuf, sizeof(obuf), auth->value, auth->length);
+ if (json)
+ json_object_string_add(json, "auth-pass", obuf);
+ else
+ sbuf_push(buf, indent, " Password: %s\n", obuf);
+ break;
+ case ISIS_PASSWD_TYPE_HMAC_MD5:
+ for (unsigned int j = 0; j < 16; j++) {
+ snprintf(obuf + 2 * j, sizeof(obuf) - 2 * j, "%02hhx",
+ auth->value[j]);
+ }
+ if (json)
+ json_object_string_add(json, "auth-hmac-md5", obuf);
+ else
+ sbuf_push(buf, indent, " HMAC-MD5: %s\n", obuf);
+ break;
+ default:
+ if (json)
+ json_object_int_add(json, "auth-unknown", auth->type);
+ else
+ sbuf_push(buf, indent, " Unknown (%hhu)\n",
+ auth->type);
+ break;
+ }
+}
+
+static void free_item_auth(struct isis_item *i)
+{
+ XFREE(MTYPE_ISIS_TLV, i);
+}
+
+static int pack_item_auth(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_auth *auth = (struct isis_auth *)i;
+
+ if (STREAM_WRITEABLE(s) < 1) {
+ *min_len = 1;
+ return 1;
+ }
+ stream_putc(s, auth->type);
+
+ switch (auth->type) {
+ case ISIS_PASSWD_TYPE_CLEARTXT:
+ if (STREAM_WRITEABLE(s) < auth->length) {
+ *min_len = 1 + auth->length;
+ return 1;
+ }
+ stream_put(s, auth->passwd, auth->length);
+ break;
+ case ISIS_PASSWD_TYPE_HMAC_MD5:
+ if (STREAM_WRITEABLE(s) < 16) {
+ *min_len = 1 + 16;
+ return 1;
+ }
+ auth->offset = stream_get_endp(s);
+ stream_put(s, NULL, 16);
+ break;
+ default:
+ return 1;
+ }
+
+ return 0;
+}
+
+static int unpack_item_auth(uint16_t mtid, uint8_t len, struct stream *s,
+ struct sbuf *log, void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+
+ sbuf_push(log, indent, "Unpack Auth TLV...\n");
+ if (len < 1) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left.(Expected 1 bytes of auth type, got %hhu)\n",
+ len);
+ return 1;
+ }
+
+ struct isis_auth *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->type = stream_getc(s);
+ rv->length = len - 1;
+
+ if (rv->type == ISIS_PASSWD_TYPE_HMAC_MD5 && rv->length != 16) {
+ sbuf_push(
+ log, indent,
+ "Unexpected auth length for HMAC-MD5 (expected 16, got %hhu)\n",
+ rv->length);
+ XFREE(MTYPE_ISIS_TLV, rv);
+ return 1;
+ }
+
+ rv->offset = stream_get_getp(s);
+ stream_get(rv->value, s, rv->length);
+ format_item_auth(mtid, (struct isis_item *)rv, log, NULL, indent + 2);
+ append_item(&tlvs->isis_auth, (struct isis_item *)rv);
+ return 0;
+}
+
+/* Functions related to TLV 13 Purge Originator */
+
+static struct isis_purge_originator *copy_tlv_purge_originator(
+ struct isis_purge_originator *poi)
+{
+ if (!poi)
+ return NULL;
+
+ struct isis_purge_originator *rv;
+
+ rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+ rv->sender_set = poi->sender_set;
+ memcpy(rv->generator, poi->generator, sizeof(rv->generator));
+ if (poi->sender_set)
+ memcpy(rv->sender, poi->sender, sizeof(rv->sender));
+ return rv;
+}
+
+static void format_tlv_purge_originator(struct isis_purge_originator *poi,
+ struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ char sen_id[ISO_SYSID_STRLEN];
+ char gen_id[ISO_SYSID_STRLEN];
+
+ if (!poi)
+ return;
+
+ snprintfrr(gen_id, ISO_SYSID_STRLEN, "%pSY", poi->generator);
+ if (poi->sender_set)
+ snprintfrr(sen_id, ISO_SYSID_STRLEN, "%pSY", poi->sender);
+
+ if (json) {
+ struct json_object *purge_json;
+ purge_json = json_object_new_object();
+ json_object_object_add(json, "purge_originator", purge_json);
+
+ json_object_string_add(purge_json, "id", gen_id);
+ if (poi->sender_set)
+ json_object_string_add(purge_json, "rec-from", sen_id);
+ } else {
+ sbuf_push(buf, indent, "Purge Originator Identification:\n");
+ sbuf_push(buf, indent, " Generator: %s\n", gen_id);
+ if (poi->sender_set)
+ sbuf_push(buf, indent, " Received-From: %s\n", sen_id);
+ }
+}
+
+static void free_tlv_purge_originator(struct isis_purge_originator *poi)
+{
+ XFREE(MTYPE_ISIS_TLV, poi);
+}
+
+static int pack_tlv_purge_originator(struct isis_purge_originator *poi,
+ struct stream *s)
+{
+ if (!poi)
+ return 0;
+
+ uint8_t data_len = 1 + sizeof(poi->generator);
+
+ if (poi->sender_set)
+ data_len += sizeof(poi->sender);
+
+ if (STREAM_WRITEABLE(s) < (unsigned)(2 + data_len))
+ return 1;
+
+ stream_putc(s, ISIS_TLV_PURGE_ORIGINATOR);
+ stream_putc(s, data_len);
+ stream_putc(s, poi->sender_set ? 2 : 1);
+ stream_put(s, poi->generator, sizeof(poi->generator));
+ if (poi->sender_set)
+ stream_put(s, poi->sender, sizeof(poi->sender));
+ return 0;
+}
+
+static int unpack_tlv_purge_originator(enum isis_tlv_context context,
+ uint8_t tlv_type, uint8_t tlv_len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+ struct isis_purge_originator poi = {};
+
+ sbuf_push(log, indent, "Unpacking Purge Originator Identification TLV...\n");
+ if (tlv_len < 7) {
+ sbuf_push(log, indent, "Not enough data left. (Expected at least 7 bytes, got %hhu)\n", tlv_len);
+ return 1;
+ }
+
+ uint8_t number_of_ids = stream_getc(s);
+
+ if (number_of_ids == 1) {
+ poi.sender_set = false;
+ } else if (number_of_ids == 2) {
+ poi.sender_set = true;
+ } else {
+ sbuf_push(log, indent, "Got invalid value for number of system IDs: %hhu)\n", number_of_ids);
+ return 1;
+ }
+
+ if (tlv_len != 1 + 6 * number_of_ids) {
+ sbuf_push(log, indent, "Incorrect tlv len for number of IDs.\n");
+ return 1;
+ }
+
+ stream_get(poi.generator, s, sizeof(poi.generator));
+ if (poi.sender_set)
+ stream_get(poi.sender, s, sizeof(poi.sender));
+
+ if (tlvs->purge_originator) {
+ sbuf_push(log, indent,
+ "WARNING: Purge originator present multiple times, ignoring.\n");
+ return 0;
+ }
+
+ tlvs->purge_originator = copy_tlv_purge_originator(&poi);
+ return 0;
+}
+
+
+/* Functions relating to item TLVs */
+
+static void init_item_list(struct isis_item_list *items)
+{
+ items->head = NULL;
+ items->tail = &items->head;
+ items->count = 0;
+}
+
+static struct isis_item *copy_item(enum isis_tlv_context context,
+ enum isis_tlv_type type,
+ struct isis_item *item)
+{
+ const struct tlv_ops *ops = tlv_table[context][type];
+
+ if (ops && ops->copy_item)
+ return ops->copy_item(item);
+
+ assert(!"Unknown item tlv type!");
+ return NULL;
+}
+
+static void copy_items(enum isis_tlv_context context, enum isis_tlv_type type,
+ struct isis_item_list *src, struct isis_item_list *dest)
+{
+ struct isis_item *item;
+
+ init_item_list(dest);
+
+ for (item = src->head; item; item = item->next) {
+ append_item(dest, copy_item(context, type, item));
+ }
+}
+
+static void format_item(uint16_t mtid, enum isis_tlv_context context,
+ enum isis_tlv_type type, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json, int indent)
+{
+ const struct tlv_ops *ops = tlv_table[context][type];
+
+ if (ops && ops->format_item) {
+ ops->format_item(mtid, i, buf, json, indent);
+ return;
+ }
+
+ assert(!"Unknown item tlv type!");
+}
+
+static void format_items_(uint16_t mtid, enum isis_tlv_context context,
+ enum isis_tlv_type type, struct isis_item_list *items,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ struct isis_item *i;
+
+ for (i = items->head; i; i = i->next)
+ format_item(mtid, context, type, i, buf, json, indent);
+}
+
+static void free_item(enum isis_tlv_context tlv_context,
+ enum isis_tlv_type tlv_type, struct isis_item *item)
+{
+ const struct tlv_ops *ops = tlv_table[tlv_context][tlv_type];
+
+ if (ops && ops->free_item) {
+ ops->free_item(item);
+ return;
+ }
+
+ assert(!"Unknown item tlv type!");
+}
+
+static void free_items(enum isis_tlv_context context, enum isis_tlv_type type,
+ struct isis_item_list *items)
+{
+ struct isis_item *item, *next_item;
+
+ for (item = items->head; item; item = next_item) {
+ next_item = item->next;
+ free_item(context, type, item);
+ }
+}
+
+static int pack_item(enum isis_tlv_context context, enum isis_tlv_type type,
+ struct isis_item *i, struct stream *s, size_t *min_len,
+ struct isis_tlvs **fragment_tlvs,
+ const struct pack_order_entry *pe, uint16_t mtid)
+{
+ const struct tlv_ops *ops = tlv_table[context][type];
+
+ if (ops && ops->pack_item) {
+ return ops->pack_item(i, s, min_len);
+ }
+
+ assert(!"Unknown item tlv type!");
+ return 1;
+}
+
+static void add_item_to_fragment(struct isis_item *i,
+ const struct pack_order_entry *pe,
+ struct isis_tlvs *fragment_tlvs, uint16_t mtid)
+{
+ struct isis_item_list *l;
+
+ if (pe->how_to_pack == ISIS_ITEMS) {
+ l = (struct isis_item_list *)(((char *)fragment_tlvs) + pe->what_to_pack);
+ } else {
+ struct isis_mt_item_list *m;
+ m = (struct isis_mt_item_list *)(((char *)fragment_tlvs) + pe->what_to_pack);
+ l = isis_get_mt_items(m, mtid);
+ }
+
+ append_item(l, copy_item(pe->context, pe->type, i));
+}
+
+static int pack_items_(uint16_t mtid, enum isis_tlv_context context,
+ enum isis_tlv_type type, struct isis_item_list *items,
+ struct stream *s, struct isis_tlvs **fragment_tlvs,
+ const struct pack_order_entry *pe,
+ struct isis_tlvs *(*new_fragment)(struct list *l),
+ struct list *new_fragment_arg)
+{
+ size_t len_pos, last_len, len;
+ struct isis_item *item = NULL;
+ int rv;
+ size_t min_len = 0;
+
+ if (!items->head)
+ return 0;
+
+top:
+ if (STREAM_WRITEABLE(s) < 2)
+ goto too_long;
+
+ stream_putc(s, type);
+ len_pos = stream_get_endp(s);
+ stream_putc(s, 0); /* Put 0 as length for now */
+
+ if (context == ISIS_CONTEXT_LSP && IS_COMPAT_MT_TLV(type)
+ && mtid != ISIS_MT_IPV4_UNICAST) {
+ if (STREAM_WRITEABLE(s) < 2)
+ goto too_long;
+ stream_putw(s, mtid);
+ }
+
+ /* The SRv6 Locator TLV (RFC 9352 section #7.1) starts with the MTID
+ * field */
+ if (context == ISIS_CONTEXT_LSP && type == ISIS_TLV_SRV6_LOCATOR) {
+ if (STREAM_WRITEABLE(s) < 2)
+ goto too_long;
+ stream_putw(s, mtid);
+ }
+
+ if (context == ISIS_CONTEXT_LSP && type == ISIS_TLV_OLDSTYLE_REACH) {
+ if (STREAM_WRITEABLE(s) < 1)
+ goto too_long;
+ stream_putc(s, 0); /* Virtual flag is set to 0 */
+ }
+
+ last_len = len = 0;
+ for (item = item ? item : items->head; item; item = item->next) {
+ rv = pack_item(context, type, item, s, &min_len, fragment_tlvs,
+ pe, mtid);
+ if (rv)
+ goto too_long;
+
+ len = stream_get_endp(s) - len_pos - 1;
+
+ /* Multiple auths don't go into one TLV, so always break */
+ if (context == ISIS_CONTEXT_LSP && type == ISIS_TLV_AUTH) {
+ item = item->next;
+ break;
+ }
+
+ /* Multiple prefix-sids don't go into one TLV, so always break */
+ if (type == ISIS_SUBTLV_PREFIX_SID
+ && (context == ISIS_CONTEXT_SUBTLV_IP_REACH
+ || context == ISIS_CONTEXT_SUBTLV_IPV6_REACH)) {
+ item = item->next;
+ break;
+ }
+
+ if (len > 255) {
+ if (!last_len) /* strange, not a single item fit */
+ return 1;
+ /* drop last tlv, otherwise, its too long */
+ stream_set_endp(s, len_pos + 1 + last_len);
+ len = last_len;
+ break;
+ }
+
+ if (fragment_tlvs)
+ add_item_to_fragment(item, pe, *fragment_tlvs, mtid);
+
+ last_len = len;
+ }
+
+ stream_putc_at(s, len_pos, len);
+ if (item)
+ goto top;
+
+ return 0;
+too_long:
+ if (!fragment_tlvs)
+ return 1;
+ stream_reset(s);
+ if (STREAM_WRITEABLE(s) < min_len)
+ return 1;
+ *fragment_tlvs = new_fragment(new_fragment_arg);
+ goto top;
+}
+#define pack_items(...) pack_items_(ISIS_MT_IPV4_UNICAST, __VA_ARGS__)
+
+static void append_item(struct isis_item_list *dest, struct isis_item *item)
+{
+ *dest->tail = item;
+ dest->tail = &(*dest->tail)->next;
+ dest->count++;
+}
+
+static void delete_item(struct isis_item_list *dest, struct isis_item *del)
+{
+ struct isis_item *item, *prev = NULL, *next;
+
+ /* Sanity Check */
+ if ((dest == NULL) || (del == NULL))
+ return;
+
+ /*
+ * TODO: delete is tricky because "dest" is a singly linked list.
+ * We need to switch a doubly linked list.
+ */
+ for (item = dest->head; item; item = next) {
+ if (item->next == del) {
+ prev = item;
+ break;
+ }
+ next = item->next;
+ }
+ if (prev)
+ prev->next = del->next;
+ if (dest->head == del)
+ dest->head = del->next;
+ if ((struct isis_item *)dest->tail == del) {
+ *dest->tail = prev;
+ if (prev)
+ dest->tail = &(*dest->tail)->next;
+ else
+ dest->tail = &dest->head;
+ }
+ dest->count--;
+}
+
+static struct isis_item *last_item(struct isis_item_list *list)
+{
+ return container_of(list->tail, struct isis_item, next);
+}
+
+static int unpack_item(uint16_t mtid, enum isis_tlv_context context,
+ uint8_t tlv_type, uint8_t len, struct stream *s,
+ struct sbuf *log, void *dest, int indent)
+{
+ const struct tlv_ops *ops = tlv_table[context][tlv_type];
+
+ if (ops && ops->unpack_item)
+ return ops->unpack_item(mtid, len, s, log, dest, indent);
+
+ assert(!"Unknown item tlv type!");
+ sbuf_push(log, indent, "Unknown item tlv type!\n");
+ return 1;
+}
+
+static int unpack_tlv_with_items(enum isis_tlv_context context,
+ uint8_t tlv_type, uint8_t tlv_len,
+ struct stream *s, struct sbuf *log, void *dest,
+ int indent)
+{
+ size_t tlv_start;
+ size_t tlv_pos;
+ int rv;
+ uint16_t mtid;
+
+ tlv_start = stream_get_getp(s);
+ tlv_pos = 0;
+
+ if (context == ISIS_CONTEXT_LSP &&
+ (IS_COMPAT_MT_TLV(tlv_type) || tlv_type == ISIS_TLV_SRV6_LOCATOR)) {
+ if (tlv_len < 2) {
+ sbuf_push(log, indent,
+ "TLV is too short to contain MTID\n");
+ return 1;
+ }
+ mtid = stream_getw(s) & ISIS_MT_MASK;
+ tlv_pos += 2;
+ sbuf_push(log, indent, "Unpacking as MT %s item TLV...\n",
+ isis_mtid2str_fake(mtid));
+ } else {
+ sbuf_push(log, indent, "Unpacking as item TLV...\n");
+ mtid = ISIS_MT_IPV4_UNICAST;
+ }
+
+ if (context == ISIS_CONTEXT_LSP
+ && tlv_type == ISIS_TLV_OLDSTYLE_REACH) {
+ if (tlv_len - tlv_pos < 1) {
+ sbuf_push(log, indent,
+ "TLV is too short for old style reach\n");
+ return 1;
+ }
+ stream_forward_getp(s, 1);
+ tlv_pos += 1;
+ }
+
+ if (context == ISIS_CONTEXT_LSP
+ && tlv_type == ISIS_TLV_OLDSTYLE_IP_REACH) {
+ struct isis_tlvs *tlvs = dest;
+ dest = &tlvs->oldstyle_ip_reach;
+ } else if (context == ISIS_CONTEXT_LSP
+ && tlv_type == ISIS_TLV_OLDSTYLE_IP_REACH_EXT) {
+ struct isis_tlvs *tlvs = dest;
+ dest = &tlvs->oldstyle_ip_reach_ext;
+ }
+
+ if (context == ISIS_CONTEXT_LSP
+ && tlv_type == ISIS_TLV_MT_ROUTER_INFO) {
+ struct isis_tlvs *tlvs = dest;
+ tlvs->mt_router_info_empty = (tlv_pos >= (size_t)tlv_len);
+ }
+
+ while (tlv_pos < (size_t)tlv_len) {
+ rv = unpack_item(mtid, context, tlv_type, tlv_len - tlv_pos, s,
+ log, dest, indent + 2);
+ if (rv)
+ return rv;
+
+ tlv_pos = stream_get_getp(s) - tlv_start;
+ }
+
+ return 0;
+}
+
+/* Functions to manipulate mt_item_lists */
+
+static int isis_mt_item_list_cmp(const struct isis_item_list *a,
+ const struct isis_item_list *b)
+{
+ if (a->mtid < b->mtid)
+ return -1;
+ if (a->mtid > b->mtid)
+ return 1;
+ return 0;
+}
+
+RB_PROTOTYPE(isis_mt_item_list, isis_item_list, mt_tree, isis_mt_item_list_cmp);
+RB_GENERATE(isis_mt_item_list, isis_item_list, mt_tree, isis_mt_item_list_cmp);
+
+struct isis_item_list *isis_get_mt_items(struct isis_mt_item_list *m,
+ uint16_t mtid)
+{
+ struct isis_item_list *rv;
+
+ rv = isis_lookup_mt_items(m, mtid);
+ if (!rv) {
+ rv = XCALLOC(MTYPE_ISIS_MT_ITEM_LIST, sizeof(*rv));
+ init_item_list(rv);
+ rv->mtid = mtid;
+ RB_INSERT(isis_mt_item_list, m, rv);
+ }
+
+ return rv;
+}
+
+struct isis_item_list *isis_lookup_mt_items(struct isis_mt_item_list *m,
+ uint16_t mtid)
+{
+ struct isis_item_list key = {.mtid = mtid};
+
+ return RB_FIND(isis_mt_item_list, m, &key);
+}
+
+static void free_mt_items(enum isis_tlv_context context,
+ enum isis_tlv_type type, struct isis_mt_item_list *m)
+{
+ struct isis_item_list *n, *nnext;
+
+ RB_FOREACH_SAFE (n, isis_mt_item_list, m, nnext) {
+ free_items(context, type, n);
+ RB_REMOVE(isis_mt_item_list, m, n);
+ XFREE(MTYPE_ISIS_MT_ITEM_LIST, n);
+ }
+}
+
+static void format_mt_items(enum isis_tlv_context context,
+ enum isis_tlv_type type,
+ struct isis_mt_item_list *m, struct sbuf *buf,
+ struct json_object *json, int indent)
+{
+ struct isis_item_list *n;
+
+ RB_FOREACH (n, isis_mt_item_list, m) {
+ format_items_(n->mtid, context, type, n, buf, json, indent);
+ }
+}
+
+static int pack_mt_items(enum isis_tlv_context context, enum isis_tlv_type type,
+ struct isis_mt_item_list *m, struct stream *s,
+ struct isis_tlvs **fragment_tlvs,
+ const struct pack_order_entry *pe,
+ struct isis_tlvs *(*new_fragment)(struct list *l),
+ struct list *new_fragment_arg)
+{
+ struct isis_item_list *n;
+
+ RB_FOREACH (n, isis_mt_item_list, m) {
+ int rv;
+
+ rv = pack_items_(n->mtid, context, type, n, s, fragment_tlvs,
+ pe, new_fragment, new_fragment_arg);
+ if (rv)
+ return rv;
+ }
+
+ return 0;
+}
+
+static void copy_mt_items(enum isis_tlv_context context,
+ enum isis_tlv_type type,
+ struct isis_mt_item_list *src,
+ struct isis_mt_item_list *dest)
+{
+ struct isis_item_list *n;
+
+ RB_INIT(isis_mt_item_list, dest);
+
+ RB_FOREACH (n, isis_mt_item_list, src) {
+ copy_items(context, type, n, isis_get_mt_items(dest, n->mtid));
+ }
+}
+
+/* Functions related to TLV 27 SRv6 Locator as per RFC 9352 section #7.1*/
+static struct isis_item *copy_item_srv6_locator(struct isis_item *i)
+{
+ struct isis_srv6_locator_tlv *loc = (struct isis_srv6_locator_tlv *)i;
+ struct isis_srv6_locator_tlv *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->metric = loc->metric;
+ rv->flags = loc->flags;
+ rv->algorithm = loc->algorithm;
+ rv->prefix = loc->prefix;
+ rv->subtlvs = copy_subtlvs(loc->subtlvs);
+
+ return (struct isis_item *)rv;
+}
+
+static void format_item_srv6_locator(uint16_t mtid, struct isis_item *i,
+ struct sbuf *buf, struct json_object *json,
+ int indent)
+{
+ struct isis_srv6_locator_tlv *loc = (struct isis_srv6_locator_tlv *)i;
+
+ if (json) {
+ struct json_object *loc_json;
+ loc_json = json_object_new_object();
+ json_object_object_add(json, "srv6-locator", loc_json);
+ json_object_int_add(loc_json, "mt-id", mtid);
+ json_object_string_addf(loc_json, "prefix", "%pFX",
+ &loc->prefix);
+ json_object_int_add(loc_json, "metric", loc->metric);
+ json_object_string_add(
+ loc_json, "d-flag",
+ CHECK_FLAG(loc->flags, ISIS_TLV_SRV6_LOCATOR_FLAG_D)
+ ? "yes"
+ : "");
+ json_object_int_add(loc_json, "algorithm", loc->algorithm);
+ json_object_string_add(loc_json, "mt-name",
+ isis_mtid2str(mtid));
+ if (loc->subtlvs) {
+ struct json_object *subtlvs_json;
+ subtlvs_json = json_object_new_object();
+ json_object_object_add(loc_json, "subtlvs",
+ subtlvs_json);
+ format_subtlvs(loc->subtlvs, NULL, subtlvs_json, 0);
+ }
+ } else {
+ sbuf_push(buf, indent, "SRv6 Locator: %pFX (Metric: %u)%s",
+ &loc->prefix, loc->metric,
+ CHECK_FLAG(loc->flags, ISIS_TLV_SRV6_LOCATOR_FLAG_D)
+ ? " D-flag"
+ : "");
+ sbuf_push(buf, 0, " %s\n", isis_mtid2str(mtid));
+
+ if (loc->subtlvs) {
+ sbuf_push(buf, indent, " Sub-TLVs:\n");
+ format_subtlvs(loc->subtlvs, buf, NULL, indent + 4);
+ }
+ }
+}
+
+static void free_item_srv6_locator(struct isis_item *i)
+{
+ struct isis_srv6_locator_tlv *item = (struct isis_srv6_locator_tlv *)i;
+
+ isis_free_subtlvs(item->subtlvs);
+ XFREE(MTYPE_ISIS_TLV, item);
+}
+
+static int pack_item_srv6_locator(struct isis_item *i, struct stream *s,
+ size_t *min_len)
+{
+ struct isis_srv6_locator_tlv *loc = (struct isis_srv6_locator_tlv *)i;
+
+ if (STREAM_WRITEABLE(s) < 7 + (unsigned)PSIZE(loc->prefix.prefixlen)) {
+ *min_len = 7 + (unsigned)PSIZE(loc->prefix.prefixlen);
+ return 1;
+ }
+
+ stream_putl(s, loc->metric);
+ stream_putc(s, loc->flags);
+ stream_putc(s, loc->algorithm);
+ /* Locator size */
+ stream_putc(s, loc->prefix.prefixlen);
+ /* Locator prefix */
+ stream_put(s, &loc->prefix.prefix.s6_addr,
+ PSIZE(loc->prefix.prefixlen));
+
+ if (loc->subtlvs) {
+ /* Pack Sub-TLVs */
+ if (pack_subtlvs(loc->subtlvs, s))
+ return 1;
+ } else {
+ /* No Sub-TLVs */
+ if (STREAM_WRITEABLE(s) < 1) {
+ *min_len = 8 + (unsigned)PSIZE(loc->prefix.prefixlen);
+ return 1;
+ }
+
+ /* Put 0 as Sub-TLV length, because we have no Sub-TLVs */
+ stream_putc(s, 0);
+ }
+
+ return 0;
+}
+
+static int unpack_item_srv6_locator(uint16_t mtid, uint8_t len,
+ struct stream *s, struct sbuf *log,
+ void *dest, int indent)
+{
+ struct isis_tlvs *tlvs = dest;
+ struct isis_srv6_locator_tlv *rv = NULL;
+ size_t consume;
+ uint8_t subtlv_len;
+ struct isis_item_list *items;
+
+ items = isis_get_mt_items(&tlvs->srv6_locator, mtid);
+
+ sbuf_push(log, indent, "Unpacking SRv6 Locator...\n");
+ consume = 7;
+ if (len < consume) {
+ sbuf_push(
+ log, indent,
+ "Not enough data left. (expected 7 or more bytes, got %hhu)\n",
+ len);
+ goto out;
+ }
+
+ rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ rv->metric = stream_getl(s);
+ rv->flags = stream_getc(s);
+ rv->algorithm = stream_getc(s);
+
+ rv->prefix.family = AF_INET6;
+ rv->prefix.prefixlen = stream_getc(s);
+ if (rv->prefix.prefixlen > IPV6_MAX_BITLEN) {
+ sbuf_push(log, indent, "Loc Size %u is implausible for SRv6\n",
+ rv->prefix.prefixlen);
+ goto out;
+ }
+
+ consume += PSIZE(rv->prefix.prefixlen);
+ if (len < consume) {
+ sbuf_push(
+ log, indent,
+ "Expected %u bytes of prefix, but only %u bytes available.\n",
+ PSIZE(rv->prefix.prefixlen), len - 7);
+ goto out;
+ }
+ stream_get(&rv->prefix.prefix.s6_addr, s, PSIZE(rv->prefix.prefixlen));
+
+ struct in6_addr orig_locator = rv->prefix.prefix;
+ apply_mask_ipv6(&rv->prefix);
+ if (memcmp(&orig_locator, &rv->prefix.prefix, sizeof(orig_locator)))
+ sbuf_push(log, indent + 2,
+ "WARNING: SRv6 Locator had hostbits set.\n");
+ format_item_srv6_locator(mtid, (struct isis_item *)rv, log, NULL,
+ indent + 2);
+
+ consume += 1;
+ if (len < consume) {
+ sbuf_push(
+ log, indent,
+ "Expected 1 byte of subtlv len, but no more data persent.\n");
+ goto out;
+ }
+ subtlv_len = stream_getc(s);
+
+ if (subtlv_len) {
+ consume += subtlv_len;
+ if (len < consume) {
+ sbuf_push(
+ log, indent,
+ "Expected %hhu bytes of subtlvs, but only %u bytes available.\n",
+ subtlv_len,
+ len - 7 - PSIZE(rv->prefix.prefixlen));
+ goto out;
+ }
+
+ rv->subtlvs =
+ isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_SRV6_LOCATOR);
+
+ bool unpacked_known_tlvs = false;
+ if (unpack_tlvs(ISIS_CONTEXT_SUBTLV_SRV6_LOCATOR, subtlv_len, s,
+ log, rv->subtlvs, indent + 4,
+ &unpacked_known_tlvs)) {
+ goto out;
+ }
+ if (!unpacked_known_tlvs) {
+ isis_free_subtlvs(rv->subtlvs);
+ rv->subtlvs = NULL;
+ }
+ }
+
+ append_item(items, (struct isis_item *)rv);
+ return 0;
+out:
+ if (rv)
+ free_item_srv6_locator((struct isis_item *)rv);
+ return 1;
+}
+
+/* Functions related to tlvs in general */
+
+struct isis_tlvs *isis_alloc_tlvs(void)
+{
+ struct isis_tlvs *result;
+
+ result = XCALLOC(MTYPE_ISIS_TLV, sizeof(*result));
+
+ init_item_list(&result->isis_auth);
+ init_item_list(&result->area_addresses);
+ init_item_list(&result->mt_router_info);
+ init_item_list(&result->oldstyle_reach);
+ init_item_list(&result->lan_neighbor);
+ init_item_list(&result->lsp_entries);
+ init_item_list(&result->extended_reach);
+ RB_INIT(isis_mt_item_list, &result->mt_reach);
+ init_item_list(&result->oldstyle_ip_reach);
+ init_item_list(&result->oldstyle_ip_reach_ext);
+ init_item_list(&result->ipv4_address);
+ init_item_list(&result->ipv6_address);
+ init_item_list(&result->global_ipv6_address);
+ init_item_list(&result->extended_ip_reach);
+ RB_INIT(isis_mt_item_list, &result->mt_ip_reach);
+ init_item_list(&result->ipv6_reach);
+ RB_INIT(isis_mt_item_list, &result->mt_ipv6_reach);
+ RB_INIT(isis_mt_item_list, &result->srv6_locator);
+
+ return result;
+}
+
+struct isis_tlvs *isis_copy_tlvs(struct isis_tlvs *tlvs)
+{
+ struct isis_tlvs *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv));
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth,
+ &rv->isis_auth);
+
+ rv->purge_originator =
+ copy_tlv_purge_originator(tlvs->purge_originator);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES,
+ &tlvs->area_addresses, &rv->area_addresses);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO,
+ &tlvs->mt_router_info, &rv->mt_router_info);
+
+ rv->mt_router_info_empty = tlvs->mt_router_info_empty;
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_REACH,
+ &tlvs->oldstyle_reach, &rv->oldstyle_reach);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_LAN_NEIGHBORS,
+ &tlvs->lan_neighbor, &rv->lan_neighbor);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_LSP_ENTRY, &tlvs->lsp_entries,
+ &rv->lsp_entries);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_REACH,
+ &tlvs->extended_reach, &rv->extended_reach);
+
+ copy_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_REACH, &tlvs->mt_reach,
+ &rv->mt_reach);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH,
+ &tlvs->oldstyle_ip_reach, &rv->oldstyle_ip_reach);
+
+ copy_tlv_protocols_supported(&tlvs->protocols_supported,
+ &rv->protocols_supported);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH_EXT,
+ &tlvs->oldstyle_ip_reach_ext, &rv->oldstyle_ip_reach_ext);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV4_ADDRESS, &tlvs->ipv4_address,
+ &rv->ipv4_address);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_ADDRESS, &tlvs->ipv6_address,
+ &rv->ipv6_address);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_GLOBAL_IPV6_ADDRESS,
+ &tlvs->global_ipv6_address, &rv->global_ipv6_address);
+
+ rv->te_router_id = copy_tlv_te_router_id(tlvs->te_router_id);
+
+ rv->te_router_id_ipv6 =
+ copy_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_IP_REACH,
+ &tlvs->extended_ip_reach, &rv->extended_ip_reach);
+
+ copy_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IP_REACH,
+ &tlvs->mt_ip_reach, &rv->mt_ip_reach);
+
+ rv->hostname = copy_tlv_dynamic_hostname(tlvs->hostname);
+
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_REACH, &tlvs->ipv6_reach,
+ &rv->ipv6_reach);
+
+ copy_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IPV6_REACH,
+ &tlvs->mt_ipv6_reach, &rv->mt_ipv6_reach);
+
+ rv->threeway_adj = copy_tlv_threeway_adj(tlvs->threeway_adj);
+
+ rv->router_cap = copy_tlv_router_cap(tlvs->router_cap);
+
+ rv->spine_leaf = copy_tlv_spine_leaf(tlvs->spine_leaf);
+
+ copy_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_SRV6_LOCATOR,
+ &tlvs->srv6_locator, &rv->srv6_locator);
+
+ return rv;
+}
+
+static void format_tlvs(struct isis_tlvs *tlvs, struct sbuf *buf, struct json_object *json, int indent)
+{
+ format_tlv_protocols_supported(&tlvs->protocols_supported, buf, json,
+ indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth, buf,
+ json, indent);
+
+ format_tlv_purge_originator(tlvs->purge_originator, buf, json, indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES,
+ &tlvs->area_addresses, buf, json, indent);
+
+ if (tlvs->mt_router_info_empty) {
+ if (json)
+ json_object_string_add(json, "mt-router-info", "none");
+ else
+ sbuf_push(buf, indent, "MT Router Info: None\n");
+ } else {
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO,
+ &tlvs->mt_router_info, buf, json, indent);
+ }
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_REACH,
+ &tlvs->oldstyle_reach, buf, json, indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_LAN_NEIGHBORS,
+ &tlvs->lan_neighbor, buf, json, indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_LSP_ENTRY, &tlvs->lsp_entries,
+ buf, json, indent);
+
+ format_tlv_dynamic_hostname(tlvs->hostname, buf, json, indent);
+ format_tlv_te_router_id(tlvs->te_router_id, buf, json, indent);
+ format_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6, buf, json,
+ indent);
+ if (json)
+ format_tlv_router_cap_json(tlvs->router_cap, json);
+ else
+ format_tlv_router_cap(tlvs->router_cap, buf, indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_REACH,
+ &tlvs->extended_reach, buf, json, indent);
+
+ format_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_REACH, &tlvs->mt_reach,
+ buf, json, indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH,
+ &tlvs->oldstyle_ip_reach, buf, json, indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH_EXT,
+ &tlvs->oldstyle_ip_reach_ext, buf, json, indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV4_ADDRESS,
+ &tlvs->ipv4_address, buf, json, indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_ADDRESS,
+ &tlvs->ipv6_address, buf, json, indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_GLOBAL_IPV6_ADDRESS,
+ &tlvs->global_ipv6_address, buf, json, indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_IP_REACH,
+ &tlvs->extended_ip_reach, buf, json, indent);
+
+ format_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IP_REACH,
+ &tlvs->mt_ip_reach, buf, json, indent);
+
+ format_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_REACH, &tlvs->ipv6_reach,
+ buf, json, indent);
+
+ format_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IPV6_REACH,
+ &tlvs->mt_ipv6_reach, buf, json, indent);
+
+ format_tlv_threeway_adj(tlvs->threeway_adj, buf, json, indent);
+
+ format_tlv_spine_leaf(tlvs->spine_leaf, buf, json, indent);
+
+ format_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_SRV6_LOCATOR,
+ &tlvs->srv6_locator, buf, json, indent);
+}
+
+const char *isis_format_tlvs(struct isis_tlvs *tlvs, struct json_object *json)
+{
+ if (json) {
+ format_tlvs(tlvs, NULL, json, 0);
+ return NULL;
+ } else {
+ static struct sbuf buf;
+
+ if (!sbuf_buf(&buf))
+ sbuf_init(&buf, NULL, 0);
+
+ sbuf_reset(&buf);
+ format_tlvs(tlvs, &buf, NULL, 0);
+ return sbuf_buf(&buf);
+ }
+}
+
+void isis_free_tlvs(struct isis_tlvs *tlvs)
+{
+ if (!tlvs)
+ return;
+
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth);
+ free_tlv_purge_originator(tlvs->purge_originator);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES,
+ &tlvs->area_addresses);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO,
+ &tlvs->mt_router_info);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_REACH,
+ &tlvs->oldstyle_reach);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_LAN_NEIGHBORS,
+ &tlvs->lan_neighbor);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_LSP_ENTRY, &tlvs->lsp_entries);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_REACH,
+ &tlvs->extended_reach);
+ free_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_REACH, &tlvs->mt_reach);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH,
+ &tlvs->oldstyle_ip_reach);
+ free_tlv_protocols_supported(&tlvs->protocols_supported);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH_EXT,
+ &tlvs->oldstyle_ip_reach_ext);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV4_ADDRESS,
+ &tlvs->ipv4_address);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_ADDRESS,
+ &tlvs->ipv6_address);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_GLOBAL_IPV6_ADDRESS,
+ &tlvs->global_ipv6_address);
+ free_tlv_te_router_id(tlvs->te_router_id);
+ free_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_IP_REACH,
+ &tlvs->extended_ip_reach);
+ free_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IP_REACH,
+ &tlvs->mt_ip_reach);
+ free_tlv_dynamic_hostname(tlvs->hostname);
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_REACH, &tlvs->ipv6_reach);
+ free_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IPV6_REACH,
+ &tlvs->mt_ipv6_reach);
+ free_tlv_threeway_adj(tlvs->threeway_adj);
+ free_tlv_router_cap(tlvs->router_cap);
+ free_tlv_spine_leaf(tlvs->spine_leaf);
+ free_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_SRV6_LOCATOR,
+ &tlvs->srv6_locator);
+
+ XFREE(MTYPE_ISIS_TLV, tlvs);
+}
+
+static void add_padding(struct stream *s)
+{
+ while (STREAM_WRITEABLE(s)) {
+ if (STREAM_WRITEABLE(s) == 1)
+ break;
+ uint32_t padding_len = STREAM_WRITEABLE(s) - 2;
+
+ if (padding_len > 255) {
+ if (padding_len == 256)
+ padding_len = 254;
+ else
+ padding_len = 255;
+ }
+
+ stream_putc(s, ISIS_TLV_PADDING);
+ stream_putc(s, padding_len);
+ stream_put(s, NULL, padding_len);
+ }
+}
+
+#define LSP_REM_LIFETIME_OFF 10
+#define LSP_CHECKSUM_OFF 24
+static void safe_auth_md5(struct stream *s, uint16_t *checksum,
+ uint16_t *rem_lifetime)
+{
+ memcpy(rem_lifetime, STREAM_DATA(s) + LSP_REM_LIFETIME_OFF,
+ sizeof(*rem_lifetime));
+ memset(STREAM_DATA(s) + LSP_REM_LIFETIME_OFF, 0, sizeof(*rem_lifetime));
+ memcpy(checksum, STREAM_DATA(s) + LSP_CHECKSUM_OFF, sizeof(*checksum));
+ memset(STREAM_DATA(s) + LSP_CHECKSUM_OFF, 0, sizeof(*checksum));
+}
+
+static void restore_auth_md5(struct stream *s, uint16_t checksum,
+ uint16_t rem_lifetime)
+{
+ memcpy(STREAM_DATA(s) + LSP_REM_LIFETIME_OFF, &rem_lifetime,
+ sizeof(rem_lifetime));
+ memcpy(STREAM_DATA(s) + LSP_CHECKSUM_OFF, &checksum, sizeof(checksum));
+}
+
+static void update_auth_hmac_md5(struct isis_auth *auth, struct stream *s,
+ bool is_lsp)
+{
+ uint8_t digest[16];
+ uint16_t checksum, rem_lifetime;
+
+ if (is_lsp)
+ safe_auth_md5(s, &checksum, &rem_lifetime);
+
+ memset(STREAM_DATA(s) + auth->offset, 0, 16);
+#ifdef CRYPTO_OPENSSL
+ uint8_t *result = (uint8_t *)HMAC(EVP_md5(), auth->passwd,
+ auth->plength, STREAM_DATA(s),
+ stream_get_endp(s), NULL, NULL);
+
+ memcpy(digest, result, 16);
+#elif CRYPTO_INTERNAL
+ hmac_md5(STREAM_DATA(s), stream_get_endp(s), auth->passwd,
+ auth->plength, digest);
+#endif
+ memcpy(auth->value, digest, 16);
+ memcpy(STREAM_DATA(s) + auth->offset, digest, 16);
+
+ if (is_lsp)
+ restore_auth_md5(s, checksum, rem_lifetime);
+}
+
+static void update_auth(struct isis_tlvs *tlvs, struct stream *s, bool is_lsp)
+{
+ struct isis_auth *auth_head = (struct isis_auth *)tlvs->isis_auth.head;
+
+ for (struct isis_auth *auth = auth_head; auth; auth = auth->next) {
+ if (auth->type == ISIS_PASSWD_TYPE_HMAC_MD5)
+ update_auth_hmac_md5(auth, s, is_lsp);
+ }
+}
+
+static int handle_pack_entry(const struct pack_order_entry *pe,
+ struct isis_tlvs *tlvs, struct stream *stream,
+ struct isis_tlvs **fragment_tlvs,
+ struct isis_tlvs *(*new_fragment)(struct list *l),
+ struct list *new_fragment_arg)
+{
+ int rv;
+
+ if (pe->how_to_pack == ISIS_ITEMS) {
+ struct isis_item_list *l;
+ l = (struct isis_item_list *)(((char *)tlvs)
+ + pe->what_to_pack);
+ rv = pack_items(pe->context, pe->type, l, stream, fragment_tlvs,
+ pe, new_fragment, new_fragment_arg);
+ } else {
+ struct isis_mt_item_list *l;
+ l = (struct isis_mt_item_list *)(((char *)tlvs)
+ + pe->what_to_pack);
+ rv = pack_mt_items(pe->context, pe->type, l, stream,
+ fragment_tlvs, pe, new_fragment,
+ new_fragment_arg);
+ }
+
+ return rv;
+}
+
+static int pack_tlvs(struct isis_tlvs *tlvs, struct stream *stream,
+ struct isis_tlvs *fragment_tlvs,
+ struct isis_tlvs *(*new_fragment)(struct list *l),
+ struct list *new_fragment_arg)
+{
+ int rv;
+
+ /* When fragmenting, don't add auth as it's already accounted for in the
+ * size we are given. */
+ if (!fragment_tlvs) {
+ rv = pack_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH,
+ &tlvs->isis_auth, stream, NULL, NULL, NULL,
+ NULL);
+ if (rv)
+ return rv;
+ }
+
+ rv = pack_tlv_purge_originator(tlvs->purge_originator, stream);
+ if (rv)
+ return rv;
+ if (fragment_tlvs) {
+ fragment_tlvs->purge_originator =
+ copy_tlv_purge_originator(tlvs->purge_originator);
+ }
+
+ rv = pack_tlv_protocols_supported(&tlvs->protocols_supported, stream);
+ if (rv)
+ return rv;
+ if (fragment_tlvs) {
+ copy_tlv_protocols_supported(
+ &tlvs->protocols_supported,
+ &fragment_tlvs->protocols_supported);
+ }
+
+ rv = pack_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES,
+ &tlvs->area_addresses, stream, NULL, NULL, NULL, NULL);
+ if (rv)
+ return rv;
+ if (fragment_tlvs) {
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES,
+ &tlvs->area_addresses,
+ &fragment_tlvs->area_addresses);
+ }
+
+
+ if (tlvs->mt_router_info_empty) {
+ if (STREAM_WRITEABLE(stream) < 2)
+ return 1;
+ stream_putc(stream, ISIS_TLV_MT_ROUTER_INFO);
+ stream_putc(stream, 0);
+ if (fragment_tlvs)
+ fragment_tlvs->mt_router_info_empty = true;
+ } else {
+ rv = pack_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO,
+ &tlvs->mt_router_info, stream, NULL, NULL, NULL,
+ NULL);
+ if (rv)
+ return rv;
+ if (fragment_tlvs) {
+ copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO,
+ &tlvs->mt_router_info,
+ &fragment_tlvs->mt_router_info);
+ }
+ }
+
+ rv = pack_tlv_dynamic_hostname(tlvs->hostname, stream);
+ if (rv)
+ return rv;
+ if (fragment_tlvs)
+ fragment_tlvs->hostname =
+ copy_tlv_dynamic_hostname(tlvs->hostname);
+
+ rv = pack_tlv_router_cap(tlvs->router_cap, stream);
+ if (rv)
+ return rv;
+ if (fragment_tlvs) {
+ fragment_tlvs->router_cap =
+ copy_tlv_router_cap(tlvs->router_cap);
+ }
+
+ rv = pack_tlv_te_router_id(tlvs->te_router_id, stream);
+ if (rv)
+ return rv;
+ if (fragment_tlvs) {
+ fragment_tlvs->te_router_id =
+ copy_tlv_te_router_id(tlvs->te_router_id);
+ }
+
+ rv = pack_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6, stream);
+ if (rv)
+ return rv;
+ if (fragment_tlvs) {
+ fragment_tlvs->te_router_id_ipv6 =
+ copy_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6);
+ }
+
+ rv = pack_tlv_threeway_adj(tlvs->threeway_adj, stream);
+ if (rv)
+ return rv;
+ if (fragment_tlvs) {
+ fragment_tlvs->threeway_adj =
+ copy_tlv_threeway_adj(tlvs->threeway_adj);
+ }
+
+ rv = pack_tlv_spine_leaf(tlvs->spine_leaf, stream);
+ if (rv)
+ return rv;
+ if (fragment_tlvs) {
+ fragment_tlvs->spine_leaf =
+ copy_tlv_spine_leaf(tlvs->spine_leaf);
+ }
+
+ for (size_t pack_idx = 0; pack_idx < array_size(pack_order);
+ pack_idx++) {
+ rv = handle_pack_entry(&pack_order[pack_idx], tlvs, stream,
+ fragment_tlvs ? &fragment_tlvs : NULL,
+ new_fragment, new_fragment_arg);
+
+ if (rv)
+ return rv;
+ }
+
+ return 0;
+}
+
+int isis_pack_tlvs(struct isis_tlvs *tlvs, struct stream *stream,
+ size_t len_pointer, bool pad, bool is_lsp)
+{
+ int rv;
+
+ rv = pack_tlvs(tlvs, stream, NULL, NULL, NULL);
+ if (rv)
+ return rv;
+
+ if (pad)
+ add_padding(stream);
+
+ if (len_pointer != (size_t)-1) {
+ stream_putw_at(stream, len_pointer, stream_get_endp(stream));
+ }
+
+ update_auth(tlvs, stream, is_lsp);
+
+ return 0;
+}
+
+static struct isis_tlvs *new_fragment(struct list *l)
+{
+ struct isis_tlvs *rv = isis_alloc_tlvs();
+
+ listnode_add(l, rv);
+ return rv;
+}
+
+struct list *isis_fragment_tlvs(struct isis_tlvs *tlvs, size_t size)
+{
+ struct stream *dummy_stream = stream_new(size);
+ struct list *rv = list_new();
+ struct isis_tlvs *fragment_tlvs = new_fragment(rv);
+
+ if (pack_tlvs(tlvs, dummy_stream, fragment_tlvs, new_fragment, rv)) {
+ struct listnode *node;
+ for (ALL_LIST_ELEMENTS_RO(rv, node, fragment_tlvs))
+ isis_free_tlvs(fragment_tlvs);
+ list_delete(&rv);
+ }
+
+ stream_free(dummy_stream);
+ return rv;
+}
+
+static int unpack_tlv_unknown(enum isis_tlv_context context, uint8_t tlv_type,
+ uint8_t tlv_len, struct stream *s,
+ struct sbuf *log, int indent)
+{
+ stream_forward_getp(s, tlv_len);
+ sbuf_push(log, indent,
+ "Skipping unknown TLV %hhu (%hhu bytes)\n",
+ tlv_type, tlv_len);
+ return 0;
+}
+
+static int unpack_tlv(enum isis_tlv_context context, size_t avail_len,
+ struct stream *stream, struct sbuf *log, void *dest,
+ int indent, bool *unpacked_known_tlvs)
+{
+ uint8_t tlv_type, tlv_len;
+ const struct tlv_ops *ops;
+
+ sbuf_push(log, indent, "Unpacking TLV...\n");
+
+ if (avail_len < 2) {
+ sbuf_push(
+ log, indent + 2,
+ "Available data %zu too short to contain a TLV header.\n",
+ avail_len);
+ return 1;
+ }
+
+ tlv_type = stream_getc(stream);
+ tlv_len = stream_getc(stream);
+
+ sbuf_push(log, indent + 2,
+ "Found TLV of type %hhu and len %hhu.\n",
+ tlv_type, tlv_len);
+
+ if (avail_len < ((size_t)tlv_len) + 2) {
+ sbuf_push(log, indent + 2,
+ "Available data %zu too short for claimed TLV len %hhu.\n",
+ avail_len - 2, tlv_len);
+ return 1;
+ }
+
+ ops = tlv_table[context][tlv_type];
+ if (ops && ops->unpack) {
+ if (unpacked_known_tlvs)
+ *unpacked_known_tlvs = true;
+ return ops->unpack(context, tlv_type, tlv_len, stream, log,
+ dest, indent + 2);
+ }
+
+ return unpack_tlv_unknown(context, tlv_type, tlv_len, stream, log,
+ indent + 2);
+}
+
+static int unpack_tlvs(enum isis_tlv_context context, size_t avail_len,
+ struct stream *stream, struct sbuf *log, void *dest,
+ int indent, bool *unpacked_known_tlvs)
+{
+ int rv;
+ size_t tlv_start, tlv_pos;
+
+ tlv_start = stream_get_getp(stream);
+ tlv_pos = 0;
+
+ sbuf_push(log, indent, "Unpacking %zu bytes of %s...\n", avail_len,
+ (context == ISIS_CONTEXT_LSP) ? "TLVs" : "sub-TLVs");
+
+ while (tlv_pos < avail_len) {
+ rv = unpack_tlv(context, avail_len - tlv_pos, stream, log, dest,
+ indent + 2, unpacked_known_tlvs);
+ if (rv)
+ return rv;
+
+ tlv_pos = stream_get_getp(stream) - tlv_start;
+ }
+
+ return 0;
+}
+
+int isis_unpack_tlvs(size_t avail_len, struct stream *stream,
+ struct isis_tlvs **dest, const char **log)
+{
+ static struct sbuf logbuf;
+ int indent = 0;
+ int rv;
+ struct isis_tlvs *result;
+
+ if (!sbuf_buf(&logbuf))
+ sbuf_init(&logbuf, NULL, 0);
+
+ sbuf_reset(&logbuf);
+ if (avail_len > STREAM_READABLE(stream)) {
+ sbuf_push(&logbuf, indent,
+ "Stream doesn't contain sufficient data. Claimed %zu, available %zu\n",
+ avail_len, STREAM_READABLE(stream));
+ return 1;
+ }
+
+ result = isis_alloc_tlvs();
+ rv = unpack_tlvs(ISIS_CONTEXT_LSP, avail_len, stream, &logbuf, result,
+ indent, NULL);
+
+ *log = sbuf_buf(&logbuf);
+ *dest = result;
+
+ return rv;
+}
+
+#define TLV_OPS(_name_, _desc_) \
+ static const struct tlv_ops tlv_##_name_##_ops = { \
+ .name = _desc_, .unpack = unpack_tlv_##_name_, \
+ }
+
+#define ITEM_TLV_OPS(_name_, _desc_) \
+ static const struct tlv_ops tlv_##_name_##_ops = { \
+ .name = _desc_, \
+ .unpack = unpack_tlv_with_items, \
+ \
+ .pack_item = pack_item_##_name_, \
+ .free_item = free_item_##_name_, \
+ .unpack_item = unpack_item_##_name_, \
+ .format_item = format_item_##_name_, \
+ .copy_item = copy_item_##_name_}
+
+#define SUBTLV_OPS(_name_, _desc_) \
+ static const struct tlv_ops subtlv_##_name_##_ops = { \
+ .name = _desc_, .unpack = unpack_subtlv_##_name_, \
+ }
+
+#define ITEM_SUBTLV_OPS(_name_, _desc_) \
+ ITEM_TLV_OPS(_name_, _desc_)
+
+#define SUBSUBTLV_OPS(_name_, _desc_) \
+ static const struct tlv_ops subsubtlv_##_name_##_ops = { \
+ .name = _desc_, \
+ .unpack = unpack_subsubtlv_##_name_, \
+ }
+
+#define ITEM_SUBSUBTLV_OPS(_name_, _desc_) \
+ ITEM_TLV_OPS(_name_, _desc_)
+
+ITEM_TLV_OPS(area_address, "TLV 1 Area Addresses");
+ITEM_TLV_OPS(oldstyle_reach, "TLV 2 IS Reachability");
+ITEM_TLV_OPS(lan_neighbor, "TLV 6 LAN Neighbors");
+ITEM_TLV_OPS(lsp_entry, "TLV 9 LSP Entries");
+ITEM_TLV_OPS(auth, "TLV 10 IS-IS Auth");
+TLV_OPS(purge_originator, "TLV 13 Purge Originator Identification");
+ITEM_TLV_OPS(extended_reach, "TLV 22 Extended Reachability");
+ITEM_TLV_OPS(oldstyle_ip_reach, "TLV 128/130 IP Reachability");
+TLV_OPS(protocols_supported, "TLV 129 Protocols Supported");
+ITEM_TLV_OPS(ipv4_address, "TLV 132 IPv4 Interface Address");
+TLV_OPS(te_router_id, "TLV 134 TE Router ID");
+ITEM_TLV_OPS(extended_ip_reach, "TLV 135 Extended IP Reachability");
+TLV_OPS(dynamic_hostname, "TLV 137 Dynamic Hostname");
+TLV_OPS(te_router_id_ipv6, "TLV 140 IPv6 TE Router ID");
+TLV_OPS(spine_leaf, "TLV 150 Spine Leaf Extensions");
+ITEM_TLV_OPS(mt_router_info, "TLV 229 MT Router Information");
+TLV_OPS(threeway_adj, "TLV 240 P2P Three-Way Adjacency");
+ITEM_TLV_OPS(ipv6_address, "TLV 232 IPv6 Interface Address");
+ITEM_TLV_OPS(global_ipv6_address, "TLV 233 Global IPv6 Interface Address");
+ITEM_TLV_OPS(ipv6_reach, "TLV 236 IPv6 Reachability");
+TLV_OPS(router_cap, "TLV 242 Router Capability");
+
+ITEM_SUBTLV_OPS(prefix_sid, "Sub-TLV 3 SR Prefix-SID");
+SUBTLV_OPS(ipv6_source_prefix, "Sub-TLV 22 IPv6 Source Prefix");
+
+ITEM_TLV_OPS(srv6_locator, "TLV 27 SRv6 Locator");
+ITEM_SUBTLV_OPS(srv6_end_sid, "Sub-TLV 5 SRv6 End SID");
+SUBSUBTLV_OPS(srv6_sid_structure, "Sub-Sub-TLV 1 SRv6 SID Structure");
+
+static const struct tlv_ops *const tlv_table[ISIS_CONTEXT_MAX][ISIS_TLV_MAX] = {
+ [ISIS_CONTEXT_LSP] = {
+ [ISIS_TLV_AREA_ADDRESSES] = &tlv_area_address_ops,
+ [ISIS_TLV_OLDSTYLE_REACH] = &tlv_oldstyle_reach_ops,
+ [ISIS_TLV_LAN_NEIGHBORS] = &tlv_lan_neighbor_ops,
+ [ISIS_TLV_LSP_ENTRY] = &tlv_lsp_entry_ops,
+ [ISIS_TLV_AUTH] = &tlv_auth_ops,
+ [ISIS_TLV_PURGE_ORIGINATOR] = &tlv_purge_originator_ops,
+ [ISIS_TLV_EXTENDED_REACH] = &tlv_extended_reach_ops,
+ [ISIS_TLV_OLDSTYLE_IP_REACH] = &tlv_oldstyle_ip_reach_ops,
+ [ISIS_TLV_PROTOCOLS_SUPPORTED] = &tlv_protocols_supported_ops,
+ [ISIS_TLV_OLDSTYLE_IP_REACH_EXT] = &tlv_oldstyle_ip_reach_ops,
+ [ISIS_TLV_IPV4_ADDRESS] = &tlv_ipv4_address_ops,
+ [ISIS_TLV_TE_ROUTER_ID] = &tlv_te_router_id_ops,
+ [ISIS_TLV_TE_ROUTER_ID_IPV6] = &tlv_te_router_id_ipv6_ops,
+ [ISIS_TLV_EXTENDED_IP_REACH] = &tlv_extended_ip_reach_ops,
+ [ISIS_TLV_DYNAMIC_HOSTNAME] = &tlv_dynamic_hostname_ops,
+ [ISIS_TLV_SPINE_LEAF_EXT] = &tlv_spine_leaf_ops,
+ [ISIS_TLV_MT_REACH] = &tlv_extended_reach_ops,
+ [ISIS_TLV_MT_ROUTER_INFO] = &tlv_mt_router_info_ops,
+ [ISIS_TLV_IPV6_ADDRESS] = &tlv_ipv6_address_ops,
+ [ISIS_TLV_GLOBAL_IPV6_ADDRESS] = &tlv_global_ipv6_address_ops,
+ [ISIS_TLV_MT_IP_REACH] = &tlv_extended_ip_reach_ops,
+ [ISIS_TLV_IPV6_REACH] = &tlv_ipv6_reach_ops,
+ [ISIS_TLV_MT_IPV6_REACH] = &tlv_ipv6_reach_ops,
+ [ISIS_TLV_THREE_WAY_ADJ] = &tlv_threeway_adj_ops,
+ [ISIS_TLV_ROUTER_CAPABILITY] = &tlv_router_cap_ops,
+ [ISIS_TLV_SRV6_LOCATOR] = &tlv_srv6_locator_ops,
+ },
+ [ISIS_CONTEXT_SUBTLV_NE_REACH] = {},
+ [ISIS_CONTEXT_SUBTLV_IP_REACH] = {
+ [ISIS_SUBTLV_PREFIX_SID] = &tlv_prefix_sid_ops,
+ },
+ [ISIS_CONTEXT_SUBTLV_IPV6_REACH] = {
+ [ISIS_SUBTLV_PREFIX_SID] = &tlv_prefix_sid_ops,
+ [ISIS_SUBTLV_IPV6_SOURCE_PREFIX] = &subtlv_ipv6_source_prefix_ops,
+ },
+ [ISIS_CONTEXT_SUBTLV_SRV6_LOCATOR] = {
+ [ISIS_SUBTLV_SRV6_END_SID] = &tlv_srv6_end_sid_ops,
+ },
+ [ISIS_CONTEXT_SUBSUBTLV_SRV6_END_SID] = {
+ [ISIS_SUBSUBTLV_SRV6_SID_STRUCTURE] = &subsubtlv_srv6_sid_structure_ops,
+ },
+ [ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID] = {
+ [ISIS_SUBSUBTLV_SRV6_SID_STRUCTURE] = &subsubtlv_srv6_sid_structure_ops,
+ },
+ [ISIS_CONTEXT_SUBSUBTLV_SRV6_LAN_ENDX_SID] = {
+ [ISIS_SUBSUBTLV_SRV6_SID_STRUCTURE] = &subsubtlv_srv6_sid_structure_ops,
+ }
+};
+
+/* Accessor functions */
+
+void isis_tlvs_add_auth(struct isis_tlvs *tlvs, struct isis_passwd *passwd)
+{
+ free_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth);
+ init_item_list(&tlvs->isis_auth);
+
+ if (passwd->type == ISIS_PASSWD_TYPE_UNUSED)
+ return;
+
+ struct isis_auth *auth = XCALLOC(MTYPE_ISIS_TLV, sizeof(*auth));
+
+ auth->type = passwd->type;
+
+ auth->plength = passwd->len;
+ memcpy(auth->passwd, passwd->passwd,
+ MIN(sizeof(auth->passwd), sizeof(passwd->passwd)));
+
+ if (auth->type == ISIS_PASSWD_TYPE_CLEARTXT) {
+ auth->length = passwd->len;
+ memcpy(auth->value, passwd->passwd,
+ MIN(sizeof(auth->value), sizeof(passwd->passwd)));
+ }
+
+ append_item(&tlvs->isis_auth, (struct isis_item *)auth);
+}
+
+void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs,
+ struct list *addresses)
+{
+ struct listnode *node;
+ struct iso_address *area_addr;
+
+ for (ALL_LIST_ELEMENTS_RO(addresses, node, area_addr)) {
+ struct isis_area_address *a =
+ XCALLOC(MTYPE_ISIS_TLV, sizeof(*a));
+
+ a->len = area_addr->addr_len;
+ memcpy(a->addr, area_addr->area_addr, ISO_ADDR_SIZE);
+ append_item(&tlvs->area_addresses, (struct isis_item *)a);
+ }
+}
+
+void isis_tlvs_add_lan_neighbors(struct isis_tlvs *tlvs, struct list *neighbors)
+{
+ struct listnode *node;
+ uint8_t *snpa;
+
+ for (ALL_LIST_ELEMENTS_RO(neighbors, node, snpa)) {
+ struct isis_lan_neighbor *n =
+ XCALLOC(MTYPE_ISIS_TLV, sizeof(*n));
+
+ memcpy(n->mac, snpa, 6);
+ append_item(&tlvs->lan_neighbor, (struct isis_item *)n);
+ }
+}
+
+void isis_tlvs_set_protocols_supported(struct isis_tlvs *tlvs,
+ struct nlpids *nlpids)
+{
+ tlvs->protocols_supported.count = nlpids->count;
+ XFREE(MTYPE_ISIS_TLV, tlvs->protocols_supported.protocols);
+ if (nlpids->count) {
+ tlvs->protocols_supported.protocols =
+ XCALLOC(MTYPE_ISIS_TLV, nlpids->count);
+ memcpy(tlvs->protocols_supported.protocols, nlpids->nlpids,
+ nlpids->count);
+ } else {
+ tlvs->protocols_supported.protocols = NULL;
+ }
+}
+
+void isis_tlvs_add_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid,
+ bool overload, bool attached)
+{
+ struct isis_mt_router_info *i = XCALLOC(MTYPE_ISIS_TLV, sizeof(*i));
+
+ i->overload = overload;
+ i->attached = attached;
+ i->mtid = mtid;
+ append_item(&tlvs->mt_router_info, (struct isis_item *)i);
+}
+
+void isis_tlvs_add_ipv4_address(struct isis_tlvs *tlvs, struct in_addr *addr)
+{
+ struct isis_ipv4_address *a = XCALLOC(MTYPE_ISIS_TLV, sizeof(*a));
+ a->addr = *addr;
+ append_item(&tlvs->ipv4_address, (struct isis_item *)a);
+}
+
+
+void isis_tlvs_add_ipv4_addresses(struct isis_tlvs *tlvs,
+ struct list *addresses)
+{
+ struct listnode *node;
+ struct prefix_ipv4 *ip_addr;
+ unsigned int addr_count = 0;
+
+ for (ALL_LIST_ELEMENTS_RO(addresses, node, ip_addr)) {
+ isis_tlvs_add_ipv4_address(tlvs, &ip_addr->prefix);
+ addr_count++;
+ if (addr_count >= 63)
+ break;
+ }
+}
+
+void isis_tlvs_add_ipv6_addresses(struct isis_tlvs *tlvs,
+ struct list *addresses)
+{
+ struct listnode *node;
+ struct prefix_ipv6 *ip_addr;
+ unsigned int addr_count = 0;
+
+ for (ALL_LIST_ELEMENTS_RO(addresses, node, ip_addr)) {
+ if (addr_count >= 15)
+ break;
+
+ struct isis_ipv6_address *a =
+ XCALLOC(MTYPE_ISIS_TLV, sizeof(*a));
+
+ a->addr = ip_addr->prefix;
+ append_item(&tlvs->ipv6_address, (struct isis_item *)a);
+ addr_count++;
+ }
+}
+
+void isis_tlvs_add_global_ipv6_addresses(struct isis_tlvs *tlvs,
+ struct list *addresses)
+{
+ struct listnode *node;
+ struct prefix_ipv6 *ip_addr;
+ unsigned int addr_count = 0;
+
+ for (ALL_LIST_ELEMENTS_RO(addresses, node, ip_addr)) {
+ if (addr_count >= 15)
+ break;
+
+ struct isis_ipv6_address *a =
+ XCALLOC(MTYPE_ISIS_TLV, sizeof(*a));
+
+ a->addr = ip_addr->prefix;
+ append_item(&tlvs->global_ipv6_address, (struct isis_item *)a);
+ addr_count++;
+ }
+}
+
+typedef bool (*auth_validator_func)(struct isis_passwd *passwd,
+ struct stream *stream,
+ struct isis_auth *auth, bool is_lsp);
+
+static bool auth_validator_cleartxt(struct isis_passwd *passwd,
+ struct stream *stream,
+ struct isis_auth *auth, bool is_lsp)
+{
+ return (auth->length == passwd->len
+ && !memcmp(auth->value, passwd->passwd, passwd->len));
+}
+
+static bool auth_validator_hmac_md5(struct isis_passwd *passwd,
+ struct stream *stream,
+ struct isis_auth *auth, bool is_lsp)
+{
+ uint8_t digest[16];
+ uint16_t checksum;
+ uint16_t rem_lifetime;
+
+ if (is_lsp)
+ safe_auth_md5(stream, &checksum, &rem_lifetime);
+
+ memset(STREAM_DATA(stream) + auth->offset, 0, 16);
+#ifdef CRYPTO_OPENSSL
+ uint8_t *result = (uint8_t *)HMAC(EVP_md5(), passwd->passwd,
+ passwd->len, STREAM_DATA(stream),
+ stream_get_endp(stream), NULL, NULL);
+
+ memcpy(digest, result, 16);
+#elif CRYPTO_INTERNAL
+ hmac_md5(STREAM_DATA(stream), stream_get_endp(stream), passwd->passwd,
+ passwd->len, digest);
+#endif
+ memcpy(STREAM_DATA(stream) + auth->offset, auth->value, 16);
+
+ bool rv = !memcmp(digest, auth->value, 16);
+
+ if (is_lsp)
+ restore_auth_md5(stream, checksum, rem_lifetime);
+
+ return rv;
+}
+
+static const auth_validator_func auth_validators[] = {
+ [ISIS_PASSWD_TYPE_CLEARTXT] = auth_validator_cleartxt,
+ [ISIS_PASSWD_TYPE_HMAC_MD5] = auth_validator_hmac_md5,
+};
+
+int isis_tlvs_auth_is_valid(struct isis_tlvs *tlvs, struct isis_passwd *passwd,
+ struct stream *stream, bool is_lsp)
+{
+ /* If no auth is set, always pass authentication */
+ if (!passwd->type)
+ return ISIS_AUTH_OK;
+
+ /* If we don't known how to validate the auth, return invalid */
+ if (passwd->type >= array_size(auth_validators)
+ || !auth_validators[passwd->type])
+ return ISIS_AUTH_NO_VALIDATOR;
+
+ struct isis_auth *auth_head = (struct isis_auth *)tlvs->isis_auth.head;
+ struct isis_auth *auth;
+ for (auth = auth_head; auth; auth = auth->next) {
+ if (auth->type == passwd->type)
+ break;
+ }
+
+ /* If matching auth TLV could not be found, return invalid */
+ if (!auth)
+ return ISIS_AUTH_TYPE_FAILURE;
+
+
+ /* Perform validation and return result */
+ if (auth_validators[passwd->type](passwd, stream, auth, is_lsp))
+ return ISIS_AUTH_OK;
+ else
+ return ISIS_AUTH_FAILURE;
+}
+
+bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs,
+ struct list *addresses)
+{
+ struct isis_area_address *addr_head;
+
+ addr_head = (struct isis_area_address *)tlvs->area_addresses.head;
+ for (struct isis_area_address *addr = addr_head; addr;
+ addr = addr->next) {
+ struct listnode *node;
+ struct iso_address *a;
+
+ for (ALL_LIST_ELEMENTS_RO(addresses, node, a)) {
+ if (a->addr_len == addr->len
+ && !memcmp(a->area_addr, addr->addr, addr->len))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void tlvs_area_addresses_to_adj(struct isis_tlvs *tlvs,
+ struct isis_adjacency *adj,
+ bool *changed)
+{
+ if (adj->area_address_count != tlvs->area_addresses.count) {
+ uint32_t oc = adj->area_address_count;
+
+ *changed = true;
+ adj->area_address_count = tlvs->area_addresses.count;
+ adj->area_addresses = XREALLOC(
+ MTYPE_ISIS_ADJACENCY_INFO, adj->area_addresses,
+ adj->area_address_count * sizeof(*adj->area_addresses));
+
+ for (; oc < adj->area_address_count; oc++) {
+ adj->area_addresses[oc].addr_len = 0;
+ memset(&adj->area_addresses[oc].area_addr, 0,
+ sizeof(adj->area_addresses[oc].area_addr));
+ }
+ }
+
+ struct isis_area_address *addr = NULL;
+ for (unsigned int i = 0; i < tlvs->area_addresses.count; i++) {
+ if (!addr)
+ addr = (struct isis_area_address *)
+ tlvs->area_addresses.head;
+ else
+ addr = addr->next;
+
+ if (adj->area_addresses[i].addr_len == addr->len
+ && !memcmp(adj->area_addresses[i].area_addr, addr->addr,
+ addr->len)) {
+ continue;
+ }
+
+ *changed = true;
+ adj->area_addresses[i].addr_len = addr->len;
+ memcpy(adj->area_addresses[i].area_addr, addr->addr, addr->len);
+ }
+}
+
+static void tlvs_protocols_supported_to_adj(struct isis_tlvs *tlvs,
+ struct isis_adjacency *adj,
+ bool *changed)
+{
+ bool ipv4_supported = false, ipv6_supported = false;
+
+ for (uint8_t i = 0; i < tlvs->protocols_supported.count; i++) {
+ if (tlvs->protocols_supported.protocols[i] == NLPID_IP)
+ ipv4_supported = true;
+ if (tlvs->protocols_supported.protocols[i] == NLPID_IPV6)
+ ipv6_supported = true;
+ }
+
+ struct nlpids reduced = {};
+
+ if (ipv4_supported && ipv6_supported) {
+ reduced.count = 2;
+ reduced.nlpids[0] = NLPID_IP;
+ reduced.nlpids[1] = NLPID_IPV6;
+ } else if (ipv4_supported) {
+ reduced.count = 1;
+ reduced.nlpids[0] = NLPID_IP;
+ } else if (ipv6_supported) {
+ reduced.count = 1;
+ reduced.nlpids[0] = NLPID_IPV6;
+ } else {
+ reduced.count = 0;
+ }
+
+ if (adj->nlpids.count == reduced.count
+ && !memcmp(adj->nlpids.nlpids, reduced.nlpids, reduced.count))
+ return;
+
+ *changed = true;
+ adj->nlpids.count = reduced.count;
+ memcpy(adj->nlpids.nlpids, reduced.nlpids, reduced.count);
+}
+
+DEFINE_HOOK(isis_adj_ip_enabled_hook,
+ (struct isis_adjacency * adj, int family, bool global),
+ (adj, family, global));
+DEFINE_HOOK(isis_adj_ip_disabled_hook,
+ (struct isis_adjacency * adj, int family, bool global),
+ (adj, family, global));
+
+static void tlvs_ipv4_addresses_to_adj(struct isis_tlvs *tlvs,
+ struct isis_adjacency *adj,
+ bool *changed)
+{
+ bool ipv4_enabled = false;
+
+ if (adj->ipv4_address_count == 0 && tlvs->ipv4_address.count > 0)
+ ipv4_enabled = true;
+ else if (adj->ipv4_address_count > 0 && tlvs->ipv4_address.count == 0)
+ hook_call(isis_adj_ip_disabled_hook, adj, AF_INET, false);
+
+ if (adj->ipv4_address_count != tlvs->ipv4_address.count) {
+ uint32_t oc = adj->ipv4_address_count;
+
+ *changed = true;
+ adj->ipv4_address_count = tlvs->ipv4_address.count;
+ adj->ipv4_addresses = XREALLOC(
+ MTYPE_ISIS_ADJACENCY_INFO, adj->ipv4_addresses,
+ adj->ipv4_address_count * sizeof(*adj->ipv4_addresses));
+
+ for (; oc < adj->ipv4_address_count; oc++) {
+ memset(&adj->ipv4_addresses[oc], 0,
+ sizeof(adj->ipv4_addresses[oc]));
+ }
+ }
+
+ struct isis_ipv4_address *addr = NULL;
+ for (unsigned int i = 0; i < tlvs->ipv4_address.count; i++) {
+ if (!addr)
+ addr = (struct isis_ipv4_address *)
+ tlvs->ipv4_address.head;
+ else
+ addr = addr->next;
+
+ if (!memcmp(&adj->ipv4_addresses[i], &addr->addr,
+ sizeof(addr->addr)))
+ continue;
+
+ *changed = true;
+ adj->ipv4_addresses[i] = addr->addr;
+ }
+
+ if (ipv4_enabled)
+ hook_call(isis_adj_ip_enabled_hook, adj, AF_INET, false);
+}
+
+static void tlvs_ipv6_addresses_to_adj(struct isis_tlvs *tlvs,
+ struct isis_adjacency *adj,
+ bool *changed)
+{
+ bool ipv6_enabled = false;
+
+ if (adj->ll_ipv6_count == 0 && tlvs->ipv6_address.count > 0)
+ ipv6_enabled = true;
+ else if (adj->ll_ipv6_count > 0 && tlvs->ipv6_address.count == 0)
+ hook_call(isis_adj_ip_disabled_hook, adj, AF_INET6, false);
+
+ if (adj->ll_ipv6_count != tlvs->ipv6_address.count) {
+ uint32_t oc = adj->ll_ipv6_count;
+
+ *changed = true;
+ adj->ll_ipv6_count = tlvs->ipv6_address.count;
+ adj->ll_ipv6_addrs = XREALLOC(
+ MTYPE_ISIS_ADJACENCY_INFO, adj->ll_ipv6_addrs,
+ adj->ll_ipv6_count * sizeof(*adj->ll_ipv6_addrs));
+
+ for (; oc < adj->ll_ipv6_count; oc++) {
+ memset(&adj->ll_ipv6_addrs[oc], 0,
+ sizeof(adj->ll_ipv6_addrs[oc]));
+ }
+ }
+
+ struct isis_ipv6_address *addr = NULL;
+ for (unsigned int i = 0; i < tlvs->ipv6_address.count; i++) {
+ if (!addr)
+ addr = (struct isis_ipv6_address *)
+ tlvs->ipv6_address.head;
+ else
+ addr = addr->next;
+
+ if (!memcmp(&adj->ll_ipv6_addrs[i], &addr->addr,
+ sizeof(addr->addr)))
+ continue;
+
+ *changed = true;
+ adj->ll_ipv6_addrs[i] = addr->addr;
+ }
+
+ if (ipv6_enabled)
+ hook_call(isis_adj_ip_enabled_hook, adj, AF_INET6, false);
+}
+
+
+static void tlvs_global_ipv6_addresses_to_adj(struct isis_tlvs *tlvs,
+ struct isis_adjacency *adj,
+ bool *changed)
+{
+ bool global_ipv6_enabled = false;
+
+ if (adj->global_ipv6_count == 0 && tlvs->global_ipv6_address.count > 0)
+ global_ipv6_enabled = true;
+ else if (adj->global_ipv6_count > 0
+ && tlvs->global_ipv6_address.count == 0)
+ hook_call(isis_adj_ip_disabled_hook, adj, AF_INET6, true);
+
+ if (adj->global_ipv6_count != tlvs->global_ipv6_address.count) {
+ uint32_t oc = adj->global_ipv6_count;
+
+ *changed = true;
+ adj->global_ipv6_count = tlvs->global_ipv6_address.count;
+ adj->global_ipv6_addrs = XREALLOC(
+ MTYPE_ISIS_ADJACENCY_INFO, adj->global_ipv6_addrs,
+ adj->global_ipv6_count
+ * sizeof(*adj->global_ipv6_addrs));
+
+ for (; oc < adj->global_ipv6_count; oc++) {
+ memset(&adj->global_ipv6_addrs[oc], 0,
+ sizeof(adj->global_ipv6_addrs[oc]));
+ }
+ }
+
+ struct isis_ipv6_address *addr = NULL;
+ for (unsigned int i = 0; i < tlvs->global_ipv6_address.count; i++) {
+ if (!addr)
+ addr = (struct isis_ipv6_address *)
+ tlvs->global_ipv6_address.head;
+ else
+ addr = addr->next;
+
+ if (!memcmp(&adj->global_ipv6_addrs[i], &addr->addr,
+ sizeof(addr->addr)))
+ continue;
+
+ *changed = true;
+ adj->global_ipv6_addrs[i] = addr->addr;
+ }
+
+ if (global_ipv6_enabled)
+ hook_call(isis_adj_ip_enabled_hook, adj, AF_INET6, true);
+}
+
+void isis_tlvs_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj,
+ bool *changed)
+{
+ *changed = false;
+
+ tlvs_area_addresses_to_adj(tlvs, adj, changed);
+ tlvs_protocols_supported_to_adj(tlvs, adj, changed);
+ tlvs_ipv4_addresses_to_adj(tlvs, adj, changed);
+ tlvs_ipv6_addresses_to_adj(tlvs, adj, changed);
+ tlvs_global_ipv6_addresses_to_adj(tlvs, adj, changed);
+}
+
+bool isis_tlvs_own_snpa_found(struct isis_tlvs *tlvs, uint8_t *snpa)
+{
+ struct isis_lan_neighbor *ne_head;
+
+ ne_head = (struct isis_lan_neighbor *)tlvs->lan_neighbor.head;
+ for (struct isis_lan_neighbor *ne = ne_head; ne; ne = ne->next) {
+ if (!memcmp(ne->mac, snpa, ETH_ALEN))
+ return true;
+ }
+
+ return false;
+}
+
+void isis_tlvs_add_lsp_entry(struct isis_tlvs *tlvs, struct isis_lsp *lsp)
+{
+ struct isis_lsp_entry *entry = XCALLOC(MTYPE_ISIS_TLV, sizeof(*entry));
+
+ entry->rem_lifetime = lsp->hdr.rem_lifetime;
+ memcpy(entry->id, lsp->hdr.lsp_id, ISIS_SYS_ID_LEN + 2);
+ entry->checksum = lsp->hdr.checksum;
+ entry->seqno = lsp->hdr.seqno;
+ entry->lsp = lsp;
+
+ append_item(&tlvs->lsp_entries, (struct isis_item *)entry);
+}
+
+void isis_tlvs_add_csnp_entries(struct isis_tlvs *tlvs, uint8_t *start_id,
+ uint8_t *stop_id, uint16_t num_lsps,
+ struct lspdb_head *head,
+ struct isis_lsp **last_lsp)
+{
+ struct isis_lsp searchfor;
+ struct isis_lsp *first, *lsp;
+
+ memcpy(&searchfor.hdr.lsp_id, start_id, sizeof(searchfor.hdr.lsp_id));
+ first = lspdb_find_gteq(head, &searchfor);
+ if (!first)
+ return;
+
+ frr_each_from (lspdb, head, lsp, first) {
+ if (memcmp(lsp->hdr.lsp_id, stop_id, sizeof(lsp->hdr.lsp_id))
+ > 0 || tlvs->lsp_entries.count == num_lsps)
+ break;
+
+ isis_tlvs_add_lsp_entry(tlvs, lsp);
+ *last_lsp = lsp;
+ }
+}
+
+void isis_tlvs_set_dynamic_hostname(struct isis_tlvs *tlvs,
+ const char *hostname)
+{
+ XFREE(MTYPE_ISIS_TLV, tlvs->hostname);
+ if (hostname)
+ tlvs->hostname = XSTRDUP(MTYPE_ISIS_TLV, hostname);
+}
+
+/* Init Router Capability TLV parameters */
+struct isis_router_cap *isis_tlvs_init_router_capability(struct isis_tlvs *tlvs)
+{
+ tlvs->router_cap = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->router_cap));
+
+ /* init SR algo list content to the default value */
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
+ tlvs->router_cap->algo[i] = SR_ALGORITHM_UNSET;
+
+ return tlvs->router_cap;
+}
+
+#ifndef FABRICD
+void isis_tlvs_set_router_capability_fad(struct isis_tlvs *tlvs,
+ struct flex_algo *fa, int algorithm,
+ uint8_t *sysid)
+{
+ struct isis_router_cap_fad *rcap_fad;
+
+ assert(tlvs->router_cap);
+
+ rcap_fad = tlvs->router_cap->fads[algorithm];
+
+ if (!rcap_fad)
+ rcap_fad = XCALLOC(MTYPE_ISIS_TLV,
+ sizeof(struct isis_router_cap_fad));
+
+ memset(rcap_fad->sysid, 0, ISIS_SYS_ID_LEN + 2);
+ memcpy(rcap_fad->sysid, sysid, ISIS_SYS_ID_LEN);
+
+ memcpy(&rcap_fad->fad, fa, sizeof(struct flex_algo));
+
+ rcap_fad->fad.admin_group_exclude_any.bitmap.data = NULL;
+ rcap_fad->fad.admin_group_include_any.bitmap.data = NULL;
+ rcap_fad->fad.admin_group_include_all.bitmap.data = NULL;
+
+ admin_group_copy(&rcap_fad->fad.admin_group_exclude_any,
+ &fa->admin_group_exclude_any);
+ admin_group_copy(&rcap_fad->fad.admin_group_include_any,
+ &fa->admin_group_include_any);
+ admin_group_copy(&rcap_fad->fad.admin_group_include_all,
+ &fa->admin_group_include_all);
+
+ tlvs->router_cap->fads[algorithm] = rcap_fad;
+}
+#endif /* ifndef FABRICD */
+
+int isis_tlvs_sr_algo_count(const struct isis_router_cap *cap)
+{
+ int count = 0;
+
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
+ if (cap->algo[i] != SR_ALGORITHM_UNSET)
+ count++;
+ return count;
+}
+
+void isis_tlvs_set_te_router_id(struct isis_tlvs *tlvs,
+ const struct in_addr *id)
+{
+ XFREE(MTYPE_ISIS_TLV, tlvs->te_router_id);
+ if (!id)
+ return;
+ tlvs->te_router_id = XCALLOC(MTYPE_ISIS_TLV, sizeof(*id));
+ memcpy(tlvs->te_router_id, id, sizeof(*id));
+}
+
+void isis_tlvs_set_te_router_id_ipv6(struct isis_tlvs *tlvs,
+ const struct in6_addr *id)
+{
+ XFREE(MTYPE_ISIS_TLV, tlvs->te_router_id_ipv6);
+ if (!id)
+ return;
+ tlvs->te_router_id_ipv6 = XCALLOC(MTYPE_ISIS_TLV, sizeof(*id));
+ memcpy(tlvs->te_router_id_ipv6, id, sizeof(*id));
+}
+
+void isis_tlvs_add_oldstyle_ip_reach(struct isis_tlvs *tlvs,
+ struct prefix_ipv4 *dest, uint8_t metric)
+{
+ struct isis_oldstyle_ip_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r));
+
+ r->metric = metric;
+ memcpy(&r->prefix, dest, sizeof(*dest));
+ apply_mask_ipv4(&r->prefix);
+ append_item(&tlvs->oldstyle_ip_reach, (struct isis_item *)r);
+}
+
+/* Add IS-IS SR Adjacency-SID subTLVs */
+void isis_tlvs_add_adj_sid(struct isis_ext_subtlvs *exts,
+ struct isis_adj_sid *adj)
+{
+ append_item(&exts->adj_sid, (struct isis_item *)adj);
+ SET_SUBTLV(exts, EXT_ADJ_SID);
+}
+
+/* Delete IS-IS SR Adjacency-SID subTLVs */
+void isis_tlvs_del_adj_sid(struct isis_ext_subtlvs *exts,
+ struct isis_adj_sid *adj)
+{
+ delete_item(&exts->adj_sid, (struct isis_item *)adj);
+ XFREE(MTYPE_ISIS_SUBTLV, adj);
+ if (exts->adj_sid.count == 0)
+ UNSET_SUBTLV(exts, EXT_ADJ_SID);
+}
+
+/* Add IS-IS SR LAN-Adjacency-SID subTLVs */
+void isis_tlvs_add_lan_adj_sid(struct isis_ext_subtlvs *exts,
+ struct isis_lan_adj_sid *lan)
+{
+ append_item(&exts->lan_sid, (struct isis_item *)lan);
+ SET_SUBTLV(exts, EXT_LAN_ADJ_SID);
+}
+
+/* Delete IS-IS SR LAN-Adjacency-SID subTLVs */
+void isis_tlvs_del_lan_adj_sid(struct isis_ext_subtlvs *exts,
+ struct isis_lan_adj_sid *lan)
+{
+ delete_item(&exts->lan_sid, (struct isis_item *)lan);
+ XFREE(MTYPE_ISIS_SUBTLV, lan);
+ if (exts->lan_sid.count == 0)
+ UNSET_SUBTLV(exts, EXT_LAN_ADJ_SID);
+}
+
+/* Add IS-IS SRv6 End.X SID subTLVs */
+void isis_tlvs_add_srv6_endx_sid(struct isis_ext_subtlvs *exts,
+ struct isis_srv6_endx_sid_subtlv *adj)
+{
+ append_item(&exts->srv6_endx_sid, (struct isis_item *)adj);
+ SET_SUBTLV(exts, EXT_SRV6_ENDX_SID);
+}
+
+/* Delete IS-IS SRv6 End.X SID subTLVs */
+void isis_tlvs_del_srv6_endx_sid(struct isis_ext_subtlvs *exts,
+ struct isis_srv6_endx_sid_subtlv *adj)
+{
+ isis_free_subsubtlvs(adj->subsubtlvs);
+ delete_item(&exts->srv6_endx_sid, (struct isis_item *)adj);
+ XFREE(MTYPE_ISIS_SUBTLV, adj);
+ if (exts->srv6_endx_sid.count == 0)
+ UNSET_SUBTLV(exts, EXT_SRV6_ENDX_SID);
+}
+
+/* Add IS-IS SRv6 LAN End.X SID subTLVs */
+void isis_tlvs_add_srv6_lan_endx_sid(struct isis_ext_subtlvs *exts,
+ struct isis_srv6_lan_endx_sid_subtlv *lan)
+{
+ append_item(&exts->srv6_lan_endx_sid, (struct isis_item *)lan);
+ SET_SUBTLV(exts, EXT_SRV6_LAN_ENDX_SID);
+}
+
+/* Delete IS-IS SRv6 LAN End.X SID subTLVs */
+void isis_tlvs_del_srv6_lan_endx_sid(struct isis_ext_subtlvs *exts,
+ struct isis_srv6_lan_endx_sid_subtlv *lan)
+{
+ isis_free_subsubtlvs(lan->subsubtlvs);
+ delete_item(&exts->srv6_lan_endx_sid, (struct isis_item *)lan);
+ XFREE(MTYPE_ISIS_SUBTLV, lan);
+ if (exts->srv6_lan_endx_sid.count == 0)
+ UNSET_SUBTLV(exts, EXT_SRV6_LAN_ENDX_SID);
+}
+
+void isis_tlvs_del_asla_flex_algo(struct isis_ext_subtlvs *ext,
+ struct isis_asla_subtlvs *asla)
+{
+ admin_group_term(&asla->ext_admin_group);
+ listnode_delete(ext->aslas, asla);
+ XFREE(MTYPE_ISIS_SUBTLV, asla);
+}
+
+struct isis_asla_subtlvs *
+isis_tlvs_find_alloc_asla(struct isis_ext_subtlvs *ext, uint8_t standard_apps)
+{
+ struct isis_asla_subtlvs *asla;
+ struct listnode *node;
+
+ if (!list_isempty(ext->aslas)) {
+ for (ALL_LIST_ELEMENTS_RO(ext->aslas, node, asla)) {
+ if (CHECK_FLAG(asla->standard_apps, standard_apps))
+ return asla;
+ }
+ }
+
+ asla = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(struct isis_asla_subtlvs));
+ admin_group_init(&asla->ext_admin_group);
+ SET_FLAG(asla->standard_apps, standard_apps);
+ SET_FLAG(asla->user_def_apps, standard_apps);
+ asla->standard_apps_length = ASLA_APP_IDENTIFIER_BIT_LENGTH;
+ asla->user_def_apps_length = ASLA_APP_IDENTIFIER_BIT_LENGTH;
+
+ listnode_add(ext->aslas, asla);
+ return asla;
+}
+
+void isis_tlvs_free_asla(struct isis_ext_subtlvs *ext, uint8_t standard_apps)
+{
+ struct isis_asla_subtlvs *asla;
+ struct listnode *node;
+
+ if (!ext)
+ return;
+
+ for (ALL_LIST_ELEMENTS_RO(ext->aslas, node, asla)) {
+ if (!CHECK_FLAG(asla->standard_apps, standard_apps))
+ continue;
+ isis_tlvs_del_asla_flex_algo(ext, asla);
+ break;
+ }
+}
+
+void isis_tlvs_add_extended_ip_reach(struct isis_tlvs *tlvs,
+ struct prefix_ipv4 *dest, uint32_t metric,
+ bool external,
+ struct sr_prefix_cfg **pcfgs)
+{
+ struct isis_extended_ip_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r));
+
+ r->metric = metric;
+ memcpy(&r->prefix, dest, sizeof(*dest));
+ apply_mask_ipv4(&r->prefix);
+
+ if (pcfgs) {
+ r->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IP_REACH);
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ struct isis_prefix_sid *psid;
+ struct sr_prefix_cfg *pcfg = pcfgs[i];
+
+ if (!pcfg)
+ continue;
+
+ psid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*psid));
+ isis_sr_prefix_cfg2subtlv(pcfg, external, psid);
+ append_item(&r->subtlvs->prefix_sids,
+ (struct isis_item *)psid);
+ }
+ }
+
+ append_item(&tlvs->extended_ip_reach, (struct isis_item *)r);
+}
+
+void isis_tlvs_add_ipv6_reach(struct isis_tlvs *tlvs, uint16_t mtid,
+ struct prefix_ipv6 *dest, uint32_t metric,
+ bool external, struct sr_prefix_cfg **pcfgs)
+{
+ struct isis_ipv6_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r));
+
+ r->metric = metric;
+ memcpy(&r->prefix, dest, sizeof(*dest));
+ apply_mask_ipv6(&r->prefix);
+ if (pcfgs) {
+ r->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IP_REACH);
+ for (int i = 0; i < SR_ALGORITHM_COUNT; i++) {
+ struct isis_prefix_sid *psid;
+ struct sr_prefix_cfg *pcfg = pcfgs[i];
+
+ if (!pcfg)
+ continue;
+
+ psid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*psid));
+ isis_sr_prefix_cfg2subtlv(pcfg, external, psid);
+ append_item(&r->subtlvs->prefix_sids,
+ (struct isis_item *)psid);
+ }
+ }
+
+ struct isis_item_list *l;
+ l = (mtid == ISIS_MT_IPV4_UNICAST)
+ ? &tlvs->ipv6_reach
+ : isis_get_mt_items(&tlvs->mt_ipv6_reach, mtid);
+ append_item(l, (struct isis_item *)r);
+}
+
+void isis_tlvs_add_ipv6_dstsrc_reach(struct isis_tlvs *tlvs, uint16_t mtid,
+ struct prefix_ipv6 *dest,
+ struct prefix_ipv6 *src,
+ uint32_t metric)
+{
+ isis_tlvs_add_ipv6_reach(tlvs, mtid, dest, metric, false, NULL);
+ struct isis_item_list *l = isis_get_mt_items(&tlvs->mt_ipv6_reach,
+ mtid);
+
+ struct isis_ipv6_reach *r = (struct isis_ipv6_reach*)last_item(l);
+ r->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IPV6_REACH);
+ r->subtlvs->source_prefix = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*src));
+ memcpy(r->subtlvs->source_prefix, src, sizeof(*src));
+}
+
+void isis_tlvs_add_oldstyle_reach(struct isis_tlvs *tlvs, uint8_t *id,
+ uint8_t metric)
+{
+ struct isis_oldstyle_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r));
+
+ r->metric = metric;
+ memcpy(r->id, id, sizeof(r->id));
+ append_item(&tlvs->oldstyle_reach, (struct isis_item *)r);
+}
+
+void isis_tlvs_add_extended_reach(struct isis_tlvs *tlvs, uint16_t mtid,
+ uint8_t *id, uint32_t metric,
+ struct isis_ext_subtlvs *exts)
+{
+ struct isis_extended_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r));
+
+ memcpy(r->id, id, sizeof(r->id));
+ r->metric = metric;
+ if (exts)
+ r->subtlvs = copy_item_ext_subtlvs(exts, mtid);
+
+ struct isis_item_list *l;
+ if ((mtid == ISIS_MT_IPV4_UNICAST) || (mtid == ISIS_MT_DISABLE))
+ l = &tlvs->extended_reach;
+ else
+ l = isis_get_mt_items(&tlvs->mt_reach, mtid);
+ append_item(l, (struct isis_item *)r);
+}
+
+void isis_tlvs_add_threeway_adj(struct isis_tlvs *tlvs,
+ enum isis_threeway_state state,
+ uint32_t local_circuit_id,
+ const uint8_t *neighbor_id,
+ uint32_t neighbor_circuit_id)
+{
+ assert(!tlvs->threeway_adj);
+
+ tlvs->threeway_adj = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->threeway_adj));
+ tlvs->threeway_adj->state = state;
+ tlvs->threeway_adj->local_circuit_id = local_circuit_id;
+
+ if (neighbor_id) {
+ tlvs->threeway_adj->neighbor_set = true;
+ memcpy(tlvs->threeway_adj->neighbor_id, neighbor_id, 6);
+ tlvs->threeway_adj->neighbor_circuit_id = neighbor_circuit_id;
+ }
+}
+
+void isis_tlvs_add_spine_leaf(struct isis_tlvs *tlvs, uint8_t tier,
+ bool has_tier, bool is_leaf, bool is_spine,
+ bool is_backup)
+{
+ assert(!tlvs->spine_leaf);
+
+ tlvs->spine_leaf = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->spine_leaf));
+
+ if (has_tier) {
+ tlvs->spine_leaf->tier = tier;
+ }
+
+ tlvs->spine_leaf->has_tier = has_tier;
+ tlvs->spine_leaf->is_leaf = is_leaf;
+ tlvs->spine_leaf->is_spine = is_spine;
+ tlvs->spine_leaf->is_backup = is_backup;
+}
+
+struct isis_mt_router_info *
+isis_tlvs_lookup_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid)
+{
+ if (!tlvs || tlvs->mt_router_info_empty)
+ return NULL;
+
+ struct isis_mt_router_info *rv;
+ for (rv = (struct isis_mt_router_info *)tlvs->mt_router_info.head; rv;
+ rv = rv->next) {
+ if (rv->mtid == mtid)
+ return rv;
+ }
+
+ return NULL;
+}
+
+void isis_tlvs_set_purge_originator(struct isis_tlvs *tlvs,
+ const uint8_t *generator,
+ const uint8_t *sender)
+{
+ assert(!tlvs->purge_originator);
+
+ tlvs->purge_originator = XCALLOC(MTYPE_ISIS_TLV,
+ sizeof(*tlvs->purge_originator));
+ memcpy(tlvs->purge_originator->generator, generator,
+ sizeof(tlvs->purge_originator->generator));
+ if (sender) {
+ tlvs->purge_originator->sender_set = true;
+ memcpy(tlvs->purge_originator->sender, sender,
+ sizeof(tlvs->purge_originator->sender));
+ }
+}
+
+/* Set SRv6 SID Structure Sub-Sub-TLV parameters */
+void isis_subsubtlvs_set_srv6_sid_structure(struct isis_subsubtlvs *subsubtlvs,
+ struct isis_srv6_sid *sid)
+{
+ assert(!subsubtlvs->srv6_sid_structure);
+
+ subsubtlvs->srv6_sid_structure = XCALLOC(
+ MTYPE_ISIS_SUBSUBTLV, sizeof(*subsubtlvs->srv6_sid_structure));
+
+ isis_srv6_sid_structure2subsubtlv(sid, subsubtlvs->srv6_sid_structure);
+}
+
+/* Add an SRv6 End SID to the SRv6 End SID Sub-TLV */
+void isis_subtlvs_add_srv6_end_sid(struct isis_subtlvs *subtlvs,
+ struct isis_srv6_sid *sid)
+{
+ struct isis_srv6_end_sid_subtlv *sid_subtlv;
+
+ if (!sid)
+ return;
+
+ /* The SRv6 End SID Sub-TLV advertises SRv6 SIDs with Endpoint behaviors
+ * that do not require a particular neighbor in order to be correctly
+ * applied (e.g. End, End.DT6, ...). Before proceeding, let's make sure
+ * we are encoding one of the supported behaviors. */
+ if (sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END &&
+ sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID &&
+ sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT6 &&
+ sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID &&
+ sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT4 &&
+ sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID &&
+ sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT46 &&
+ sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID)
+ return;
+
+ /* Allocate memory for the Sub-TLV */
+ sid_subtlv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*sid_subtlv));
+
+ /* Fill in the SRv6 End SID Sub-TLV according to the SRv6 SID
+ * configuration */
+ isis_srv6_end_sid2subtlv(sid, sid_subtlv);
+
+ /* Add the SRv6 SID Structure Sub-Sub-TLV */
+ sid_subtlv->subsubtlvs =
+ isis_alloc_subsubtlvs(ISIS_CONTEXT_SUBSUBTLV_SRV6_END_SID);
+ isis_subsubtlvs_set_srv6_sid_structure(sid_subtlv->subsubtlvs, sid);
+
+ /* Append the SRv6 End SID Sub-TLV to the Sub-TLVs list */
+ append_item(&subtlvs->srv6_end_sids, (struct isis_item *)sid_subtlv);
+}
+
+/* Add an SRv6 Locator to the SRv6 Locator TLV */
+void isis_tlvs_add_srv6_locator(struct isis_tlvs *tlvs, uint16_t mtid,
+ struct isis_srv6_locator *loc)
+{
+ bool subtlvs_present = false;
+ struct listnode *node;
+ struct isis_srv6_sid *sid;
+ struct isis_srv6_locator_tlv *loc_tlv =
+ XCALLOC(MTYPE_ISIS_TLV, sizeof(*loc_tlv));
+
+ /* Fill in the SRv6 Locator TLV according to the SRv6 Locator
+ * configuration */
+ isis_srv6_locator2tlv(loc, loc_tlv);
+
+ /* Add the SRv6 End SID Sub-TLVs */
+ loc_tlv->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_SRV6_LOCATOR);
+ for (ALL_LIST_ELEMENTS_RO(loc->srv6_sid, node, sid)) {
+ if (sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END ||
+ sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID ||
+ sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT6 ||
+ sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID ||
+ sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT4 ||
+ sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID ||
+ sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT46 ||
+ sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID) {
+ isis_subtlvs_add_srv6_end_sid(loc_tlv->subtlvs, sid);
+ subtlvs_present = true;
+ }
+ }
+
+ if (!subtlvs_present) {
+ isis_free_subtlvs(loc_tlv->subtlvs);
+ loc_tlv->subtlvs = NULL;
+ }
+
+ /* Append the SRv6 Locator TLV to the TLVs list */
+ struct isis_item_list *l;
+ l = isis_get_mt_items(&tlvs->srv6_locator, mtid);
+ append_item(l, (struct isis_item *)loc_tlv);
+}
diff --git a/isisd/isis_tlvs.h b/isisd/isis_tlvs.h
new file mode 100644
index 0000000..6ecd4c5
--- /dev/null
+++ b/isisd/isis_tlvs.h
@@ -0,0 +1,903 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS TLV Serializer/Deserializer
+ *
+ * Copyright (C) 2015,2017 Christian Franke
+
+ * Copyright (C) 2019 Olivier Dugeon - Orange Labs (for TE and SR)
+ *
+ * Copyright (C) 2023 Carmine Scarpitta - University of Rome Tor Vergata
+ * (for IS-IS Extensions to Support SRv6 as per RFC 9352)
+ */
+#ifndef ISIS_TLVS_H
+#define ISIS_TLVS_H
+
+#include "segment_routing.h"
+#include "openbsd-tree.h"
+#include "prefix.h"
+#include "flex_algo.h"
+#include "affinitymap.h"
+
+
+#include "lib/srv6.h"
+
+DECLARE_MTYPE(ISIS_SUBTLV);
+DECLARE_MTYPE(ISIS_SUBSUBTLV);
+
+struct lspdb_head;
+struct sr_prefix_cfg;
+struct isis_srv6_sid;
+struct isis_srv6_locator;
+
+struct isis_area_address {
+ struct isis_area_address *next;
+
+ uint8_t addr[20];
+ uint8_t len;
+};
+
+#define ISIS_WIDE_METRIC_INFINITY 0xFFFFFE
+#define ISIS_NARROW_METRIC_INFINITY 62
+
+struct isis_oldstyle_reach {
+ struct isis_oldstyle_reach *next;
+
+ uint8_t id[7];
+ uint8_t metric;
+};
+
+struct isis_oldstyle_ip_reach {
+ struct isis_oldstyle_ip_reach *next;
+
+ uint8_t metric;
+ struct prefix_ipv4 prefix;
+};
+
+struct isis_lsp_entry {
+ struct isis_lsp_entry *next;
+
+ uint16_t rem_lifetime;
+ uint8_t id[8];
+ uint16_t checksum;
+ uint32_t seqno;
+
+ struct isis_lsp *lsp;
+};
+
+struct isis_extended_reach {
+ struct isis_extended_reach *next;
+
+ uint8_t id[7];
+ uint32_t metric;
+
+ struct isis_ext_subtlvs *subtlvs;
+};
+
+struct isis_extended_ip_reach {
+ struct isis_extended_ip_reach *next;
+
+ uint32_t metric;
+ bool down;
+ struct prefix_ipv4 prefix;
+
+ struct isis_subtlvs *subtlvs;
+};
+
+struct isis_ipv6_reach {
+ struct isis_ipv6_reach *next;
+
+ uint32_t metric;
+ bool down;
+ bool external;
+
+ struct prefix_ipv6 prefix;
+
+ struct isis_subtlvs *subtlvs;
+};
+
+struct isis_protocols_supported {
+ uint8_t count;
+ uint8_t *protocols;
+};
+
+#define ISIS_TIER_UNDEFINED 15
+
+struct isis_spine_leaf {
+ uint8_t tier;
+
+ bool has_tier;
+ bool is_leaf;
+ bool is_spine;
+ bool is_backup;
+};
+
+enum isis_threeway_state {
+ ISIS_THREEWAY_DOWN = 2,
+ ISIS_THREEWAY_INITIALIZING = 1,
+ ISIS_THREEWAY_UP = 0,
+};
+
+struct isis_threeway_adj {
+ enum isis_threeway_state state;
+ uint32_t local_circuit_id;
+ bool neighbor_set;
+ uint8_t neighbor_id[6];
+ uint32_t neighbor_circuit_id;
+};
+
+/* Segment Routing subTLV's as per RFC8667 */
+#define ISIS_SUBTLV_SRGB_FLAG_I 0x80
+#define ISIS_SUBTLV_SRGB_FLAG_V 0x40
+#define IS_SR_IPV4(srgb) ((srgb)->flags & ISIS_SUBTLV_SRGB_FLAG_I)
+#define IS_SR_IPV6(srgb) ((srgb)->flags & ISIS_SUBTLV_SRGB_FLAG_V)
+#define SUBTLV_SR_BLOCK_SIZE 6
+#define SUBTLV_RANGE_INDEX_SIZE 10
+#define SUBTLV_RANGE_LABEL_SIZE 9
+
+/* Structure aggregating SR Global (SRGB) or Local (SRLB) Block info */
+struct isis_sr_block {
+ uint8_t flags;
+ uint32_t range_size;
+ uint32_t lower_bound;
+};
+
+/* Prefix-SID sub-TLVs flags */
+#define ISIS_PREFIX_SID_READVERTISED 0x80
+#define ISIS_PREFIX_SID_NODE 0x40
+#define ISIS_PREFIX_SID_NO_PHP 0x20
+#define ISIS_PREFIX_SID_EXPLICIT_NULL 0x10
+#define ISIS_PREFIX_SID_VALUE 0x08
+#define ISIS_PREFIX_SID_LOCAL 0x04
+
+struct isis_prefix_sid {
+ struct isis_prefix_sid *next;
+
+ uint8_t flags;
+ uint8_t algorithm;
+ uint32_t value;
+};
+
+/* Adj-SID and LAN-Ajd-SID sub-TLVs flags */
+#define EXT_SUBTLV_LINK_ADJ_SID_FFLG 0x80
+#define EXT_SUBTLV_LINK_ADJ_SID_BFLG 0x40
+#define EXT_SUBTLV_LINK_ADJ_SID_VFLG 0x20
+#define EXT_SUBTLV_LINK_ADJ_SID_LFLG 0x10
+#define EXT_SUBTLV_LINK_ADJ_SID_SFLG 0x08
+#define EXT_SUBTLV_LINK_ADJ_SID_PFLG 0x04
+
+struct isis_adj_sid {
+ struct isis_adj_sid *next;
+
+ uint8_t family;
+ uint8_t flags;
+ uint8_t weight;
+ uint32_t sid;
+};
+
+struct isis_lan_adj_sid {
+ struct isis_lan_adj_sid *next;
+
+ uint8_t family;
+ uint8_t flags;
+ uint8_t weight;
+ uint8_t neighbor_id[ISIS_SYS_ID_LEN];
+ uint32_t sid;
+};
+
+/* RFC 4971 & RFC 7981 */
+#define ISIS_ROUTER_CAP_FLAG_S 0x01
+#define ISIS_ROUTER_CAP_FLAG_D 0x02
+#define ISIS_ROUTER_CAP_SIZE 5
+
+#define MSD_TYPE_BASE_MPLS_IMPOSITION 0x01
+#define MSD_TLV_SIZE 2
+
+#ifndef FABRICD
+struct isis_router_cap_fad;
+struct isis_router_cap_fad {
+ uint8_t sysid[ISIS_SYS_ID_LEN + 2];
+
+ struct flex_algo fad;
+};
+#endif /* ifndef FABRICD */
+
+/* SRv6 SID Structure Sub-Sub-TLV as per RFC 9352 section #9 */
+struct isis_srv6_sid_structure_subsubtlv {
+ uint8_t loc_block_len;
+ uint8_t loc_node_len;
+ uint8_t func_len;
+ uint8_t arg_len;
+};
+
+/* SRv6 End SID Sub-TLV as per RFC 9352 section #7.2 */
+struct isis_srv6_end_sid_subtlv {
+ struct isis_srv6_end_sid_subtlv *next;
+
+ uint8_t flags;
+ enum srv6_endpoint_behavior_codepoint behavior;
+ struct in6_addr sid;
+
+ struct isis_subsubtlvs *subsubtlvs;
+};
+
+/* SRv6 End.X SID and SRv6 LAN End.X SID sub-TLVs flags */
+#define EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG 0x20
+#define EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG 0x40
+#define EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG 0x80
+
+/* SRv6 End.X SID Sub-TLV as per RFC 9352 section #8.1 */
+struct isis_srv6_endx_sid_subtlv {
+ struct isis_srv6_endx_sid_subtlv *next;
+
+ uint8_t flags;
+ uint8_t algorithm;
+ uint8_t weight;
+ enum srv6_endpoint_behavior_codepoint behavior;
+ struct in6_addr sid;
+
+ struct isis_subsubtlvs *subsubtlvs;
+};
+
+/* SRv6 End.X SID Sub-TLV as per RFC 9352 section #8.2 */
+struct isis_srv6_lan_endx_sid_subtlv {
+ struct isis_srv6_lan_endx_sid_subtlv *next;
+
+ uint8_t neighbor_id[ISIS_SYS_ID_LEN];
+ uint8_t flags;
+ uint8_t algorithm;
+ uint8_t weight;
+ enum srv6_endpoint_behavior_codepoint behavior;
+ struct in6_addr sid;
+
+ struct isis_subsubtlvs *subsubtlvs;
+};
+
+/* RFC 9352 section 7.1 */
+struct isis_srv6_locator_tlv {
+ struct isis_srv6_locator_tlv *next;
+
+ uint32_t metric;
+
+ uint8_t flags;
+#define ISIS_TLV_SRV6_LOCATOR_FLAG_D 1 << 7
+
+ uint8_t algorithm;
+ struct prefix_ipv6 prefix;
+
+ struct isis_subtlvs *subtlvs;
+};
+
+#define ISIS_SRV6_LOCATOR_HDR_SIZE 22
+
+/* Maximum SRv6 SID Depths (MSD) as per RFC 9352 section #4 */
+struct isis_srv6_msd {
+ /* RFC 9352 section #4.1 */
+ uint8_t max_seg_left_msd;
+ /* RFC 9352 section #4.2 */
+ uint8_t max_end_pop_msd;
+ /* RFC 9352 section #4.3 */
+ uint8_t max_h_encaps_msd;
+ /* RFC 9352 section #4.4 */
+ uint8_t max_end_d_msd;
+};
+
+/* SRv6 Capabilities as per RFC 9352 section #2 */
+struct isis_srv6_cap {
+ bool is_srv6_capable;
+
+ uint16_t flags;
+#define ISIS_SUBTLV_SRV6_FLAG_O 0x4000
+#define SUPPORTS_SRV6_OAM(srv6) \
+ (CHECK_FLAG((srv6)->flags, ISIS_SUBTLV_SRV6_FLAG_O))
+};
+
+struct isis_router_cap {
+ struct in_addr router_id;
+ uint8_t flags;
+
+ /* RFC 8667 section #3 */
+ struct isis_sr_block srgb;
+ struct isis_sr_block srlb;
+ uint8_t algo[SR_ALGORITHM_COUNT];
+ /* RFC 8491 */
+ uint8_t msd;
+
+#ifndef FABRICD
+ /* RFC9350 Flex-Algorithm */
+ struct isis_router_cap_fad *fads[SR_ALGORITHM_COUNT];
+#endif /* ifndef FABRICD */
+
+ /* RFC 9352 section #2 */
+ struct isis_srv6_cap srv6_cap;
+
+ /* RFC 9352 section #4 */
+ struct isis_srv6_msd srv6_msd;
+};
+
+struct isis_item {
+ struct isis_item *next;
+};
+
+struct isis_lan_neighbor {
+ struct isis_lan_neighbor *next;
+
+ uint8_t mac[6];
+};
+
+struct isis_ipv4_address {
+ struct isis_ipv4_address *next;
+
+ struct in_addr addr;
+};
+
+struct isis_ipv6_address {
+ struct isis_ipv6_address *next;
+
+ struct in6_addr addr;
+};
+
+struct isis_mt_router_info {
+ struct isis_mt_router_info *next;
+
+ bool overload;
+ bool attached;
+ uint16_t mtid;
+};
+
+struct isis_auth {
+ struct isis_auth *next;
+
+ uint8_t type;
+ uint8_t length;
+ uint8_t value[256];
+
+ uint8_t plength;
+ uint8_t passwd[256];
+
+ size_t offset; /* Only valid after packing */
+};
+
+struct isis_item_list {
+ struct isis_item *head;
+ struct isis_item **tail;
+
+ RB_ENTRY(isis_item_list) mt_tree;
+ uint16_t mtid;
+ unsigned int count;
+};
+
+struct isis_purge_originator {
+ bool sender_set;
+
+ uint8_t generator[6];
+ uint8_t sender[6];
+};
+
+enum isis_auth_result {
+ ISIS_AUTH_OK = 0,
+ ISIS_AUTH_TYPE_FAILURE,
+ ISIS_AUTH_FAILURE,
+ ISIS_AUTH_NO_VALIDATOR,
+};
+
+RB_HEAD(isis_mt_item_list, isis_item_list);
+
+struct isis_item_list *isis_get_mt_items(struct isis_mt_item_list *m,
+ uint16_t mtid);
+struct isis_item_list *isis_lookup_mt_items(struct isis_mt_item_list *m,
+ uint16_t mtid);
+
+struct isis_tlvs {
+ struct isis_item_list isis_auth;
+ struct isis_purge_originator *purge_originator;
+ struct isis_item_list area_addresses;
+ struct isis_item_list oldstyle_reach;
+ struct isis_item_list lan_neighbor;
+ struct isis_item_list lsp_entries;
+ struct isis_item_list extended_reach;
+ struct isis_mt_item_list mt_reach;
+ struct isis_item_list oldstyle_ip_reach;
+ struct isis_protocols_supported protocols_supported;
+ struct isis_item_list oldstyle_ip_reach_ext;
+ struct isis_item_list ipv4_address;
+ struct isis_item_list ipv6_address;
+ struct isis_item_list global_ipv6_address;
+ struct isis_item_list mt_router_info;
+ bool mt_router_info_empty;
+ struct in_addr *te_router_id;
+ struct in6_addr *te_router_id_ipv6;
+ struct isis_item_list extended_ip_reach;
+ struct isis_mt_item_list mt_ip_reach;
+ char *hostname;
+ struct isis_item_list ipv6_reach;
+ struct isis_mt_item_list mt_ipv6_reach;
+ struct isis_threeway_adj *threeway_adj;
+ struct isis_router_cap *router_cap;
+ struct isis_spine_leaf *spine_leaf;
+ struct isis_mt_item_list srv6_locator;
+};
+
+enum isis_tlv_context {
+ ISIS_CONTEXT_LSP,
+ ISIS_CONTEXT_SUBTLV_NE_REACH,
+ ISIS_CONTEXT_SUBTLV_IP_REACH,
+ ISIS_CONTEXT_SUBTLV_IPV6_REACH,
+ ISIS_CONTEXT_SUBTLV_SRV6_LOCATOR,
+ ISIS_CONTEXT_SUBSUBTLV_SRV6_END_SID,
+ ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID,
+ ISIS_CONTEXT_SUBSUBTLV_SRV6_LAN_ENDX_SID,
+ ISIS_CONTEXT_MAX,
+};
+
+struct isis_subtlvs {
+ enum isis_tlv_context context;
+
+ /* draft-baker-ipv6-isis-dst-src-routing-06 */
+ struct prefix_ipv6 *source_prefix;
+ /* RFC 8667 section #2.4 */
+ struct isis_item_list prefix_sids;
+
+ /* RFC 9352 section #7.2 */
+ struct isis_item_list srv6_end_sids;
+};
+
+struct isis_subsubtlvs {
+ enum isis_tlv_context context;
+
+ /* RFC 9352 section #9 */
+ struct isis_srv6_sid_structure_subsubtlv *srv6_sid_structure;
+};
+
+enum isis_tlv_type {
+ /* TLVs code point */
+ ISIS_TLV_AREA_ADDRESSES = 1,
+ ISIS_TLV_OLDSTYLE_REACH = 2,
+ ISIS_TLV_LAN_NEIGHBORS = 6,
+ ISIS_TLV_PADDING = 8,
+ ISIS_TLV_LSP_ENTRY = 9,
+ ISIS_TLV_AUTH = 10,
+ ISIS_TLV_PURGE_ORIGINATOR = 13,
+ ISIS_TLV_EXTENDED_REACH = 22,
+
+ ISIS_TLV_SRV6_LOCATOR = 27,
+
+ ISIS_TLV_OLDSTYLE_IP_REACH = 128,
+ ISIS_TLV_PROTOCOLS_SUPPORTED = 129,
+ ISIS_TLV_OLDSTYLE_IP_REACH_EXT = 130,
+ ISIS_TLV_IPV4_ADDRESS = 132,
+ ISIS_TLV_TE_ROUTER_ID = 134,
+ ISIS_TLV_EXTENDED_IP_REACH = 135,
+ ISIS_TLV_DYNAMIC_HOSTNAME = 137,
+ ISIS_TLV_TE_ROUTER_ID_IPV6 = 140,
+ ISIS_TLV_SPINE_LEAF_EXT = 150,
+ ISIS_TLV_MT_REACH = 222,
+ ISIS_TLV_MT_ROUTER_INFO = 229,
+ ISIS_TLV_IPV6_ADDRESS = 232,
+ ISIS_TLV_GLOBAL_IPV6_ADDRESS = 233,
+ ISIS_TLV_MT_IP_REACH = 235,
+ ISIS_TLV_IPV6_REACH = 236,
+ ISIS_TLV_MT_IPV6_REACH = 237,
+ ISIS_TLV_THREE_WAY_ADJ = 240,
+ ISIS_TLV_ROUTER_CAPABILITY = 242,
+ ISIS_TLV_MAX = 256,
+
+ /* subTLVs code point */
+ ISIS_SUBTLV_IPV6_SOURCE_PREFIX = 22,
+
+ /* RFC 5305 & RFC 6119 */
+ ISIS_SUBTLV_ADMIN_GRP = 3,
+ ISIS_SUBTLV_LOCAL_IPADDR = 6,
+ ISIS_SUBTLV_RMT_IPADDR = 8,
+ ISIS_SUBTLV_MAX_BW = 9,
+ ISIS_SUBTLV_MAX_RSV_BW = 10,
+ ISIS_SUBTLV_UNRSV_BW = 11,
+ ISIS_SUBTLV_LOCAL_IPADDR6 = 12,
+ ISIS_SUBTLV_RMT_IPADDR6 = 13,
+ ISIS_SUBTLV_TE_METRIC = 18,
+
+ /* RFC 5307 */
+ ISIS_SUBTLV_LLRI = 4,
+
+ /* RFC 8491 */
+ ISIS_SUBTLV_NODE_MSD = 23,
+
+ /* RFC 5316 */
+ ISIS_SUBTLV_RAS = 24,
+ ISIS_SUBTLV_RIP = 25,
+
+ /* RFC 8667 section #4 IANA allocation */
+ ISIS_SUBTLV_SID_LABEL = 1,
+ ISIS_SUBTLV_SID_LABEL_RANGE = 2,
+ ISIS_SUBTLV_ALGORITHM = 19,
+ ISIS_SUBTLV_SRLB = 22,
+ ISIS_SUBTLV_PREFIX_SID = 3,
+ ISIS_SUBTLV_ADJ_SID = 31,
+ ISIS_SUBTLV_LAN_ADJ_SID = 32,
+
+ /* RFC 7810 */
+ ISIS_SUBTLV_AV_DELAY = 33,
+ ISIS_SUBTLV_MM_DELAY = 34,
+ ISIS_SUBTLV_DELAY_VAR = 35,
+ ISIS_SUBTLV_PKT_LOSS = 36,
+ ISIS_SUBTLV_RES_BW = 37,
+ ISIS_SUBTLV_AVA_BW = 38,
+ ISIS_SUBTLV_USE_BW = 39,
+
+ /* RFC 7308 */
+ ISIS_SUBTLV_EXT_ADMIN_GRP = 14,
+
+ /* RFC 8919 */
+ ISIS_SUBTLV_ASLA = 16,
+
+ /* draft-ietf-lsr-isis-srv6-extensions */
+ ISIS_SUBTLV_SID_END = 5,
+ ISIS_SUBTLV_SID_END_X = 43,
+
+ ISIS_SUBTLV_MAX = 40,
+
+ /* RFC 9352 section #2 */
+ ISIS_SUBTLV_SRV6_CAPABILITIES = 25,
+ /* RFC 9352 section #4.1 */
+ ISIS_SUBTLV_SRV6_MAX_SL_MSD = 41,
+ /* RFC 9352 section #4.2 */
+ ISIS_SUBTLV_SRV6_MAX_END_POP_MSD = 42,
+ /* RFC 9352 section #4.3 */
+ ISIS_SUBTLV_SRV6_MAX_H_ENCAPS_MSD = 44,
+ /* RFC 9352 section #4.4 */
+ ISIS_SUBTLV_SRV6_MAX_END_D_MSD = 45,
+
+ ISIS_SUBTLV_SRV6_END_SID = 5,
+ ISIS_SUBTLV_SRV6_ENDX_SID = 43,
+ ISIS_SUBTLV_SRV6_LAN_ENDX_SID = 44,
+
+ ISIS_SUBSUBTLV_SRV6_SID_STRUCTURE = 1,
+
+ /* draft-ietf-lsr-isis-srv6-extensions */
+ ISIS_SUBSUBTLV_SID_STRUCTURE = 1,
+
+ ISIS_SUBSUBTLV_MAX = 256,
+};
+
+/* subTLVs size for TE and SR */
+enum ext_subtlv_size {
+ /* Sub-TLV Type and Length fields */
+ ISIS_SUBTLV_TYPE_FIELD_SIZE = 1,
+ ISIS_SUBTLV_LENGTH_FIELD_SIZE = 1,
+
+ /* RFC 5307 */
+ ISIS_SUBTLV_LLRI_SIZE = 8,
+
+ /* RFC 5305 & RFC 6119 */
+ ISIS_SUBTLV_UNRSV_BW_SIZE = 32,
+ ISIS_SUBTLV_TE_METRIC_SIZE = 3,
+ ISIS_SUBTLV_IPV6_ADDR_SIZE = 16,
+
+ /* RFC 8491 */
+ ISIS_SUBTLV_NODE_MSD_SIZE = 2,
+ ISIS_SUBTLV_NODE_MSD_TYPE_SIZE = 1,
+ ISIS_SUBTLV_NODE_MSD_VALUE_SIZE = 1,
+
+ /* RFC 8667 sections #2 & #3 */
+ ISIS_SUBTLV_SID_LABEL_SIZE = 3,
+ ISIS_SUBTLV_SID_INDEX_SIZE = 4,
+ ISIS_SUBTLV_SID_LABEL_RANGE_SIZE = 9,
+ ISIS_SUBTLV_ALGORITHM_SIZE = 4,
+ ISIS_SUBTLV_ADJ_SID_SIZE = 5,
+ ISIS_SUBTLV_LAN_ADJ_SID_SIZE = 11,
+ ISIS_SUBTLV_PREFIX_SID_SIZE = 5,
+
+ /* RFC 7810 */
+ ISIS_SUBTLV_MM_DELAY_SIZE = 8,
+
+ /* RFC9350 - Flex-Algorithm */
+ ISIS_SUBTLV_FAD = 26,
+ ISIS_SUBTLV_FAD_MIN_SIZE = 4,
+
+ ISIS_SUBTLV_HDR_SIZE = 2,
+ ISIS_SUBTLV_DEF_SIZE = 4,
+
+ ISIS_SUBTLV_MAX_SIZE = 180,
+
+ /* RFC 9352 sections #8.1 & #8.2 */
+ ISIS_SUBTLV_SRV6_ENDX_SID_SIZE = 21,
+ ISIS_SUBTLV_SRV6_LAN_ENDX_SID_SIZE = 27,
+
+ /* draft-ietf-lsr-isis-srv6-extensions */
+ ISIS_SUBSUBTLV_SID_STRUCTURE_SIZE = 4,
+
+ ISIS_SUBSUBTLV_HDR_SIZE = 2,
+ ISIS_SUBSUBTLV_MAX_SIZE = 180,
+
+ /* RFC9350 - Flex-Algorithm */
+ ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS_SIZE = 1,
+
+ /* RFC 9352 section #2 */
+ ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE = 2,
+};
+
+enum ext_subsubtlv_types {
+ ISIS_SUBTLV_FAD_SUBSUBTLV_EXCAG = 1,
+ ISIS_SUBTLV_FAD_SUBSUBTLV_INCANYAG = 2,
+ ISIS_SUBTLV_FAD_SUBSUBTLV_INCALLAG = 3,
+ ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS = 4,
+ ISIS_SUBTLV_FAD_SUBSUBTLV_ESRLG = 5,
+};
+
+/* Macros to manage the optional presence of EXT subTLVs */
+#define SET_SUBTLV(s, t) ((s->status) |= (t))
+#define UNSET_SUBTLV(s, t) ((s->status) &= ~(t))
+#define IS_SUBTLV(s, t) (s->status & t)
+#define RESET_SUBTLV(s) (s->status = 0)
+#define NO_SUBTLV(s) (s->status == 0)
+
+#define EXT_DISABLE 0x000000
+#define EXT_ADM_GRP 0x000001
+#define EXT_LLRI 0x000002
+#define EXT_LOCAL_ADDR 0x000004
+#define EXT_NEIGH_ADDR 0x000008
+#define EXT_LOCAL_ADDR6 0x000010
+#define EXT_NEIGH_ADDR6 0x000020
+#define EXT_MAX_BW 0x000040
+#define EXT_MAX_RSV_BW 0x000080
+#define EXT_UNRSV_BW 0x000100
+#define EXT_TE_METRIC 0x000200
+#define EXT_RMT_AS 0x000400
+#define EXT_RMT_IP 0x000800
+#define EXT_ADJ_SID 0x001000
+#define EXT_LAN_ADJ_SID 0x002000
+#define EXT_DELAY 0x004000
+#define EXT_MM_DELAY 0x008000
+#define EXT_DELAY_VAR 0x010000
+#define EXT_PKT_LOSS 0x020000
+#define EXT_RES_BW 0x040000
+#define EXT_AVA_BW 0x080000
+#define EXT_USE_BW 0x100000
+#define EXT_EXTEND_ADM_GRP 0x200000
+#define EXT_SRV6_ENDX_SID 0x400000
+#define EXT_SRV6_LAN_ENDX_SID 0x800000
+
+/*
+ * This structure groups all Extended IS Reachability subTLVs.
+ *
+ * Each bit of the status field indicates if a subTLVs is valid or not.
+ * SubTLVs values use following units:
+ * - Bandwidth in bytes/sec following IEEE format,
+ * - Delay in micro-seconds with only 24 bits significant
+ * - Packet Loss in percentage of total traffic with only 24 bits (2^24 - 2)
+ *
+ * For Delay and packet Loss, upper bit (A) indicates if the value is
+ * normal (0) or anomalous (1).
+ */
+#define IS_ANORMAL(v) (v & TE_EXT_ANORMAL)
+
+struct isis_ext_subtlvs {
+
+ uint32_t status;
+
+ uint32_t adm_group; /* Resource Class/Color - RFC 5305 */
+ struct admin_group ext_admin_group; /* Res. Class/Color - RFC 7308 */
+ /* Link Local/Remote Identifiers - RFC 5307 */
+ uint32_t local_llri;
+ uint32_t remote_llri;
+ struct in_addr local_addr; /* Local IP Address - RFC 5305 */
+ struct in_addr neigh_addr; /* Neighbor IP Address - RFC 5305 */
+ struct in6_addr local_addr6; /* Local IPv6 Address - RFC 6119 */
+ struct in6_addr neigh_addr6; /* Neighbor IPv6 Address - RFC 6119 */
+ float max_bw; /* Maximum Bandwidth - RFC 5305 */
+ float max_rsv_bw; /* Maximum Reservable Bandwidth - RFC 5305 */
+ float unrsv_bw[8]; /* Unreserved Bandwidth - RFC 5305 */
+ uint32_t te_metric; /* Traffic Engineering Metric - RFC 5305 */
+ uint32_t remote_as; /* Remote AS Number sub-TLV - RFC5316 */
+ struct in_addr remote_ip; /* IPv4 Remote ASBR ID Sub-TLV - RFC5316 */
+
+ uint32_t delay; /* Average Link Delay - RFC 8570 */
+ uint32_t min_delay; /* Low Link Delay - RFC 8570 */
+ uint32_t max_delay; /* High Link Delay - RFC 8570 */
+ uint32_t delay_var; /* Link Delay Variation i.e. Jitter - RFC 8570 */
+ uint32_t pkt_loss; /* Unidirectional Link Packet Loss - RFC 8570 */
+ float res_bw; /* Unidirectional Residual Bandwidth - RFC 8570 */
+ float ava_bw; /* Unidirectional Available Bandwidth - RFC 8570 */
+ float use_bw; /* Unidirectional Utilized Bandwidth - RFC 8570 */
+
+ /* Segment Routing Adjacency & LAN Adjacency Segment ID */
+ struct isis_item_list adj_sid;
+ struct isis_item_list lan_sid;
+
+ struct list *aslas;
+
+ /* SRv6 End.X & LAN End.X SID */
+ struct isis_item_list srv6_endx_sid;
+ struct isis_item_list srv6_lan_endx_sid;
+};
+
+/* RFC 8919 */
+#define ISIS_SABM_FLAG_R 0x80 /* RSVP-TE */
+#define ISIS_SABM_FLAG_S 0x40 /* Segment Routing Policy */
+#define ISIS_SABM_FLAG_L 0x20 /* Loop-Free Alternate */
+#define ISIS_SABM_FLAG_X 0x10 /* Flex-Algorithm - RFC9350 */
+
+#define ASLA_APP_IDENTIFIER_BIT_LENGTH 1
+#define ASLA_LEGACY_FLAG 0x80
+#define ASLA_APPS_LENGTH_MASK 0x7f
+
+struct isis_asla_subtlvs {
+ uint32_t status;
+
+ /* Application Specific Link Attribute - RFC 8919 */
+ bool legacy; /* L-Flag */
+ uint8_t standard_apps_length;
+ uint8_t user_def_apps_length;
+ uint8_t standard_apps;
+ uint8_t user_def_apps;
+
+ /* Sub-TLV list - rfc8919 section-3.1 */
+ uint32_t admin_group;
+ struct admin_group ext_admin_group; /* Res. Class/Color - RFC 7308 */
+ float max_bw; /* Maximum Bandwidth - RFC 5305 */
+ float max_rsv_bw; /* Maximum Reservable Bandwidth - RFC 5305 */
+ float unrsv_bw[8]; /* Unreserved Bandwidth - RFC 5305 */
+ uint32_t te_metric; /* Traffic Engineering Metric - RFC 5305 */
+ uint32_t delay; /* Average Link Delay - RFC 8570 */
+ uint32_t min_delay; /* Low Link Delay - RFC 8570 */
+ uint32_t max_delay; /* High Link Delay - RFC 8570 */
+ uint32_t delay_var; /* Link Delay Variation i.e. Jitter - RFC 8570 */
+ uint32_t pkt_loss; /* Unidirectional Link Packet Loss - RFC 8570 */
+ float res_bw; /* Unidirectional Residual Bandwidth - RFC 8570 */
+ float ava_bw; /* Unidirectional Available Bandwidth - RFC 8570 */
+ float use_bw; /* Unidirectional Utilized Bandwidth - RFC 8570 */
+};
+
+#define IS_COMPAT_MT_TLV(tlv_type) \
+ ((tlv_type == ISIS_TLV_MT_REACH) || (tlv_type == ISIS_TLV_MT_IP_REACH) \
+ || (tlv_type == ISIS_TLV_MT_IPV6_REACH))
+
+struct stream;
+int isis_pack_tlvs(struct isis_tlvs *tlvs, struct stream *stream,
+ size_t len_pointer, bool pad, bool is_lsp);
+void isis_free_tlvs(struct isis_tlvs *tlvs);
+struct isis_tlvs *isis_alloc_tlvs(void);
+struct isis_subsubtlvs *isis_alloc_subsubtlvs(enum isis_tlv_context context);
+int isis_unpack_tlvs(size_t avail_len, struct stream *stream,
+ struct isis_tlvs **dest, const char **error_log);
+const char *isis_format_tlvs(struct isis_tlvs *tlvs, struct json_object *json);
+struct isis_tlvs *isis_copy_tlvs(struct isis_tlvs *tlvs);
+struct list *isis_fragment_tlvs(struct isis_tlvs *tlvs, size_t size);
+
+#define ISIS_EXTENDED_IP_REACH_DOWN 0x80
+#define ISIS_EXTENDED_IP_REACH_SUBTLV 0x40
+
+#define ISIS_IPV6_REACH_DOWN 0x80
+#define ISIS_IPV6_REACH_EXTERNAL 0x40
+#define ISIS_IPV6_REACH_SUBTLV 0x20
+
+#ifndef ISIS_MT_MASK
+#define ISIS_MT_MASK 0x0fff
+#define ISIS_MT_OL_MASK 0x8000
+#define ISIS_MT_AT_MASK 0x4000
+#endif
+
+/* RFC 8919 */
+#define ISIS_SABM_FLAG_R 0x80 /* RSVP-TE */
+#define ISIS_SABM_FLAG_S 0x40 /* Segment Routing Policy */
+#define ISIS_SABM_FLAG_L 0x20 /* Loop-Free Alternate */
+#define ISIS_SABM_FLAG_X 0x10 /* Flex-Algorithm - RFC9350 */
+
+void isis_tlvs_add_auth(struct isis_tlvs *tlvs, struct isis_passwd *passwd);
+void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs,
+ struct list *addresses);
+void isis_tlvs_add_lan_neighbors(struct isis_tlvs *tlvs,
+ struct list *neighbors);
+void isis_tlvs_set_protocols_supported(struct isis_tlvs *tlvs,
+ struct nlpids *nlpids);
+void isis_tlvs_add_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid,
+ bool overload, bool attached);
+void isis_tlvs_add_ipv4_address(struct isis_tlvs *tlvs, struct in_addr *addr);
+void isis_tlvs_add_ipv4_addresses(struct isis_tlvs *tlvs,
+ struct list *addresses);
+void isis_tlvs_add_ipv6_addresses(struct isis_tlvs *tlvs,
+ struct list *addresses);
+void isis_tlvs_add_global_ipv6_addresses(struct isis_tlvs *tlvs,
+ struct list *addresses);
+int isis_tlvs_auth_is_valid(struct isis_tlvs *tlvs, struct isis_passwd *passwd,
+ struct stream *stream, bool is_lsp);
+bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs,
+ struct list *addresses);
+struct isis_adjacency;
+void isis_tlvs_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj,
+ bool *changed);
+bool isis_tlvs_own_snpa_found(struct isis_tlvs *tlvs, uint8_t *snpa);
+void isis_tlvs_add_lsp_entry(struct isis_tlvs *tlvs, struct isis_lsp *lsp);
+void isis_tlvs_add_csnp_entries(struct isis_tlvs *tlvs, uint8_t *start_id,
+ uint8_t *stop_id, uint16_t num_lsps,
+ struct lspdb_head *lspdb,
+ struct isis_lsp **last_lsp);
+void isis_tlvs_set_dynamic_hostname(struct isis_tlvs *tlvs,
+ const char *hostname);
+struct isis_router_cap *
+isis_tlvs_init_router_capability(struct isis_tlvs *tlvs);
+
+struct isis_area;
+struct isis_flex_algo;
+void isis_tlvs_set_router_capability_fad(struct isis_tlvs *tlvs,
+ struct flex_algo *fa, int algorithm,
+ uint8_t *sysid);
+
+struct isis_area;
+
+int isis_tlvs_sr_algo_count(const struct isis_router_cap *cap);
+
+void isis_tlvs_set_te_router_id(struct isis_tlvs *tlvs,
+ const struct in_addr *id);
+void isis_tlvs_set_te_router_id_ipv6(struct isis_tlvs *tlvs,
+ const struct in6_addr *id);
+void isis_tlvs_add_oldstyle_ip_reach(struct isis_tlvs *tlvs,
+ struct prefix_ipv4 *dest, uint8_t metric);
+void isis_tlvs_add_extended_ip_reach(struct isis_tlvs *tlvs,
+ struct prefix_ipv4 *dest, uint32_t metric,
+ bool external,
+ struct sr_prefix_cfg **pcfgs);
+void isis_tlvs_add_ipv6_reach(struct isis_tlvs *tlvs, uint16_t mtid,
+ struct prefix_ipv6 *dest, uint32_t metric,
+ bool external, struct sr_prefix_cfg **pcfgs);
+void isis_tlvs_add_ipv6_dstsrc_reach(struct isis_tlvs *tlvs, uint16_t mtid,
+ struct prefix_ipv6 *dest,
+ struct prefix_ipv6 *src,
+ uint32_t metric);
+struct isis_ext_subtlvs *isis_alloc_ext_subtlvs(void);
+void isis_del_ext_subtlvs(struct isis_ext_subtlvs *ext);
+void isis_tlvs_add_adj_sid(struct isis_ext_subtlvs *exts,
+ struct isis_adj_sid *adj);
+void isis_tlvs_del_adj_sid(struct isis_ext_subtlvs *exts,
+ struct isis_adj_sid *adj);
+void isis_tlvs_add_lan_adj_sid(struct isis_ext_subtlvs *exts,
+ struct isis_lan_adj_sid *lan);
+void isis_tlvs_del_lan_adj_sid(struct isis_ext_subtlvs *exts,
+ struct isis_lan_adj_sid *lan);
+
+void isis_tlvs_del_asla_flex_algo(struct isis_ext_subtlvs *ext,
+ struct isis_asla_subtlvs *asla);
+struct isis_asla_subtlvs *
+isis_tlvs_find_alloc_asla(struct isis_ext_subtlvs *ext, uint8_t standard_apps);
+void isis_tlvs_free_asla(struct isis_ext_subtlvs *ext, uint8_t standard_apps);
+
+void isis_tlvs_add_oldstyle_reach(struct isis_tlvs *tlvs, uint8_t *id,
+ uint8_t metric);
+void isis_tlvs_add_extended_reach(struct isis_tlvs *tlvs, uint16_t mtid,
+ uint8_t *id, uint32_t metric,
+ struct isis_ext_subtlvs *subtlvs);
+
+const char *isis_threeway_state_name(enum isis_threeway_state state);
+
+void isis_tlvs_add_threeway_adj(struct isis_tlvs *tlvs,
+ enum isis_threeway_state state,
+ uint32_t local_circuit_id,
+ const uint8_t *neighbor_id,
+ uint32_t neighbor_circuit_id);
+
+void isis_tlvs_add_spine_leaf(struct isis_tlvs *tlvs, uint8_t tier,
+ bool has_tier, bool is_leaf, bool is_spine,
+ bool is_backup);
+
+struct isis_mt_router_info *
+isis_tlvs_lookup_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid);
+
+void isis_tlvs_set_purge_originator(struct isis_tlvs *tlvs,
+ const uint8_t *generator,
+ const uint8_t *sender);
+
+void isis_subsubtlvs_set_srv6_sid_structure(struct isis_subsubtlvs *subsubtlvs,
+ struct isis_srv6_sid *sid);
+void isis_subtlvs_add_srv6_end_sid(struct isis_subtlvs *subtlvs,
+ struct isis_srv6_sid *sid);
+void isis_tlvs_add_srv6_locator(struct isis_tlvs *tlvs, uint16_t mtid,
+ struct isis_srv6_locator *loc);
+
+void isis_tlvs_add_srv6_endx_sid(struct isis_ext_subtlvs *exts,
+ struct isis_srv6_endx_sid_subtlv *adj);
+void isis_tlvs_del_srv6_endx_sid(struct isis_ext_subtlvs *exts,
+ struct isis_srv6_endx_sid_subtlv *adj);
+void isis_tlvs_add_srv6_lan_endx_sid(struct isis_ext_subtlvs *exts,
+ struct isis_srv6_lan_endx_sid_subtlv *lan);
+void isis_tlvs_del_srv6_lan_endx_sid(struct isis_ext_subtlvs *exts,
+ struct isis_srv6_lan_endx_sid_subtlv *lan);
+#endif
diff --git a/isisd/isis_tx_queue.c b/isisd/isis_tx_queue.c
new file mode 100644
index 0000000..caf97f1
--- /dev/null
+++ b/isisd/isis_tx_queue.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - LSP TX Queuing logic
+ *
+ * Copyright (C) 2018 Christian Franke
+ *
+ * This file is part of FRRouting (FRR)
+ */
+#include <zebra.h>
+
+#include "hash.h"
+#include "jhash.h"
+
+#include "isisd/isisd.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_tx_queue.h"
+
+DEFINE_MTYPE_STATIC(ISISD, TX_QUEUE, "ISIS TX Queue");
+DEFINE_MTYPE_STATIC(ISISD, TX_QUEUE_ENTRY, "ISIS TX Queue Entry");
+
+struct isis_tx_queue {
+ struct isis_circuit *circuit;
+ void (*send_event)(struct isis_circuit *circuit,
+ struct isis_lsp *, enum isis_tx_type);
+ struct hash *hash;
+};
+
+struct isis_tx_queue_entry {
+ struct isis_lsp *lsp;
+ enum isis_tx_type type;
+ bool is_retry;
+ struct event *retry;
+ struct isis_tx_queue *queue;
+};
+
+static unsigned tx_queue_hash_key(const void *p)
+{
+ const struct isis_tx_queue_entry *e = p;
+
+ uint32_t id_key = jhash(e->lsp->hdr.lsp_id,
+ ISIS_SYS_ID_LEN + 2, 0x55aa5a5a);
+
+ return jhash_1word(e->lsp->level, id_key);
+}
+
+static bool tx_queue_hash_cmp(const void *a, const void *b)
+{
+ const struct isis_tx_queue_entry *ea = a, *eb = b;
+
+ if (ea->lsp->level != eb->lsp->level)
+ return false;
+
+ if (memcmp(ea->lsp->hdr.lsp_id, eb->lsp->hdr.lsp_id,
+ ISIS_SYS_ID_LEN + 2))
+ return false;
+
+ return true;
+}
+
+struct isis_tx_queue *isis_tx_queue_new(
+ struct isis_circuit *circuit,
+ void(*send_event)(struct isis_circuit *circuit,
+ struct isis_lsp *,
+ enum isis_tx_type))
+{
+ struct isis_tx_queue *rv = XCALLOC(MTYPE_TX_QUEUE, sizeof(*rv));
+
+ rv->circuit = circuit;
+ rv->send_event = send_event;
+
+ rv->hash = hash_create(tx_queue_hash_key, tx_queue_hash_cmp, NULL);
+ return rv;
+}
+
+static void tx_queue_element_free(void *element)
+{
+ struct isis_tx_queue_entry *e = element;
+
+ EVENT_OFF(e->retry);
+
+ XFREE(MTYPE_TX_QUEUE_ENTRY, e);
+}
+
+void isis_tx_queue_free(struct isis_tx_queue *queue)
+{
+ hash_clean_and_free(&queue->hash, tx_queue_element_free);
+ XFREE(MTYPE_TX_QUEUE, queue);
+}
+
+static struct isis_tx_queue_entry *tx_queue_find(struct isis_tx_queue *queue,
+ struct isis_lsp *lsp)
+{
+ struct isis_tx_queue_entry e = {
+ .lsp = lsp
+ };
+
+ return hash_lookup(queue->hash, &e);
+}
+
+static void tx_queue_send_event(struct event *thread)
+{
+ struct isis_tx_queue_entry *e = EVENT_ARG(thread);
+ struct isis_tx_queue *queue = e->queue;
+
+ event_add_timer(master, tx_queue_send_event, e, 5, &e->retry);
+
+ if (e->is_retry)
+ queue->circuit->area->lsp_rxmt_count++;
+ else
+ e->is_retry = true;
+
+ queue->send_event(queue->circuit, e->lsp, e->type);
+ /* Don't access e here anymore, send_event might have destroyed it */
+}
+
+void _isis_tx_queue_add(struct isis_tx_queue *queue,
+ struct isis_lsp *lsp,
+ enum isis_tx_type type,
+ const char *func, const char *file,
+ int line)
+{
+ if (!queue)
+ return;
+
+ if (IS_DEBUG_TX_QUEUE) {
+ zlog_debug(
+ "Add LSP %pLS to %s queue as %s LSP. (From %s %s:%d)",
+ lsp->hdr.lsp_id, queue->circuit->interface->name,
+ (type == TX_LSP_CIRCUIT_SCOPED) ? "circuit scoped"
+ : "regular",
+ func, file, line);
+ }
+
+ struct isis_tx_queue_entry *e = tx_queue_find(queue, lsp);
+ if (!e) {
+ e = XCALLOC(MTYPE_TX_QUEUE_ENTRY, sizeof(*e));
+ e->lsp = lsp;
+ e->queue = queue;
+
+ struct isis_tx_queue_entry *inserted;
+ inserted = hash_get(queue->hash, e, hash_alloc_intern);
+ assert(inserted == e);
+ }
+
+ e->type = type;
+
+ EVENT_OFF(e->retry);
+ event_add_event(master, tx_queue_send_event, e, 0, &e->retry);
+
+ e->is_retry = false;
+}
+
+void _isis_tx_queue_del(struct isis_tx_queue *queue, struct isis_lsp *lsp,
+ const char *func, const char *file, int line)
+{
+ if (!queue)
+ return;
+
+ struct isis_tx_queue_entry *e = tx_queue_find(queue, lsp);
+ if (!e)
+ return;
+
+ if (IS_DEBUG_TX_QUEUE) {
+ zlog_debug("Remove LSP %pLS from %s queue. (From %s %s:%d)",
+ lsp->hdr.lsp_id, queue->circuit->interface->name,
+ func, file, line);
+ }
+
+ EVENT_OFF(e->retry);
+
+ hash_release(queue->hash, e);
+ XFREE(MTYPE_TX_QUEUE_ENTRY, e);
+}
+
+unsigned long isis_tx_queue_len(struct isis_tx_queue *queue)
+{
+ if (!queue)
+ return 0;
+
+ return hashcount(queue->hash);
+}
+
+void isis_tx_queue_clean(struct isis_tx_queue *queue)
+{
+ hash_clean(queue->hash, tx_queue_element_free);
+}
diff --git a/isisd/isis_tx_queue.h b/isisd/isis_tx_queue.h
new file mode 100644
index 0000000..c3e36d0
--- /dev/null
+++ b/isisd/isis_tx_queue.h
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - LSP TX Queuing logic
+ *
+ * Copyright (C) 2018 Christian Franke
+ *
+ * This file is part of FRRouting (FRR)
+ */
+#ifndef ISIS_TX_QUEUE_H
+#define ISIS_TX_QUEUE_H
+
+enum isis_tx_type {
+ TX_LSP_NORMAL = 0,
+ TX_LSP_CIRCUIT_SCOPED
+};
+
+struct isis_tx_queue;
+
+struct isis_tx_queue *isis_tx_queue_new(
+ struct isis_circuit *circuit,
+ void(*send_event)(struct isis_circuit *circuit,
+ struct isis_lsp *,
+ enum isis_tx_type)
+);
+
+void isis_tx_queue_free(struct isis_tx_queue *queue);
+
+#define isis_tx_queue_add(queue, lsp, type) \
+ _isis_tx_queue_add((queue), (lsp), (type), \
+ __func__, __FILE__, __LINE__)
+void _isis_tx_queue_add(struct isis_tx_queue *queue, struct isis_lsp *lsp,
+ enum isis_tx_type type, const char *func,
+ const char *file, int line);
+
+#define isis_tx_queue_del(queue, lsp) \
+ _isis_tx_queue_del((queue), (lsp), __func__, __FILE__, __LINE__)
+void _isis_tx_queue_del(struct isis_tx_queue *queue, struct isis_lsp *lsp,
+ const char *func, const char *file, int line);
+
+unsigned long isis_tx_queue_len(struct isis_tx_queue *queue);
+
+void isis_tx_queue_clean(struct isis_tx_queue *queue);
+
+#endif
diff --git a/isisd/isis_vty_fabricd.c b/isisd/isis_vty_fabricd.c
new file mode 100644
index 0000000..0d25f66
--- /dev/null
+++ b/isisd/isis_vty_fabricd.c
@@ -0,0 +1,1141 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_vty_fabricd.c
+ *
+ * This file contains the CLI that is specific to OpenFabric
+ *
+ * Copyright (C) 2018 Christian Franke, for NetDEF, Inc.
+ */
+#include <zebra.h>
+
+#include "command.h"
+
+#include "lib/bfd.h"
+#include "isisd/isis_bfd.h"
+#include "isisd/isisd.h"
+#include "isisd/fabricd.h"
+#include "isisd/isis_tlvs.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isis_circuit.h"
+#include "lib/spf_backoff.h"
+#include "isisd/isis_mt.h"
+
+static struct isis_circuit *isis_circuit_lookup(struct vty *vty)
+{
+ struct interface *ifp = VTY_GET_CONTEXT(interface);
+ struct isis_circuit *circuit;
+
+ if (!ifp) {
+ vty_out(vty, "Invalid interface \n");
+ return NULL;
+ }
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit) {
+ vty_out(vty, "ISIS is not enabled on circuit %s\n", ifp->name);
+ return NULL;
+ }
+
+ return circuit;
+}
+
+DEFUN (fabric_tier,
+ fabric_tier_cmd,
+ "fabric-tier (0-14)",
+ "Statically configure the tier to advertise\n"
+ "Tier to advertise\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ uint8_t tier = atoi(argv[1]->arg);
+
+ fabricd_configure_tier(area, tier);
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_fabric_tier,
+ no_fabric_tier_cmd,
+ "no fabric-tier [(0-14)]",
+ NO_STR
+ "Statically configure the tier to advertise\n"
+ "Tier to advertise\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ fabricd_configure_tier(area, ISIS_TIER_UNDEFINED);
+ return CMD_SUCCESS;
+}
+
+DEFUN (triggered_csnp,
+ triggered_csnp_cmd,
+ "triggered-csnp-delay (100-10000) [always]",
+ "Configure the delay for triggered CSNPs\n"
+ "Delay in milliseconds\n"
+ "Trigger CSNP for all LSPs, not only circuit-scoped\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ int csnp_delay = atoi(argv[1]->arg);
+ bool always_send_csnp = (argc == 3);
+
+ fabricd_configure_triggered_csnp(area, csnp_delay, always_send_csnp);
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_triggered_csnp,
+ no_triggered_csnp_cmd,
+ "no triggered-csnp-delay [(100-10000) [always]]",
+ NO_STR
+ "Configure the delay for triggered CSNPs\n"
+ "Delay in milliseconds\n"
+ "Trigger CSNP for all LSPs, not only circuit-scoped\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ fabricd_configure_triggered_csnp(area, FABRICD_DEFAULT_CSNP_DELAY,
+ false);
+ return CMD_SUCCESS;
+}
+
+static void lsp_print_flooding(struct vty *vty, struct isis_lsp *lsp,
+ struct isis *isis)
+{
+ char lspid[255];
+ char buf[MONOTIME_STRLEN];
+
+ lspid_print(lsp->hdr.lsp_id, lspid, sizeof(lspid), true, true, isis);
+ vty_out(vty, "Flooding information for %s\n", lspid);
+
+ if (!lsp->flooding_neighbors[TX_LSP_NORMAL]) {
+ vty_out(vty, " Never received.\n");
+ return;
+ }
+
+ vty_out(vty, " Last received on: %s (",
+ lsp->flooding_interface ?
+ lsp->flooding_interface : "(null)");
+
+ time_t uptime = time(NULL) - lsp->flooding_time;
+
+ frrtime_to_interval(uptime, buf, sizeof(buf));
+
+ vty_out(vty, "%s ago)\n", buf);
+
+ if (lsp->flooding_circuit_scoped) {
+ vty_out(vty, " Received as circuit-scoped LSP, so not flooded.\n");
+ return;
+ }
+
+ for (enum isis_tx_type type = TX_LSP_NORMAL;
+ type <= TX_LSP_CIRCUIT_SCOPED; type++) {
+ struct listnode *node;
+ uint8_t *neighbor_id;
+
+ vty_out(vty, " %s:\n",
+ (type == TX_LSP_NORMAL) ? "RF" : "DNR");
+ for (ALL_LIST_ELEMENTS_RO(lsp->flooding_neighbors[type],
+ node, neighbor_id)) {
+ vty_out(vty, " %s\n",
+ print_sys_hostname(neighbor_id));
+ }
+ }
+}
+
+DEFUN (show_lsp_flooding,
+ show_lsp_flooding_cmd,
+ "show openfabric flooding [WORD]",
+ SHOW_STR
+ PROTO_HELP
+ "Flooding information\n"
+ "LSP ID\n")
+{
+ const char *lspid = NULL;
+
+ if (argc == 4)
+ lspid = argv[3]->arg;
+
+ struct listnode *node;
+ struct isis_area *area;
+ struct isis *isis = NULL;
+
+ isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+
+ if (isis == NULL) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ struct lspdb_head *head = &area->lspdb[ISIS_LEVEL2 - 1];
+ struct isis_lsp *lsp;
+
+ vty_out(vty, "Area %s:\n",
+ area->area_tag ? area->area_tag : "null");
+ if (lspid) {
+ lsp = lsp_for_sysid(head, lspid, isis);
+ if (lsp)
+ lsp_print_flooding(vty, lsp, isis);
+ continue;
+ }
+ frr_each (lspdb, head, lsp) {
+ lsp_print_flooding(vty, lsp, isis);
+ vty_out(vty, "\n");
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (ip_router_isis,
+ ip_router_isis_cmd,
+ "ip router " PROTO_NAME " WORD",
+ "Interface Internet Protocol config commands\n"
+ "IP router interface commands\n"
+ PROTO_HELP
+ "Routing process tag\n")
+{
+ int idx_afi = 0;
+ int idx_word = 3;
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct isis_circuit *circuit;
+ struct isis_area *area;
+ const char *af = argv[idx_afi]->arg;
+ const char *area_tag = argv[idx_word]->arg;
+
+ /* Prevent more than one area per circuit */
+ circuit = circuit_scan_by_ifp(ifp);
+ if (circuit && circuit->area) {
+ if (strcmp(circuit->area->area_tag, area_tag)) {
+ vty_out(vty, "ISIS circuit is already defined on %s\n",
+ circuit->area->area_tag);
+ return CMD_ERR_NOTHING_TODO;
+ }
+ }
+
+ area = isis_area_lookup(area_tag, VRF_DEFAULT);
+ if (!area)
+ isis_area_create(area_tag, VRF_DEFAULT_NAME);
+
+ if (!circuit) {
+ circuit = isis_circuit_new(ifp, area_tag);
+
+ if (circuit->state != C_STATE_CONF
+ && circuit->state != C_STATE_UP) {
+ vty_out(vty,
+ "Couldn't bring up interface, please check log.\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ bool ip = circuit->ip_router, ipv6 = circuit->ipv6_router;
+ if (af[2] != '\0')
+ ipv6 = true;
+ else
+ ip = true;
+
+ isis_circuit_af_set(circuit, ip, ipv6);
+ return CMD_SUCCESS;
+}
+
+DEFUN (ip6_router_isis,
+ ip6_router_isis_cmd,
+ "ipv6 router " PROTO_NAME " WORD",
+ "Interface Internet Protocol config commands\n"
+ "IP router interface commands\n"
+ PROTO_HELP
+ "Routing process tag\n")
+{
+ return ip_router_isis(self, vty, argc, argv);
+}
+
+DEFUN (no_ip_router_isis,
+ no_ip_router_isis_cmd,
+ "no <ip|ipv6> router " PROTO_NAME " WORD",
+ NO_STR
+ "Interface Internet Protocol config commands\n"
+ "IP router interface commands\n"
+ "IP router interface commands\n"
+ PROTO_HELP
+ "Routing process tag\n")
+{
+ int idx_afi = 1;
+ int idx_word = 4;
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ const char *af = argv[idx_afi]->arg;
+ const char *area_tag = argv[idx_word]->arg;
+
+ area = isis_area_lookup(area_tag, VRF_DEFAULT);
+ if (!area) {
+ vty_out(vty, "Can't find ISIS instance %s\n",
+ area_tag);
+ return CMD_ERR_NO_MATCH;
+ }
+
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit) {
+ vty_out(vty, "ISIS is not enabled on circuit %s\n", ifp->name);
+ return CMD_ERR_NO_MATCH;
+ }
+
+ bool ip = circuit->ip_router, ipv6 = circuit->ipv6_router;
+ if (af[2] != '\0')
+ ipv6 = false;
+ else
+ ip = false;
+
+ isis_circuit_af_set(circuit, ip, ipv6);
+
+ if (!ip && !ipv6)
+ isis_circuit_del(circuit);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (isis_bfd,
+ isis_bfd_cmd,
+ PROTO_NAME " bfd",
+ PROTO_HELP
+ "Enable BFD support\n")
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ if (circuit->bfd_config.enabled)
+ return CMD_SUCCESS;
+
+ circuit->bfd_config.enabled = true;
+ isis_bfd_circuit_cmd(circuit);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_isis_bfd,
+ no_isis_bfd_cmd,
+ "no " PROTO_NAME " bfd",
+ NO_STR
+ PROTO_HELP
+ "Disables BFD support\n"
+)
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ if (!circuit->bfd_config.enabled)
+ return CMD_SUCCESS;
+
+ circuit->bfd_config.enabled = false;
+ isis_bfd_circuit_cmd(circuit);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (set_overload_bit,
+ set_overload_bit_cmd,
+ "set-overload-bit",
+ "Set overload bit to avoid any transit traffic\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ isis_area_overload_bit_set(area, true);
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_set_overload_bit,
+ no_set_overload_bit_cmd,
+ "no set-overload-bit",
+ "Reset overload bit to accept transit traffic\n"
+ "Reset overload bit\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ isis_area_overload_bit_set(area, false);
+ return CMD_SUCCESS;
+}
+
+static int isis_vty_password_set(struct vty *vty, int argc,
+ struct cmd_token *argv[], int level)
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ int idx_algo = 1;
+ int idx_password = 2;
+ int idx_snp_auth = 5;
+ uint8_t snp_auth = 0;
+
+ const char *passwd = argv[idx_password]->arg;
+ if (strlen(passwd) > 254) {
+ vty_out(vty, "Too long area password (>254)\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (argc > idx_snp_auth) {
+ snp_auth = SNP_AUTH_SEND;
+ if (strmatch(argv[idx_snp_auth]->text, "validate"))
+ snp_auth |= SNP_AUTH_RECV;
+ }
+
+ if (strmatch(argv[idx_algo]->text, "clear")) {
+ return isis_area_passwd_cleartext_set(area, level,
+ passwd, snp_auth);
+ } else if (strmatch(argv[idx_algo]->text, "md5")) {
+ return isis_area_passwd_hmac_md5_set(area, level,
+ passwd, snp_auth);
+ }
+
+ return CMD_WARNING_CONFIG_FAILED;
+}
+
+DEFUN (domain_passwd,
+ domain_passwd_cmd,
+ "domain-password <clear|md5> WORD [authenticate snp <send-only|validate>]",
+ "Set the authentication password for a routing domain\n"
+ "Authentication type\n"
+ "Authentication type\n"
+ "Level-wide password\n"
+ "Authentication\n"
+ "SNP PDUs\n"
+ "Send but do not check PDUs on receiving\n"
+ "Send and check PDUs on receiving\n")
+{
+ return isis_vty_password_set(vty, argc, argv, IS_LEVEL_2);
+}
+
+DEFUN (no_domain_passwd,
+ no_domain_passwd_cmd,
+ "no domain-password",
+ NO_STR
+ "Set the authentication password for a routing domain\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ return isis_area_passwd_unset(area, IS_LEVEL_2);
+}
+
+static int
+isis_vty_lsp_gen_interval_set(struct vty *vty, int level, uint16_t interval)
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+ int lvl;
+
+ for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) {
+ if (!(lvl & level))
+ continue;
+
+ if (interval >= area->lsp_refresh[lvl - 1]) {
+ vty_out(vty,
+ "LSP gen interval %us must be less than the LSP refresh interval %us\n",
+ interval, area->lsp_refresh[lvl - 1]);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) {
+ if (!(lvl & level))
+ continue;
+ area->lsp_gen_interval[lvl - 1] = interval;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (lsp_gen_interval,
+ lsp_gen_interval_cmd,
+ "lsp-gen-interval (1-120)",
+ "Minimum interval between regenerating same LSP\n"
+ "Minimum interval in seconds\n")
+{
+ uint16_t interval = atoi(argv[1]->arg);
+
+ return isis_vty_lsp_gen_interval_set(vty, IS_LEVEL_1_AND_2, interval);
+}
+
+DEFUN (no_lsp_gen_interval,
+ no_lsp_gen_interval_cmd,
+ "no lsp-gen-interval [(1-120)]",
+ NO_STR
+ "Minimum interval between regenerating same LSP\n"
+ "Minimum interval in seconds\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ return isis_vty_lsp_gen_interval_set(vty, IS_LEVEL_1_AND_2,
+ DEFAULT_MIN_LSP_GEN_INTERVAL);
+}
+
+static int
+isis_vty_lsp_refresh_set(struct vty *vty, int level, uint16_t interval)
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+ int lvl;
+
+ for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) {
+ if (!(lvl & level))
+ continue;
+ if (interval <= area->lsp_gen_interval[lvl - 1]) {
+ vty_out(vty,
+ "LSP refresh interval %us must be greater than the configured LSP gen interval %us\n",
+ interval, area->lsp_gen_interval[lvl - 1]);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ if (interval > (area->max_lsp_lifetime[lvl - 1] - 300)) {
+ vty_out(vty,
+ "LSP refresh interval %us must be less than the configured LSP lifetime %us less 300\n",
+ interval, area->max_lsp_lifetime[lvl - 1]);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) {
+ if (!(lvl & level))
+ continue;
+ isis_area_lsp_refresh_set(area, lvl, interval);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (lsp_refresh_interval,
+ lsp_refresh_interval_cmd,
+ "lsp-refresh-interval (1-65235)",
+ "LSP refresh interval\n"
+ "LSP refresh interval in seconds\n")
+{
+ unsigned int interval = atoi(argv[1]->arg);
+ return isis_vty_lsp_refresh_set(vty, IS_LEVEL_1_AND_2, interval);
+}
+
+DEFUN (no_lsp_refresh_interval,
+ no_lsp_refresh_interval_cmd,
+ "no lsp-refresh-interval [(1-65235)]",
+ NO_STR
+ "LSP refresh interval\n"
+ "LSP refresh interval in seconds\n")
+{
+ return isis_vty_lsp_refresh_set(vty, IS_LEVEL_1_AND_2,
+ DEFAULT_MAX_LSP_GEN_INTERVAL);
+}
+
+static int
+isis_vty_max_lsp_lifetime_set(struct vty *vty, int level, uint16_t interval)
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+ int lvl;
+ uint16_t refresh_interval = interval - 300;
+ int set_refresh_interval[ISIS_LEVELS] = {0, 0};
+
+ for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) {
+ if (!(lvl & level))
+ continue;
+
+ if (refresh_interval < area->lsp_refresh[lvl - 1]) {
+ vty_out(vty,
+ "Level %d Max LSP lifetime %us must be 300s greater than the configured LSP refresh interval %us\n",
+ lvl, interval, area->lsp_refresh[lvl - 1]);
+ vty_out(vty,
+ "Automatically reducing level %d LSP refresh interval to %us\n",
+ lvl, refresh_interval);
+ set_refresh_interval[lvl - 1] = 1;
+
+ if (refresh_interval
+ <= area->lsp_gen_interval[lvl - 1]) {
+ vty_out(vty,
+ "LSP refresh interval %us must be greater than the configured LSP gen interval %us\n",
+ refresh_interval,
+ area->lsp_gen_interval[lvl - 1]);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+ }
+
+ for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) {
+ if (!(lvl & level))
+ continue;
+ isis_area_max_lsp_lifetime_set(area, lvl, interval);
+ if (set_refresh_interval[lvl - 1])
+ isis_area_lsp_refresh_set(area, lvl, refresh_interval);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (max_lsp_lifetime,
+ max_lsp_lifetime_cmd,
+ "max-lsp-lifetime (350-65535)",
+ "Maximum LSP lifetime\n"
+ "LSP lifetime in seconds\n")
+{
+ int lifetime = atoi(argv[1]->arg);
+
+ return isis_vty_max_lsp_lifetime_set(vty, IS_LEVEL_1_AND_2, lifetime);
+}
+
+
+DEFUN (no_max_lsp_lifetime,
+ no_max_lsp_lifetime_cmd,
+ "no max-lsp-lifetime [(350-65535)]",
+ NO_STR
+ "Maximum LSP lifetime\n"
+ "LSP lifetime in seconds\n")
+{
+ return isis_vty_max_lsp_lifetime_set(vty, IS_LEVEL_1_AND_2,
+ DEFAULT_LSP_LIFETIME);
+}
+
+DEFUN (spf_interval,
+ spf_interval_cmd,
+ "spf-interval (1-120)",
+ "Minimum interval between SPF calculations\n"
+ "Minimum interval between consecutive SPFs in seconds\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+ uint16_t interval = atoi(argv[1]->arg);
+
+ area->min_spf_interval[0] = interval;
+ area->min_spf_interval[1] = interval;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_spf_interval,
+ no_spf_interval_cmd,
+ "no spf-interval [(1-120)]",
+ NO_STR
+ "Minimum interval between SPF calculations\n"
+ "Minimum interval between consecutive SPFs in seconds\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ area->min_spf_interval[0] = MINIMUM_SPF_INTERVAL;
+ area->min_spf_interval[1] = MINIMUM_SPF_INTERVAL;
+
+ return CMD_SUCCESS;
+}
+
+static int isis_vty_lsp_mtu_set(struct vty *vty, unsigned int lsp_mtu)
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+ struct listnode *node;
+ struct isis_circuit *circuit;
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
+ if (circuit->state != C_STATE_INIT
+ && circuit->state != C_STATE_UP)
+ continue;
+ if (lsp_mtu > isis_circuit_pdu_size(circuit)) {
+ vty_out(vty,
+ "ISIS area contains circuit %s, which has a maximum PDU size of %zu.\n",
+ circuit->interface->name,
+ isis_circuit_pdu_size(circuit));
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ isis_area_lsp_mtu_set(area, lsp_mtu);
+ return CMD_SUCCESS;
+}
+
+DEFUN (area_lsp_mtu,
+ area_lsp_mtu_cmd,
+ "lsp-mtu (128-4352)",
+ "Configure the maximum size of generated LSPs\n"
+ "Maximum size of generated LSPs\n")
+{
+ int idx_number = 1;
+ unsigned int lsp_mtu;
+
+ lsp_mtu = strtoul(argv[idx_number]->arg, NULL, 10);
+
+ return isis_vty_lsp_mtu_set(vty, lsp_mtu);
+}
+
+DEFUN (no_area_lsp_mtu,
+ no_area_lsp_mtu_cmd,
+ "no lsp-mtu [(128-4352)]",
+ NO_STR
+ "Configure the maximum size of generated LSPs\n"
+ "Maximum size of generated LSPs\n")
+{
+ return isis_vty_lsp_mtu_set(vty, DEFAULT_LSP_MTU);
+}
+
+DEFUN (no_spf_delay_ietf,
+ no_spf_delay_ietf_cmd,
+ "no spf-delay-ietf",
+ NO_STR
+ "IETF SPF delay algorithm\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ spf_backoff_free(area->spf_delay_ietf[0]);
+ spf_backoff_free(area->spf_delay_ietf[1]);
+ area->spf_delay_ietf[0] = NULL;
+ area->spf_delay_ietf[1] = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (spf_delay_ietf,
+ spf_delay_ietf_cmd,
+ "spf-delay-ietf init-delay (0-60000) short-delay (0-60000) long-delay (0-60000) holddown (0-60000) time-to-learn (0-60000)",
+ "IETF SPF delay algorithm\n"
+ "Delay used while in QUIET state\n"
+ "Delay used while in QUIET state in milliseconds\n"
+ "Delay used while in SHORT_WAIT state\n"
+ "Delay used while in SHORT_WAIT state in milliseconds\n"
+ "Delay used while in LONG_WAIT\n"
+ "Delay used while in LONG_WAIT state in milliseconds\n"
+ "Time with no received IGP events before considering IGP stable\n"
+ "Time with no received IGP events before considering IGP stable (in milliseconds)\n"
+ "Maximum duration needed to learn all the events related to a single failure\n"
+ "Maximum duration needed to learn all the events related to a single failure (in milliseconds)\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ long init_delay = atol(argv[2]->arg);
+ long short_delay = atol(argv[4]->arg);
+ long long_delay = atol(argv[6]->arg);
+ long holddown = atol(argv[8]->arg);
+ long timetolearn = atol(argv[10]->arg);
+
+ size_t bufsiz = strlen(area->area_tag) + sizeof("IS-IS Lx");
+ char *buf = XCALLOC(MTYPE_TMP, bufsiz);
+
+ snprintf(buf, bufsiz, "IS-IS %s L1", area->area_tag);
+ spf_backoff_free(area->spf_delay_ietf[0]);
+ area->spf_delay_ietf[0] =
+ spf_backoff_new(master, buf, init_delay, short_delay,
+ long_delay, holddown, timetolearn);
+
+ snprintf(buf, bufsiz, "IS-IS %s L2", area->area_tag);
+ spf_backoff_free(area->spf_delay_ietf[1]);
+ area->spf_delay_ietf[1] =
+ spf_backoff_new(master, buf, init_delay, short_delay,
+ long_delay, holddown, timetolearn);
+
+ XFREE(MTYPE_TMP, buf);
+ return CMD_SUCCESS;
+}
+
+DEFUN (area_purge_originator,
+ area_purge_originator_cmd,
+ "[no] purge-originator",
+ NO_STR
+ "Use the RFC 6232 purge-originator\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ area->purge_originator = !!strcmp(argv[0]->text, "no");
+ return CMD_SUCCESS;
+}
+
+DEFUN (isis_passive,
+ isis_passive_cmd,
+ PROTO_NAME " passive",
+ PROTO_HELP
+ "Configure the passive mode for interface\n")
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ CMD_FERR_RETURN(isis_circuit_passive_set(circuit, 1),
+ "Cannot set passive: $ERR");
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_isis_passive,
+ no_isis_passive_cmd,
+ "no " PROTO_NAME " passive",
+ NO_STR
+ PROTO_HELP
+ "Configure the passive mode for interface\n")
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ CMD_FERR_RETURN(isis_circuit_passive_set(circuit, 0),
+ "Cannot set no passive: $ERR");
+ return CMD_SUCCESS;
+}
+
+DEFUN (isis_passwd,
+ isis_passwd_cmd,
+ PROTO_NAME " password <md5|clear> WORD",
+ PROTO_HELP
+ "Configure the authentication password for a circuit\n"
+ "HMAC-MD5 authentication\n"
+ "Cleartext password\n"
+ "Circuit password\n")
+{
+ int idx_encryption = 2;
+ int idx_word = 3;
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ ferr_r rv;
+
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ if (argv[idx_encryption]->arg[0] == 'm')
+ rv = isis_circuit_passwd_hmac_md5_set(circuit,
+ argv[idx_word]->arg);
+ else
+ rv = isis_circuit_passwd_cleartext_set(circuit,
+ argv[idx_word]->arg);
+
+ CMD_FERR_RETURN(rv, "Failed to set circuit password: $ERR");
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_isis_passwd,
+ no_isis_passwd_cmd,
+ "no " PROTO_NAME " password [<md5|clear> WORD]",
+ NO_STR
+ PROTO_HELP
+ "Configure the authentication password for a circuit\n"
+ "HMAC-MD5 authentication\n"
+ "Cleartext password\n"
+ "Circuit password\n")
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ CMD_FERR_RETURN(isis_circuit_passwd_unset(circuit),
+ "Failed to unset circuit password: $ERR");
+ return CMD_SUCCESS;
+}
+
+DEFUN (isis_metric,
+ isis_metric_cmd,
+ PROTO_NAME " metric (0-16777215)",
+ PROTO_HELP
+ "Set default metric for circuit\n"
+ "Default metric value\n")
+{
+ int idx_number = 2;
+ int met;
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ met = atoi(argv[idx_number]->arg);
+
+ /* RFC3787 section 5.1 */
+ if (circuit->area && circuit->area->oldmetric == 1
+ && met > MAX_NARROW_LINK_METRIC) {
+ vty_out(vty,
+ "Invalid metric %d - should be <0-63> when narrow metric type enabled\n",
+ met);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ /* RFC4444 */
+ if (circuit->area && circuit->area->newmetric == 1
+ && met > MAX_WIDE_LINK_METRIC) {
+ vty_out(vty,
+ "Invalid metric %d - should be <0-16777215> when wide metric type enabled\n",
+ met);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_1, met),
+ "Failed to set L1 metric: $ERR");
+ CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_2, met),
+ "Failed to set L2 metric: $ERR");
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_isis_metric,
+ no_isis_metric_cmd,
+ "no " PROTO_NAME " metric [(0-16777215)]",
+ NO_STR
+ PROTO_HELP
+ "Set default metric for circuit\n"
+ "Default metric value\n")
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_1,
+ DEFAULT_CIRCUIT_METRIC),
+ "Failed to set L1 metric: $ERR");
+ CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_2,
+ DEFAULT_CIRCUIT_METRIC),
+ "Failed to set L2 metric: $ERR");
+ return CMD_SUCCESS;
+}
+
+DEFUN (isis_hello_interval,
+ isis_hello_interval_cmd,
+ PROTO_NAME " hello-interval (1-600)",
+ PROTO_HELP
+ "Set Hello interval\n"
+ "Holdtime 1 seconds, interval depends on multiplier\n")
+{
+ uint32_t interval = atoi(argv[2]->arg);
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ circuit->hello_interval[0] = interval;
+ circuit->hello_interval[1] = interval;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_isis_hello_interval,
+ no_isis_hello_interval_cmd,
+ "no " PROTO_NAME " hello-interval [(1-600)]",
+ NO_STR
+ PROTO_HELP
+ "Set Hello interval\n"
+ "Holdtime 1 second, interval depends on multiplier\n")
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ circuit->hello_interval[0] = DEFAULT_HELLO_INTERVAL;
+ circuit->hello_interval[1] = DEFAULT_HELLO_INTERVAL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (isis_hello_multiplier,
+ isis_hello_multiplier_cmd,
+ PROTO_NAME " hello-multiplier (2-100)",
+ PROTO_HELP
+ "Set multiplier for Hello holding time\n"
+ "Hello multiplier value\n")
+{
+ uint16_t mult = atoi(argv[2]->arg);
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ circuit->hello_multiplier[0] = mult;
+ circuit->hello_multiplier[1] = mult;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_isis_hello_multiplier,
+ no_isis_hello_multiplier_cmd,
+ "no " PROTO_NAME " hello-multiplier [(2-100)]",
+ NO_STR
+ PROTO_HELP
+ "Set multiplier for Hello holding time\n"
+ "Hello multiplier value\n")
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ circuit->hello_multiplier[0] = DEFAULT_HELLO_MULTIPLIER;
+ circuit->hello_multiplier[1] = DEFAULT_HELLO_MULTIPLIER;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (csnp_interval,
+ csnp_interval_cmd,
+ PROTO_NAME " csnp-interval (1-600)",
+ PROTO_HELP
+ "Set CSNP interval in seconds\n"
+ "CSNP interval value\n")
+{
+ uint16_t interval = atoi(argv[2]->arg);
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ circuit->csnp_interval[0] = interval;
+ circuit->csnp_interval[1] = interval;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_csnp_interval,
+ no_csnp_interval_cmd,
+ "no " PROTO_NAME " csnp-interval [(1-600)]",
+ NO_STR
+ PROTO_HELP
+ "Set CSNP interval in seconds\n"
+ "CSNP interval value\n")
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ circuit->csnp_interval[0] = DEFAULT_CSNP_INTERVAL;
+ circuit->csnp_interval[1] = DEFAULT_CSNP_INTERVAL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (psnp_interval,
+ psnp_interval_cmd,
+ PROTO_NAME " psnp-interval (1-120)",
+ PROTO_HELP
+ "Set PSNP interval in seconds\n"
+ "PSNP interval value\n")
+{
+ uint16_t interval = atoi(argv[2]->arg);
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ circuit->psnp_interval[0] = interval;
+ circuit->psnp_interval[1] = interval;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_psnp_interval,
+ no_psnp_interval_cmd,
+ "no " PROTO_NAME " psnp-interval [(1-120)]",
+ NO_STR
+ PROTO_HELP
+ "Set PSNP interval in seconds\n"
+ "PSNP interval value\n")
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+
+ circuit->psnp_interval[0] = DEFAULT_PSNP_INTERVAL;
+ circuit->psnp_interval[1] = DEFAULT_PSNP_INTERVAL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (circuit_topology,
+ circuit_topology_cmd,
+ PROTO_NAME " topology " ISIS_MT_NAMES,
+ PROTO_HELP
+ "Configure interface IS-IS topologies\n"
+ ISIS_MT_DESCRIPTIONS)
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+ const char *arg = argv[2]->arg;
+ uint16_t mtid = isis_str2mtid(arg);
+
+ if (circuit->area && circuit->area->oldmetric) {
+ vty_out(vty,
+ "Multi topology IS-IS can only be used with wide metrics\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (mtid == (uint16_t)-1) {
+ vty_out(vty, "Don't know topology '%s'\n", arg);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ return isis_circuit_mt_enabled_set(circuit, mtid, true);
+}
+
+DEFUN (no_circuit_topology,
+ no_circuit_topology_cmd,
+ "no " PROTO_NAME " topology " ISIS_MT_NAMES,
+ NO_STR
+ PROTO_HELP
+ "Configure interface IS-IS topologies\n"
+ ISIS_MT_DESCRIPTIONS)
+{
+ struct isis_circuit *circuit = isis_circuit_lookup(vty);
+ if (!circuit)
+ return CMD_ERR_NO_MATCH;
+ const char *arg = argv[3]->arg;
+ uint16_t mtid = isis_str2mtid(arg);
+
+ if (circuit->area && circuit->area->oldmetric) {
+ vty_out(vty,
+ "Multi topology IS-IS can only be used with wide metrics\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (mtid == (uint16_t)-1) {
+ vty_out(vty, "Don't know topology '%s'\n", arg);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ return isis_circuit_mt_enabled_set(circuit, mtid, false);
+}
+
+void isis_vty_daemon_init(void)
+{
+ install_element(ROUTER_NODE, &fabric_tier_cmd);
+ install_element(ROUTER_NODE, &no_fabric_tier_cmd);
+ install_element(ROUTER_NODE, &triggered_csnp_cmd);
+ install_element(ROUTER_NODE, &no_triggered_csnp_cmd);
+
+ install_element(ENABLE_NODE, &show_lsp_flooding_cmd);
+
+ install_element(INTERFACE_NODE, &ip_router_isis_cmd);
+ install_element(INTERFACE_NODE, &ip6_router_isis_cmd);
+ install_element(INTERFACE_NODE, &no_ip_router_isis_cmd);
+ install_element(INTERFACE_NODE, &isis_bfd_cmd);
+ install_element(INTERFACE_NODE, &no_isis_bfd_cmd);
+
+ install_element(ROUTER_NODE, &set_overload_bit_cmd);
+ install_element(ROUTER_NODE, &no_set_overload_bit_cmd);
+
+ install_element(ROUTER_NODE, &domain_passwd_cmd);
+ install_element(ROUTER_NODE, &no_domain_passwd_cmd);
+
+ install_element(ROUTER_NODE, &lsp_gen_interval_cmd);
+ install_element(ROUTER_NODE, &no_lsp_gen_interval_cmd);
+
+ install_element(ROUTER_NODE, &lsp_refresh_interval_cmd);
+ install_element(ROUTER_NODE, &no_lsp_refresh_interval_cmd);
+
+ install_element(ROUTER_NODE, &max_lsp_lifetime_cmd);
+ install_element(ROUTER_NODE, &no_max_lsp_lifetime_cmd);
+
+ install_element(ROUTER_NODE, &area_lsp_mtu_cmd);
+ install_element(ROUTER_NODE, &no_area_lsp_mtu_cmd);
+
+ install_element(ROUTER_NODE, &spf_interval_cmd);
+ install_element(ROUTER_NODE, &no_spf_interval_cmd);
+
+ install_element(ROUTER_NODE, &spf_delay_ietf_cmd);
+ install_element(ROUTER_NODE, &no_spf_delay_ietf_cmd);
+
+ install_element(ROUTER_NODE, &area_purge_originator_cmd);
+
+ install_element(INTERFACE_NODE, &isis_passive_cmd);
+ install_element(INTERFACE_NODE, &no_isis_passive_cmd);
+
+ install_element(INTERFACE_NODE, &isis_passwd_cmd);
+ install_element(INTERFACE_NODE, &no_isis_passwd_cmd);
+
+ install_element(INTERFACE_NODE, &isis_metric_cmd);
+ install_element(INTERFACE_NODE, &no_isis_metric_cmd);
+
+ install_element(INTERFACE_NODE, &isis_hello_interval_cmd);
+ install_element(INTERFACE_NODE, &no_isis_hello_interval_cmd);
+
+ install_element(INTERFACE_NODE, &isis_hello_multiplier_cmd);
+ install_element(INTERFACE_NODE, &no_isis_hello_multiplier_cmd);
+
+ install_element(INTERFACE_NODE, &csnp_interval_cmd);
+ install_element(INTERFACE_NODE, &no_csnp_interval_cmd);
+
+ install_element(INTERFACE_NODE, &psnp_interval_cmd);
+ install_element(INTERFACE_NODE, &no_psnp_interval_cmd);
+
+ install_element(INTERFACE_NODE, &circuit_topology_cmd);
+ install_element(INTERFACE_NODE, &no_circuit_topology_cmd);
+}
diff --git a/isisd/isis_zebra.c b/isisd/isis_zebra.c
new file mode 100644
index 0000000..8252c1a
--- /dev/null
+++ b/isisd/isis_zebra.c
@@ -0,0 +1,1422 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_zebra.c
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ * Copyright (C) 2013-2015 Christian Franke <chris@opensourcerouting.org>
+ */
+
+#include <zebra.h>
+
+#include "frrevent.h"
+#include "command.h"
+#include "memory.h"
+#include "log.h"
+#include "lib_errors.h"
+#include "if.h"
+#include "network.h"
+#include "prefix.h"
+#include "zclient.h"
+#include "stream.h"
+#include "linklist.h"
+#include "nexthop.h"
+#include "vrf.h"
+#include "libfrr.h"
+#include "bfd.h"
+#include "link_state.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_spf.h"
+#include "isisd/isis_spf_private.h"
+#include "isisd/isis_route.h"
+#include "isisd/isis_zebra.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_sr.h"
+#include "isisd/isis_ldp_sync.h"
+
+struct zclient *zclient;
+static struct zclient *zclient_sync;
+
+/* Router-id update message from zebra. */
+static int isis_router_id_update_zebra(ZAPI_CALLBACK_ARGS)
+{
+ struct isis_area *area;
+ struct listnode *node;
+ struct prefix router_id;
+ struct isis *isis = NULL;
+
+ isis = isis_lookup_by_vrfid(vrf_id);
+
+ if (isis == NULL) {
+ return -1;
+ }
+
+ zebra_router_id_update_read(zclient->ibuf, &router_id);
+ if (isis->router_id == router_id.u.prefix4.s_addr)
+ return 0;
+
+ isis->router_id = router_id.u.prefix4.s_addr;
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area))
+ if (listcount(area->area_addrs) > 0)
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ return 0;
+}
+
+static int isis_zebra_if_address_add(ZAPI_CALLBACK_ARGS)
+{
+ struct isis_circuit *circuit;
+ struct connected *c;
+
+ c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD,
+ zclient->ibuf, vrf_id);
+
+ if (c == NULL)
+ return 0;
+
+#ifdef EXTREME_DEBUG
+ if (c->address->family == AF_INET)
+ zlog_debug("connected IP address %pFX", c->address);
+ if (c->address->family == AF_INET6)
+ zlog_debug("connected IPv6 address %pFX", c->address);
+#endif /* EXTREME_DEBUG */
+
+ if (if_is_operative(c->ifp)) {
+ circuit = circuit_scan_by_ifp(c->ifp);
+ if (circuit)
+ isis_circuit_add_addr(circuit, c);
+ }
+
+ sr_if_addr_update(c->ifp);
+
+ return 0;
+}
+
+static int isis_zebra_if_address_del(ZAPI_CALLBACK_ARGS)
+{
+ struct isis_circuit *circuit;
+ struct connected *c;
+
+ c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE,
+ zclient->ibuf, vrf_id);
+
+ if (c == NULL)
+ return 0;
+
+#ifdef EXTREME_DEBUG
+ if (c->address->family == AF_INET)
+ zlog_debug("disconnected IP address %pFX", c->address);
+ if (c->address->family == AF_INET6)
+ zlog_debug("disconnected IPv6 address %pFX", c->address);
+#endif /* EXTREME_DEBUG */
+
+ if (if_is_operative(c->ifp)) {
+ circuit = circuit_scan_by_ifp(c->ifp);
+ if (circuit)
+ isis_circuit_del_addr(circuit, c);
+ }
+
+ sr_if_addr_update(c->ifp);
+
+ connected_free(&c);
+
+ return 0;
+}
+
+static int isis_zebra_link_params(ZAPI_CALLBACK_ARGS)
+{
+ struct interface *ifp;
+ bool changed = false;
+
+ ifp = zebra_interface_link_params_read(zclient->ibuf, vrf_id, &changed);
+
+ if (ifp == NULL || !changed)
+ return 0;
+
+ /* Update TE TLV */
+ isis_mpls_te_update(ifp);
+
+ return 0;
+}
+
+enum isis_zebra_nexthop_type {
+ ISIS_NEXTHOP_MAIN = 0,
+ ISIS_NEXTHOP_BACKUP,
+};
+
+static int isis_zebra_add_nexthops(struct isis *isis, struct list *nexthops,
+ struct zapi_nexthop zapi_nexthops[],
+ enum isis_zebra_nexthop_type type,
+ bool mpls_lsp, uint8_t backup_nhs)
+{
+ struct isis_nexthop *nexthop;
+ struct listnode *node;
+ int count = 0;
+
+ /* Nexthops */
+ for (ALL_LIST_ELEMENTS_RO(nexthops, node, nexthop)) {
+ struct zapi_nexthop *api_nh;
+
+ if (count >= MULTIPATH_NUM)
+ break;
+ api_nh = &zapi_nexthops[count];
+ if (fabricd)
+ SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_ONLINK);
+ api_nh->vrf_id = isis->vrf_id;
+
+ switch (nexthop->family) {
+ case AF_INET:
+ /* FIXME: can it be ? */
+ if (nexthop->ip.ipv4.s_addr != INADDR_ANY) {
+ api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX;
+ api_nh->gate.ipv4 = nexthop->ip.ipv4;
+ } else {
+ api_nh->type = NEXTHOP_TYPE_IFINDEX;
+ }
+ break;
+ case AF_INET6:
+ if (!IN6_IS_ADDR_LINKLOCAL(&nexthop->ip.ipv6)
+ && !IN6_IS_ADDR_UNSPECIFIED(&nexthop->ip.ipv6)) {
+ continue;
+ }
+ api_nh->gate.ipv6 = nexthop->ip.ipv6;
+ api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX;
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unknown address family [%d]", __func__,
+ nexthop->family);
+ exit(1);
+ }
+
+ api_nh->ifindex = nexthop->ifindex;
+
+ /* Add MPLS label(s). */
+ if (nexthop->label_stack) {
+ api_nh->label_num = nexthop->label_stack->num_labels;
+ memcpy(api_nh->labels, nexthop->label_stack->label,
+ sizeof(mpls_label_t) * api_nh->label_num);
+ } else if (nexthop->sr.present) {
+ api_nh->label_num = 1;
+ api_nh->labels[0] = nexthop->sr.label;
+ } else if (mpls_lsp) {
+ switch (type) {
+ case ISIS_NEXTHOP_MAIN:
+ /*
+ * Do not use non-SR enabled nexthops to prevent
+ * broken LSPs from being formed.
+ */
+ continue;
+ case ISIS_NEXTHOP_BACKUP:
+ /*
+ * This is necessary because zebra requires
+ * the nexthops of MPLS LSPs to be labeled.
+ */
+ api_nh->label_num = 1;
+ api_nh->labels[0] = MPLS_LABEL_IMPLICIT_NULL;
+ break;
+ }
+ }
+
+ /* Backup nexthop handling. */
+ if (backup_nhs) {
+ SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP);
+ /*
+ * If the backup has multiple nexthops, all of them
+ * protect the same primary nexthop since ECMP routes
+ * have no backups.
+ */
+ api_nh->backup_num = backup_nhs;
+ for (int i = 0; i < backup_nhs; i++)
+ api_nh->backup_idx[i] = i;
+ }
+ count++;
+ }
+
+ return count;
+}
+
+void isis_zebra_route_add_route(struct isis *isis, struct prefix *prefix,
+ struct prefix_ipv6 *src_p,
+ struct isis_route_info *route_info)
+{
+ struct zapi_route api;
+ int count = 0;
+
+ if (zclient->sock < 0)
+ return;
+
+ /* Uninstall the route if it doesn't have any valid nexthop. */
+ if (list_isempty(route_info->nexthops)) {
+ isis_zebra_route_del_route(isis, prefix, src_p, route_info);
+ return;
+ }
+
+ memset(&api, 0, sizeof(api));
+ api.vrf_id = isis->vrf_id;
+ api.type = PROTO_TYPE;
+ api.safi = SAFI_UNICAST;
+ api.prefix = *prefix;
+ if (src_p && src_p->prefixlen) {
+ api.src_prefix = *src_p;
+ SET_FLAG(api.message, ZAPI_MESSAGE_SRCPFX);
+ }
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+ SET_FLAG(api.message, ZAPI_MESSAGE_METRIC);
+ api.metric = route_info->cost;
+
+ /* Add backup nexthops first. */
+ if (route_info->backup) {
+ count = isis_zebra_add_nexthops(
+ isis, route_info->backup->nexthops, api.backup_nexthops,
+ ISIS_NEXTHOP_BACKUP, false, 0);
+ if (count > 0) {
+ SET_FLAG(api.message, ZAPI_MESSAGE_BACKUP_NEXTHOPS);
+ api.backup_nexthop_num = count;
+ }
+ }
+
+ /* Add primary nexthops. */
+ count = isis_zebra_add_nexthops(isis, route_info->nexthops,
+ api.nexthops, ISIS_NEXTHOP_MAIN, false,
+ count);
+ if (!count)
+ return;
+ api.nexthop_num = count;
+
+ zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api);
+}
+
+void isis_zebra_route_del_route(struct isis *isis,
+ struct prefix *prefix,
+ struct prefix_ipv6 *src_p,
+ struct isis_route_info *route_info)
+{
+ struct zapi_route api;
+
+ if (zclient->sock < 0)
+ return;
+
+ if (!CHECK_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED))
+ return;
+
+ memset(&api, 0, sizeof(api));
+ api.vrf_id = isis->vrf_id;
+ api.type = PROTO_TYPE;
+ api.safi = SAFI_UNICAST;
+ api.prefix = *prefix;
+ if (src_p && src_p->prefixlen) {
+ api.src_prefix = *src_p;
+ SET_FLAG(api.message, ZAPI_MESSAGE_SRCPFX);
+ }
+
+ zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api);
+}
+
+/**
+ * Install Prefix-SID label entry in the forwarding plane through Zebra.
+ *
+ * @param area IS-IS area
+ * @param prefix Route prefix
+ * @param rinfo Route information
+ * @param psid Prefix-SID information
+ */
+void isis_zebra_prefix_sid_install(struct isis_area *area,
+ struct prefix *prefix,
+ struct isis_sr_psid_info *psid)
+{
+ struct zapi_labels zl;
+ int count = 0;
+
+ sr_debug("ISIS-Sr (%s): update label %u for prefix %pFX algorithm %u",
+ area->area_tag, psid->label, prefix, psid->algorithm);
+
+ /* Prepare message. */
+ memset(&zl, 0, sizeof(zl));
+ zl.type = ZEBRA_LSP_ISIS_SR;
+ zl.local_label = psid->label;
+
+ /* Local routes don't have any nexthop and require special handling. */
+ if (list_isempty(psid->nexthops)) {
+ struct zapi_nexthop *znh;
+ struct interface *ifp;
+
+ ifp = if_lookup_by_name("lo", VRF_DEFAULT);
+ if (!ifp) {
+ zlog_warn(
+ "%s: couldn't install Prefix-SID %pFX: loopback interface not found",
+ __func__, prefix);
+ return;
+ }
+
+ znh = &zl.nexthops[zl.nexthop_num++];
+ znh->type = NEXTHOP_TYPE_IFINDEX;
+ znh->ifindex = ifp->ifindex;
+ znh->label_num = 1;
+ znh->labels[0] = MPLS_LABEL_IMPLICIT_NULL;
+ } else {
+ /* Add backup nexthops first. */
+ if (psid->nexthops_backup) {
+ count = isis_zebra_add_nexthops(
+ area->isis, psid->nexthops_backup,
+ zl.backup_nexthops, ISIS_NEXTHOP_BACKUP, true,
+ 0);
+ if (count > 0) {
+ SET_FLAG(zl.message, ZAPI_LABELS_HAS_BACKUPS);
+ zl.backup_nexthop_num = count;
+ }
+ }
+
+ /* Add primary nexthops. */
+ count = isis_zebra_add_nexthops(area->isis, psid->nexthops,
+ zl.nexthops, ISIS_NEXTHOP_MAIN,
+ true, count);
+ if (!count)
+ return;
+ zl.nexthop_num = count;
+ }
+
+ /* Send message to zebra. */
+ (void)zebra_send_mpls_labels(zclient, ZEBRA_MPLS_LABELS_REPLACE, &zl);
+}
+
+/**
+ * Uninstall Prefix-SID label entry from the forwarding plane through Zebra.
+ *
+ * @param area IS-IS area
+ * @param prefix Route prefix
+ * @param rinfo Route information
+ * @param psid Prefix-SID information
+ */
+void isis_zebra_prefix_sid_uninstall(struct isis_area *area,
+ struct prefix *prefix,
+ struct isis_route_info *rinfo,
+ struct isis_sr_psid_info *psid)
+{
+ struct zapi_labels zl;
+
+ sr_debug("ISIS-Sr (%s): delete label %u for prefix %pFX algorithm %u",
+ area->area_tag, psid->label, prefix, psid->algorithm);
+
+ /* Prepare message. */
+ memset(&zl, 0, sizeof(zl));
+ zl.type = ZEBRA_LSP_ISIS_SR;
+ zl.local_label = psid->label;
+
+ /* Send message to zebra. */
+ (void)zebra_send_mpls_labels(zclient, ZEBRA_MPLS_LABELS_DELETE, &zl);
+}
+
+/**
+ * Send (LAN)-Adjacency-SID to ZEBRA for installation or deletion.
+ *
+ * @param cmd ZEBRA_MPLS_LABELS_ADD or ZEBRA_ROUTE_DELETE
+ * @param sra Segment Routing Adjacency-SID
+ */
+void isis_zebra_send_adjacency_sid(int cmd, const struct sr_adjacency *sra)
+{
+ struct isis *isis = sra->adj->circuit->area->isis;
+ struct zapi_labels zl;
+ struct zapi_nexthop *znh;
+
+ if (cmd != ZEBRA_MPLS_LABELS_ADD && cmd != ZEBRA_MPLS_LABELS_DELETE) {
+ flog_warn(EC_LIB_DEVELOPMENT, "%s: wrong ZEBRA command",
+ __func__);
+ return;
+ }
+
+ sr_debug(" |- %s label %u for interface %s",
+ cmd == ZEBRA_MPLS_LABELS_ADD ? "Add" : "Delete",
+ sra->input_label, sra->adj->circuit->interface->name);
+
+ memset(&zl, 0, sizeof(zl));
+ zl.type = ZEBRA_LSP_ISIS_SR;
+ zl.local_label = sra->input_label;
+ zl.nexthop_num = 1;
+ znh = &zl.nexthops[0];
+ znh->gate = sra->nexthop.address;
+ znh->type = (sra->nexthop.family == AF_INET)
+ ? NEXTHOP_TYPE_IPV4_IFINDEX
+ : NEXTHOP_TYPE_IPV6_IFINDEX;
+ znh->ifindex = sra->adj->circuit->interface->ifindex;
+ znh->label_num = 1;
+ znh->labels[0] = MPLS_LABEL_IMPLICIT_NULL;
+
+ /* Set backup nexthops. */
+ if (sra->type == ISIS_SR_LAN_BACKUP) {
+ int count;
+
+ count = isis_zebra_add_nexthops(isis, sra->backup_nexthops,
+ zl.backup_nexthops,
+ ISIS_NEXTHOP_BACKUP, true, 0);
+ if (count > 0) {
+ SET_FLAG(zl.message, ZAPI_LABELS_HAS_BACKUPS);
+ zl.backup_nexthop_num = count;
+
+ SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP);
+ znh->backup_num = count;
+ for (int i = 0; i < count; i++)
+ znh->backup_idx[i] = i;
+ }
+ }
+
+ (void)zebra_send_mpls_labels(zclient, cmd, &zl);
+}
+
+static int isis_zebra_read(ZAPI_CALLBACK_ARGS)
+{
+ struct zapi_route api;
+ struct isis *isis = NULL;
+
+ isis = isis_lookup_by_vrfid(vrf_id);
+
+ if (isis == NULL)
+ return -1;
+
+ if (zapi_route_decode(zclient->ibuf, &api) < 0)
+ return -1;
+
+ if (api.prefix.family == AF_INET6
+ && IN6_IS_ADDR_LINKLOCAL(&api.prefix.u.prefix6))
+ return 0;
+
+ /*
+ * Avoid advertising a false default reachability. (A default
+ * route installed by IS-IS gets redistributed from zebra back
+ * into IS-IS causing us to start advertising default reachabity
+ * without this check)
+ */
+ if (api.prefix.prefixlen == 0
+ && api.src_prefix.prefixlen == 0
+ && api.type == PROTO_TYPE) {
+ cmd = ZEBRA_REDISTRIBUTE_ROUTE_DEL;
+ }
+
+ if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD)
+ isis_redist_add(isis, api.type, &api.prefix, &api.src_prefix,
+ api.distance, api.metric, api.tag, api.instance);
+ else
+ isis_redist_delete(isis, api.type, &api.prefix, &api.src_prefix,
+ api.instance);
+
+ return 0;
+}
+
+int isis_distribute_list_update(int routetype)
+{
+ return 0;
+}
+
+void isis_zebra_redistribute_set(afi_t afi, int type, vrf_id_t vrf_id,
+ uint16_t tableid)
+{
+ if (type == DEFAULT_ROUTE)
+ zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_ADD,
+ zclient, afi, vrf_id);
+ else
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, afi, type,
+ tableid, vrf_id);
+}
+
+void isis_zebra_redistribute_unset(afi_t afi, int type, vrf_id_t vrf_id,
+ uint16_t tableid)
+{
+ if (type == DEFAULT_ROUTE)
+ zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE,
+ zclient, afi, vrf_id);
+ else
+ zclient_redistribute(ZEBRA_REDISTRIBUTE_DELETE, zclient, afi,
+ type, tableid, vrf_id);
+}
+
+/**
+ * Register RLFA with LDP.
+ */
+int isis_zebra_rlfa_register(struct isis_spftree *spftree, struct rlfa *rlfa)
+{
+ struct isis_area *area = spftree->area;
+ struct zapi_rlfa_request zr = {};
+ int ret;
+
+ if (!zclient)
+ return 0;
+
+ zr.igp.vrf_id = area->isis->vrf_id;
+ zr.igp.protocol = ZEBRA_ROUTE_ISIS;
+ strlcpy(zr.igp.isis.area_tag, area->area_tag,
+ sizeof(zr.igp.isis.area_tag));
+ zr.igp.isis.spf.tree_id = spftree->tree_id;
+ zr.igp.isis.spf.level = spftree->level;
+ zr.igp.isis.spf.run_id = spftree->runcount;
+ zr.destination = rlfa->prefix;
+ zr.pq_address = rlfa->pq_address;
+
+ zlog_debug("ISIS-LFA: registering RLFA %pFX@%pI4 with LDP",
+ &rlfa->prefix, &rlfa->pq_address);
+
+ ret = zclient_send_opaque_unicast(zclient, LDP_RLFA_REGISTER,
+ ZEBRA_ROUTE_LDP, 0, 0,
+ (const uint8_t *)&zr, sizeof(zr));
+ if (ret == ZCLIENT_SEND_FAILURE) {
+ zlog_warn("ISIS-LFA: failed to register RLFA with LDP");
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Unregister all RLFAs from the given SPF tree with LDP.
+ */
+void isis_zebra_rlfa_unregister_all(struct isis_spftree *spftree)
+{
+ struct isis_area *area = spftree->area;
+ struct zapi_rlfa_igp igp = {};
+ int ret;
+
+ if (!zclient || spftree->type != SPF_TYPE_FORWARD
+ || CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES))
+ return;
+
+ if (IS_DEBUG_LFA)
+ zlog_debug("ISIS-LFA: unregistering all RLFAs with LDP");
+
+ igp.vrf_id = area->isis->vrf_id;
+ igp.protocol = ZEBRA_ROUTE_ISIS;
+ strlcpy(igp.isis.area_tag, area->area_tag, sizeof(igp.isis.area_tag));
+ igp.isis.spf.tree_id = spftree->tree_id;
+ igp.isis.spf.level = spftree->level;
+ igp.isis.spf.run_id = spftree->runcount;
+
+ ret = zclient_send_opaque_unicast(zclient, LDP_RLFA_UNREGISTER_ALL,
+ ZEBRA_ROUTE_LDP, 0, 0,
+ (const uint8_t *)&igp, sizeof(igp));
+ if (ret == ZCLIENT_SEND_FAILURE)
+ zlog_warn("ISIS-LFA: failed to unregister RLFA with LDP");
+}
+
+/* Label Manager Functions */
+
+/**
+ * Check if Label Manager is Ready or not.
+ *
+ * @return True if Label Manager is ready, False otherwise
+ */
+bool isis_zebra_label_manager_ready(void)
+{
+ return (zclient_sync->sock > 0);
+}
+
+/**
+ * Request Label Range to the Label Manager.
+ *
+ * @param base base label of the label range to request
+ * @param chunk_size size of the label range to request
+ *
+ * @return 0 on success, -1 on failure
+ */
+int isis_zebra_request_label_range(uint32_t base, uint32_t chunk_size)
+{
+ int ret;
+ uint32_t start, end;
+
+ if (zclient_sync->sock < 0)
+ return -1;
+
+ ret = lm_get_label_chunk(zclient_sync, 0, base, chunk_size, &start,
+ &end);
+ if (ret < 0) {
+ zlog_warn("%s: error getting label range!", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Release Label Range to the Label Manager.
+ *
+ * @param start start of label range to release
+ * @param end end of label range to release
+ *
+ * @return 0 on success, -1 otherwise
+ */
+int isis_zebra_release_label_range(uint32_t start, uint32_t end)
+{
+ int ret;
+
+ if (zclient_sync->sock < 0)
+ return -1;
+
+ ret = lm_release_label_chunk(zclient_sync, start, end);
+ if (ret < 0) {
+ zlog_warn("%s: error releasing label range!", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Connect to the Label Manager.
+ *
+ * @return 0 on success, -1 otherwise
+ */
+int isis_zebra_label_manager_connect(void)
+{
+ /* Connect to label manager. */
+ if (zclient_socket_connect(zclient_sync) < 0) {
+ zlog_warn("%s: failed connecting synchronous zclient!",
+ __func__);
+ return -1;
+ }
+ /* make socket non-blocking */
+ set_nonblocking(zclient_sync->sock);
+
+ /* Send hello to notify zebra this is a synchronous client */
+ if (zclient_send_hello(zclient_sync) == ZCLIENT_SEND_FAILURE) {
+ zlog_warn("%s: failed sending hello for synchronous zclient!",
+ __func__);
+ close(zclient_sync->sock);
+ zclient_sync->sock = -1;
+ return -1;
+ }
+
+ /* Connect to label manager */
+ if (lm_label_manager_connect(zclient_sync, 0) != 0) {
+ zlog_warn("%s: failed connecting to label manager!", __func__);
+ if (zclient_sync->sock > 0) {
+ close(zclient_sync->sock);
+ zclient_sync->sock = -1;
+ }
+ return -1;
+ }
+
+ sr_debug("ISIS-Sr: Successfully connected to the Label Manager");
+
+ return 0;
+}
+
+void isis_zebra_vrf_register(struct isis *isis)
+{
+ if (!zclient || zclient->sock < 0 || !isis)
+ return;
+
+ if (isis->vrf_id != VRF_UNKNOWN) {
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("%s: Register VRF %s id %u", __func__,
+ isis->name, isis->vrf_id);
+ zclient_send_reg_requests(zclient, isis->vrf_id);
+ }
+}
+
+void isis_zebra_vrf_deregister(struct isis *isis)
+{
+ if (!zclient || zclient->sock < 0 || !isis)
+ return;
+
+ if (isis->vrf_id != VRF_UNKNOWN) {
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("%s: Deregister VRF %s id %u", __func__,
+ isis->name, isis->vrf_id);
+ zclient_send_dereg_requests(zclient, isis->vrf_id);
+ }
+}
+
+static void isis_zebra_connected(struct zclient *zclient)
+{
+ zclient_send_reg_requests(zclient, VRF_DEFAULT);
+ zclient_register_opaque(zclient, LDP_RLFA_LABELS);
+ zclient_register_opaque(zclient, LDP_IGP_SYNC_IF_STATE_UPDATE);
+ zclient_register_opaque(zclient, LDP_IGP_SYNC_ANNOUNCE_UPDATE);
+ bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT);
+}
+
+/**
+ * Register / unregister Link State ZAPI Opaque Message
+ *
+ * @param up True to register, false to unregister
+ *
+ * @return 0 if success, -1 otherwise
+ */
+int isis_zebra_ls_register(bool up)
+{
+ int rc;
+
+ if (up)
+ rc = ls_register(zclient, true);
+ else
+ rc = ls_unregister(zclient, true);
+
+ return rc;
+}
+
+/*
+ * opaque messages between processes
+ */
+static int isis_opaque_msg_handler(ZAPI_CALLBACK_ARGS)
+{
+ struct stream *s;
+ struct zapi_opaque_msg info;
+ struct zapi_opaque_reg_info dst;
+ struct ldp_igp_sync_if_state state;
+ struct ldp_igp_sync_announce announce;
+ struct zapi_rlfa_response rlfa;
+ int ret = 0;
+
+ s = zclient->ibuf;
+ if (zclient_opaque_decode(s, &info) != 0)
+ return -1;
+
+ switch (info.type) {
+ case LINK_STATE_SYNC:
+ dst.proto = info.src_proto;
+ dst.instance = info.src_instance;
+ dst.session_id = info.src_session_id;
+ dst.type = LINK_STATE_SYNC;
+ ret = isis_te_sync_ted(dst);
+ break;
+ case LDP_IGP_SYNC_IF_STATE_UPDATE:
+ STREAM_GET(&state, s, sizeof(state));
+ ret = isis_ldp_sync_state_update(state);
+ break;
+ case LDP_IGP_SYNC_ANNOUNCE_UPDATE:
+ STREAM_GET(&announce, s, sizeof(announce));
+ ret = isis_ldp_sync_announce_update(announce);
+ break;
+ case LDP_RLFA_LABELS:
+ STREAM_GET(&rlfa, s, sizeof(rlfa));
+ isis_rlfa_process_ldp_response(&rlfa);
+ break;
+ default:
+ break;
+ }
+
+stream_failure:
+
+ return ret;
+}
+
+static int isis_zebra_client_close_notify(ZAPI_CALLBACK_ARGS)
+{
+ int ret = 0;
+
+ struct zapi_client_close_info info;
+
+ if (zapi_client_close_notify_decode(zclient->ibuf, &info) < 0)
+ return -1;
+
+ isis_ldp_sync_handle_client_close(&info);
+ isis_ldp_rlfa_handle_client_close(&info);
+
+ return ret;
+}
+
+/**
+ * Send SRv6 SID to ZEBRA for installation or deletion.
+ *
+ * @param cmd ZEBRA_ROUTE_ADD or ZEBRA_ROUTE_DELETE
+ * @param sid SRv6 SID to install or delete
+ * @param prefixlen Prefix length
+ * @param oif Outgoing interface
+ * @param action SID action
+ * @param context SID context
+ */
+static void isis_zebra_send_localsid(int cmd, const struct in6_addr *sid,
+ uint16_t prefixlen, ifindex_t oif,
+ enum seg6local_action_t action,
+ const struct seg6local_context *context)
+{
+ struct prefix_ipv6 p = {};
+ struct zapi_route api = {};
+ struct zapi_nexthop *znh;
+
+ if (cmd != ZEBRA_ROUTE_ADD && cmd != ZEBRA_ROUTE_DELETE) {
+ flog_warn(EC_LIB_DEVELOPMENT, "%s: wrong ZEBRA command",
+ __func__);
+ return;
+ }
+
+ if (prefixlen > IPV6_MAX_BITLEN) {
+ flog_warn(EC_LIB_DEVELOPMENT, "%s: wrong prefixlen %u",
+ __func__, prefixlen);
+ return;
+ }
+
+ sr_debug(" |- %s SRv6 SID %pI6 behavior %s",
+ cmd == ZEBRA_ROUTE_ADD ? "Add" : "Delete", sid,
+ seg6local_action2str(action));
+
+ p.family = AF_INET6;
+ p.prefixlen = prefixlen;
+ p.prefix = *sid;
+
+ api.vrf_id = VRF_DEFAULT;
+ api.type = PROTO_TYPE;
+ api.instance = 0;
+ api.safi = SAFI_UNICAST;
+ memcpy(&api.prefix, &p, sizeof(p));
+
+ if (cmd == ZEBRA_ROUTE_DELETE)
+ return (void)zclient_route_send(ZEBRA_ROUTE_DELETE, zclient,
+ &api);
+
+ SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION);
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+
+ znh = &api.nexthops[0];
+
+ memset(znh, 0, sizeof(*znh));
+
+ znh->type = NEXTHOP_TYPE_IFINDEX;
+ znh->ifindex = oif;
+ SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_SEG6LOCAL);
+ znh->seg6local_action = action;
+ memcpy(&znh->seg6local_ctx, context, sizeof(struct seg6local_context));
+
+ api.nexthop_num = 1;
+
+ zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api);
+}
+
+/**
+ * Install SRv6 SID in the forwarding plane through Zebra.
+ *
+ * @param area IS-IS area
+ * @param sid SRv6 SID
+ */
+void isis_zebra_srv6_sid_install(struct isis_area *area,
+ struct isis_srv6_sid *sid)
+{
+ enum seg6local_action_t action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC;
+ uint16_t prefixlen = IPV6_MAX_BITLEN;
+ struct seg6local_context ctx = {};
+ struct interface *ifp;
+
+ if (!area || !sid)
+ return;
+
+ sr_debug("ISIS-SRv6 (%s): setting SRv6 SID %pI6", area->area_tag,
+ &sid->sid);
+
+ switch (sid->behavior) {
+ case SRV6_ENDPOINT_BEHAVIOR_END:
+ action = ZEBRA_SEG6_LOCAL_ACTION_END;
+ prefixlen = IPV6_MAX_BITLEN;
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID:
+ action = ZEBRA_SEG6_LOCAL_ACTION_END;
+ prefixlen = sid->locator->block_bits_length +
+ sid->locator->node_bits_length;
+ SET_SRV6_FLV_OP(ctx.flv.flv_ops,
+ ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID);
+ ctx.flv.lcblock_len = sid->locator->block_bits_length;
+ ctx.flv.lcnode_func_len = sid->locator->node_bits_length;
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_X:
+ case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID:
+ case SRV6_ENDPOINT_BEHAVIOR_RESERVED:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_OPAQUE:
+ default:
+ zlog_err(
+ "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u",
+ area->area_tag, sid->behavior);
+ return;
+ }
+
+ /* Attach the SID to the SRv6 interface */
+ ifp = if_lookup_by_name(area->srv6db.config.srv6_ifname, VRF_DEFAULT);
+ if (!ifp) {
+ zlog_warn(
+ "Failed to install SRv6 SID %pI6: %s interface not found",
+ &sid->sid, area->srv6db.config.srv6_ifname);
+ return;
+ }
+
+ /* Send the SID to zebra */
+ isis_zebra_send_localsid(ZEBRA_ROUTE_ADD, &sid->sid, prefixlen,
+ ifp->ifindex, action, &ctx);
+}
+
+/**
+ * Uninstall SRv6 SID from the forwarding plane through Zebra.
+ *
+ * @param area IS-IS area
+ * @param sid SRv6 SID
+ */
+void isis_zebra_srv6_sid_uninstall(struct isis_area *area,
+ struct isis_srv6_sid *sid)
+{
+ enum seg6local_action_t action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC;
+ struct interface *ifp;
+ uint16_t prefixlen = IPV6_MAX_BITLEN;
+
+ if (!area || !sid)
+ return;
+
+ sr_debug("ISIS-SRv6 (%s): delete SID %pI6", area->area_tag, &sid->sid);
+
+ switch (sid->behavior) {
+ case SRV6_ENDPOINT_BEHAVIOR_END:
+ prefixlen = IPV6_MAX_BITLEN;
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID:
+ prefixlen = sid->locator->block_bits_length +
+ sid->locator->node_bits_length;
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_RESERVED:
+ case SRV6_ENDPOINT_BEHAVIOR_END_X:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46:
+ case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_OPAQUE:
+ default:
+ zlog_err(
+ "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u",
+ area->area_tag, sid->behavior);
+ return;
+ }
+
+ /* The SID is attached to the SRv6 interface */
+ ifp = if_lookup_by_name(area->srv6db.config.srv6_ifname, VRF_DEFAULT);
+ if (!ifp) {
+ zlog_warn("%s interface not found: nothing to uninstall",
+ area->srv6db.config.srv6_ifname);
+ return;
+ }
+
+ /* Send delete request to zebra */
+ isis_zebra_send_localsid(ZEBRA_ROUTE_DELETE, &sid->sid, prefixlen,
+ ifp->ifindex, action, NULL);
+}
+
+void isis_zebra_srv6_adj_sid_install(struct srv6_adjacency *sra)
+{
+ enum seg6local_action_t action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC;
+ struct seg6local_context ctx = {};
+ uint16_t prefixlen = IPV6_MAX_BITLEN;
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+ struct isis_area *area;
+
+ if (!sra)
+ return;
+
+ circuit = sra->adj->circuit;
+ area = circuit->area;
+
+ sr_debug("ISIS-SRv6 (%s): setting adjacency SID %pI6", area->area_tag,
+ &sra->sid);
+
+ switch (sra->behavior) {
+ case SRV6_ENDPOINT_BEHAVIOR_END_X:
+ action = ZEBRA_SEG6_LOCAL_ACTION_END_X;
+ prefixlen = IPV6_MAX_BITLEN;
+ ctx.nh6 = sra->nexthop;
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID:
+ action = ZEBRA_SEG6_LOCAL_ACTION_END_X;
+ prefixlen = sra->locator->block_bits_length +
+ sra->locator->node_bits_length +
+ sra->locator->function_bits_length;
+ ctx.nh6 = sra->nexthop;
+ SET_SRV6_FLV_OP(ctx.flv.flv_ops,
+ ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID);
+ ctx.flv.lcblock_len = sra->locator->block_bits_length;
+ ctx.flv.lcnode_func_len = sra->locator->node_bits_length;
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_RESERVED:
+ case SRV6_ENDPOINT_BEHAVIOR_END:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46:
+ case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_OPAQUE:
+ default:
+ zlog_err(
+ "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u",
+ area->area_tag, sra->behavior);
+ return;
+ }
+
+ ifp = sra->adj->circuit->interface;
+
+ isis_zebra_send_localsid(ZEBRA_ROUTE_ADD, &sra->sid, prefixlen,
+ ifp->ifindex, action, &ctx);
+}
+
+void isis_zebra_srv6_adj_sid_uninstall(struct srv6_adjacency *sra)
+{
+ enum seg6local_action_t action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC;
+ struct interface *ifp;
+ uint16_t prefixlen = IPV6_MAX_BITLEN;
+ struct isis_circuit *circuit;
+ struct isis_area *area;
+
+ if (!sra)
+ return;
+
+ circuit = sra->adj->circuit;
+ area = circuit->area;
+
+ switch (sra->behavior) {
+ case SRV6_ENDPOINT_BEHAVIOR_END_X:
+ prefixlen = IPV6_MAX_BITLEN;
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID:
+ prefixlen = sra->locator->block_bits_length +
+ sra->locator->node_bits_length +
+ sra->locator->function_bits_length;
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_RESERVED:
+ case SRV6_ENDPOINT_BEHAVIOR_END:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46:
+ case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID:
+ case SRV6_ENDPOINT_BEHAVIOR_OPAQUE:
+ default:
+ zlog_err(
+ "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u",
+ area->area_tag, sra->behavior);
+ return;
+ }
+
+ ifp = sra->adj->circuit->interface;
+
+ sr_debug("ISIS-SRv6 (%s): delete End.X SID %pI6", area->area_tag,
+ &sra->sid);
+
+ isis_zebra_send_localsid(ZEBRA_ROUTE_DELETE, &sra->sid, prefixlen,
+ ifp->ifindex, action, NULL);
+}
+
+/**
+ * Callback to process an SRv6 locator chunk received from SRv6 Manager (zebra).
+ *
+ * @result 0 on success, -1 otherwise
+ */
+static int isis_zebra_process_srv6_locator_chunk(ZAPI_CALLBACK_ARGS)
+{
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+ struct stream *s = NULL;
+ struct listnode *node;
+ struct isis_area *area;
+ struct srv6_locator_chunk *c;
+ struct srv6_locator_chunk *chunk = srv6_locator_chunk_alloc();
+ struct isis_srv6_sid *sid;
+ struct isis_adjacency *adj;
+ enum srv6_endpoint_behavior_codepoint behavior;
+ bool allocated = false;
+
+ if (!isis) {
+ srv6_locator_chunk_free(&chunk);
+ return -1;
+ }
+
+ /* Decode the received zebra message */
+ s = zclient->ibuf;
+ if (zapi_srv6_locator_chunk_decode(s, chunk) < 0) {
+ srv6_locator_chunk_free(&chunk);
+ return -1;
+ }
+
+ sr_debug(
+ "Received SRv6 locator chunk from zebra: name %s, "
+ "prefix %pFX, block_len %u, node_len %u, func_len %u, arg_len %u",
+ chunk->locator_name, &chunk->prefix, chunk->block_bits_length,
+ chunk->node_bits_length, chunk->function_bits_length,
+ chunk->argument_bits_length);
+
+ /* Walk through all areas of the ISIS instance */
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ if (strncmp(area->srv6db.config.srv6_locator_name,
+ chunk->locator_name,
+ sizeof(area->srv6db.config.srv6_locator_name)) != 0)
+ continue;
+
+ for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_locator_chunks,
+ node, c)) {
+ if (!prefix_cmp(&c->prefix, &chunk->prefix)) {
+ srv6_locator_chunk_free(&chunk);
+ return 0;
+ }
+ }
+
+ sr_debug(
+ "SRv6 locator chunk (locator %s, prefix %pFX) assigned to IS-IS area %s",
+ chunk->locator_name, &chunk->prefix, area->area_tag);
+
+ /* Add the SRv6 Locator chunk to the per-area chunks list */
+ listnode_add(area->srv6db.srv6_locator_chunks, chunk);
+
+ /* Decide which behavior to use,depending on the locator type
+ * (i.e. uSID vs classic locator) */
+ behavior = (CHECK_FLAG(chunk->flags, SRV6_LOCATOR_USID))
+ ? SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID
+ : SRV6_ENDPOINT_BEHAVIOR_END;
+
+ /* Allocate new SRv6 End SID */
+ sid = isis_srv6_sid_alloc(area, chunk, behavior, 0);
+ if (!sid)
+ return -1;
+
+ /* Install the new SRv6 End SID in the forwarding plane through
+ * Zebra */
+ isis_zebra_srv6_sid_install(area, sid);
+
+ /* Store the SID */
+ listnode_add(area->srv6db.srv6_sids, sid);
+
+ /* Create SRv6 End.X SIDs from existing IS-IS Adjacencies */
+ for (ALL_LIST_ELEMENTS_RO(area->adjacency_list, node, adj)) {
+ if (adj->ll_ipv6_count > 0)
+ srv6_endx_sid_add(adj);
+ }
+
+ /* Regenerate LSPs to advertise the new locator and the SID */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+
+ allocated = true;
+ break;
+ }
+
+ if (!allocated) {
+ sr_debug("No IS-IS area configured for the locator %s",
+ chunk->locator_name);
+ srv6_locator_chunk_free(&chunk);
+ }
+
+ return 0;
+}
+
+/**
+ * Callback to process an SRv6 locator received from SRv6 Manager (zebra).
+ *
+ * @result 0 on success, -1 otherwise
+ */
+static int isis_zebra_process_srv6_locator_add(ZAPI_CALLBACK_ARGS)
+{
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+ struct srv6_locator loc = {};
+ struct listnode *node;
+ struct isis_area *area;
+
+ if (!isis)
+ return -1;
+
+ /* Decode the SRv6 locator */
+ if (zapi_srv6_locator_decode(zclient->ibuf, &loc) < 0)
+ return -1;
+
+ sr_debug(
+ "New SRv6 locator allocated in zebra: name %s, "
+ "prefix %pFX, block_len %u, node_len %u, func_len %u, arg_len %u",
+ loc.name, &loc.prefix, loc.block_bits_length,
+ loc.node_bits_length, loc.function_bits_length,
+ loc.argument_bits_length);
+
+ /* Lookup on the IS-IS areas */
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ /* If SRv6 is enabled on this area and the configured locator
+ * corresponds to the new locator, then request a chunk from the
+ * locator */
+ if (area->srv6db.config.enabled &&
+ strncmp(area->srv6db.config.srv6_locator_name, loc.name,
+ sizeof(area->srv6db.config.srv6_locator_name)) == 0) {
+ sr_debug(
+ "Sending a request to get a chunk from the SRv6 locator %s (%pFX) "
+ "for IS-IS area %s",
+ loc.name, &loc.prefix, area->area_tag);
+
+ if (isis_zebra_srv6_manager_get_locator_chunk(
+ loc.name) < 0)
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Callback to process a notification from SRv6 Manager (zebra) of an SRv6
+ * locator deleted.
+ *
+ * @result 0 on success, -1 otherwise
+ */
+static int isis_zebra_process_srv6_locator_delete(ZAPI_CALLBACK_ARGS)
+{
+ struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
+ struct srv6_locator loc = {};
+ struct isis_area *area;
+ struct listnode *node, *nnode;
+ struct srv6_locator_chunk *chunk;
+ struct isis_srv6_sid *sid;
+ struct srv6_adjacency *sra;
+
+ if (!isis)
+ return -1;
+
+ /* Decode the received zebra message */
+ if (zapi_srv6_locator_decode(zclient->ibuf, &loc) < 0)
+ return -1;
+
+ sr_debug(
+ "SRv6 locator deleted in zebra: name %s, "
+ "prefix %pFX, block_len %u, node_len %u, func_len %u, arg_len %u",
+ loc.name, &loc.prefix, loc.block_bits_length,
+ loc.node_bits_length, loc.function_bits_length,
+ loc.argument_bits_length);
+
+ /* Walk through all areas of the ISIS instance */
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ if (strncmp(area->srv6db.config.srv6_locator_name, loc.name,
+ sizeof(area->srv6db.config.srv6_locator_name)) != 0)
+ continue;
+
+ /* Delete SRv6 SIDs */
+ for (ALL_LIST_ELEMENTS(area->srv6db.srv6_sids, node, nnode,
+ sid)) {
+
+ sr_debug(
+ "Deleting SRv6 SID (locator %s, sid %pI6) from IS-IS area %s",
+ area->srv6db.config.srv6_locator_name,
+ &sid->sid, area->area_tag);
+
+ /* Uninstall the SRv6 SID from the forwarding plane
+ * through Zebra */
+ isis_zebra_srv6_sid_uninstall(area, sid);
+
+ listnode_delete(area->srv6db.srv6_sids, sid);
+ isis_srv6_sid_free(sid);
+ }
+
+ /* Uninstall all local Adjacency-SIDs. */
+ for (ALL_LIST_ELEMENTS(area->srv6db.srv6_endx_sids, node, nnode,
+ sra))
+ srv6_endx_sid_del(sra);
+
+ /* Free the SRv6 locator chunks */
+ for (ALL_LIST_ELEMENTS(area->srv6db.srv6_locator_chunks, node,
+ nnode, chunk)) {
+ if (prefix_match((struct prefix *)&loc.prefix,
+ (struct prefix *)&chunk->prefix)) {
+ listnode_delete(
+ area->srv6db.srv6_locator_chunks,
+ chunk);
+ srv6_locator_chunk_free(&chunk);
+ }
+ }
+
+ /* Regenerate LSPs to advertise that the locator no longer
+ * exists */
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ }
+
+ return 0;
+}
+
+/**
+ * Request an SRv6 locator chunk to the SRv6 Manager (zebra) asynchronously.
+ *
+ * @param locator_name Name of SRv6 locator
+ *
+ * @result 0 on success, -1 otherwise
+ */
+int isis_zebra_srv6_manager_get_locator_chunk(const char *name)
+{
+ return srv6_manager_get_locator_chunk(zclient, name);
+}
+
+
+/**
+ * Release an SRv6 locator chunk.
+ *
+ * @param locator_name Name of SRv6 locator
+ *
+ * @result 0 on success, -1 otherwise
+ */
+int isis_zebra_srv6_manager_release_locator_chunk(const char *name)
+{
+ return srv6_manager_release_locator_chunk(zclient, name);
+}
+
+static zclient_handler *const isis_handlers[] = {
+ [ZEBRA_ROUTER_ID_UPDATE] = isis_router_id_update_zebra,
+ [ZEBRA_INTERFACE_ADDRESS_ADD] = isis_zebra_if_address_add,
+ [ZEBRA_INTERFACE_ADDRESS_DELETE] = isis_zebra_if_address_del,
+ [ZEBRA_INTERFACE_LINK_PARAMS] = isis_zebra_link_params,
+ [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = isis_zebra_read,
+ [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = isis_zebra_read,
+
+ [ZEBRA_OPAQUE_MESSAGE] = isis_opaque_msg_handler,
+
+ [ZEBRA_CLIENT_CLOSE_NOTIFY] = isis_zebra_client_close_notify,
+
+ [ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK] =
+ isis_zebra_process_srv6_locator_chunk,
+ [ZEBRA_SRV6_LOCATOR_ADD] = isis_zebra_process_srv6_locator_add,
+ [ZEBRA_SRV6_LOCATOR_DELETE] = isis_zebra_process_srv6_locator_delete,
+};
+
+void isis_zebra_init(struct event_loop *master, int instance)
+{
+ /* Initialize asynchronous zclient. */
+ zclient = zclient_new(master, &zclient_options_default, isis_handlers,
+ array_size(isis_handlers));
+ zclient_init(zclient, PROTO_TYPE, 0, &isisd_privs);
+ zclient->zebra_connected = isis_zebra_connected;
+
+ /* Initialize special zclient for synchronous message exchanges. */
+ struct zclient_options options = zclient_options_default;
+ options.synchronous = true;
+ zclient_sync = zclient_new(master, &options, NULL, 0);
+ zclient_sync->sock = -1;
+ zclient_sync->redist_default = ZEBRA_ROUTE_ISIS;
+ zclient_sync->instance = instance;
+ /*
+ * session_id must be different from default value (0) to distinguish
+ * the asynchronous socket from the synchronous one
+ */
+ zclient_sync->session_id = 1;
+ zclient_sync->privs = &isisd_privs;
+}
+
+void isis_zebra_stop(void)
+{
+ zclient_unregister_opaque(zclient, LDP_RLFA_LABELS);
+ zclient_unregister_opaque(zclient, LDP_IGP_SYNC_IF_STATE_UPDATE);
+ zclient_unregister_opaque(zclient, LDP_IGP_SYNC_ANNOUNCE_UPDATE);
+ zclient_stop(zclient_sync);
+ zclient_free(zclient_sync);
+ zclient_stop(zclient);
+ zclient_free(zclient);
+ frr_fini();
+}
diff --git a/isisd/isis_zebra.h b/isisd/isis_zebra.h
new file mode 100644
index 0000000..f1684b7
--- /dev/null
+++ b/isisd/isis_zebra.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isis_zebra.h
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+#ifndef _ZEBRA_ISIS_ZEBRA_H
+#define _ZEBRA_ISIS_ZEBRA_H
+
+#include "isisd.h"
+
+extern struct zclient *zclient;
+
+struct label_chunk {
+ uint32_t start;
+ uint32_t end;
+ uint64_t used_mask;
+};
+#define CHUNK_SIZE 64
+
+void isis_zebra_init(struct event_loop *master, int instance);
+void isis_zebra_stop(void);
+
+struct isis_route_info;
+struct sr_adjacency;
+
+void isis_zebra_route_add_route(struct isis *isis,
+ struct prefix *prefix,
+ struct prefix_ipv6 *src_p,
+ struct isis_route_info *route_info);
+void isis_zebra_route_del_route(struct isis *isis,
+ struct prefix *prefix,
+ struct prefix_ipv6 *src_p,
+ struct isis_route_info *route_info);
+void isis_zebra_prefix_sid_install(struct isis_area *area,
+ struct prefix *prefix,
+ struct isis_sr_psid_info *psid);
+void isis_zebra_prefix_sid_uninstall(struct isis_area *area,
+ struct prefix *prefix,
+ struct isis_route_info *rinfo,
+ struct isis_sr_psid_info *psid);
+void isis_zebra_send_adjacency_sid(int cmd, const struct sr_adjacency *sra);
+int isis_distribute_list_update(int routetype);
+void isis_zebra_redistribute_set(afi_t afi, int type, vrf_id_t vrf_id,
+ uint16_t tableid);
+void isis_zebra_redistribute_unset(afi_t afi, int type, vrf_id_t vrf_id,
+ uint16_t tableid);
+int isis_zebra_rlfa_register(struct isis_spftree *spftree, struct rlfa *rlfa);
+void isis_zebra_rlfa_unregister_all(struct isis_spftree *spftree);
+bool isis_zebra_label_manager_ready(void);
+int isis_zebra_label_manager_connect(void);
+int isis_zebra_request_label_range(uint32_t base, uint32_t chunk_size);
+int isis_zebra_release_label_range(uint32_t start, uint32_t end);
+void isis_zebra_vrf_register(struct isis *isis);
+void isis_zebra_vrf_deregister(struct isis *isis);
+int isis_zebra_ls_register(bool up);
+
+extern void isis_zebra_srv6_sid_install(struct isis_area *area,
+ struct isis_srv6_sid *sid);
+extern void isis_zebra_srv6_sid_uninstall(struct isis_area *area,
+ struct isis_srv6_sid *sid);
+
+void isis_zebra_srv6_adj_sid_install(struct srv6_adjacency *sra);
+void isis_zebra_srv6_adj_sid_uninstall(struct srv6_adjacency *sra);
+
+extern int isis_zebra_srv6_manager_get_locator_chunk(const char *name);
+extern int isis_zebra_srv6_manager_release_locator_chunk(const char *name);
+
+#endif /* _ZEBRA_ISIS_ZEBRA_H */
diff --git a/isisd/isisd.c b/isisd/isisd.c
new file mode 100644
index 0000000..b1064d8
--- /dev/null
+++ b/isisd/isisd.c
@@ -0,0 +1,3968 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isisd.c
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+
+#include "frrevent.h"
+#include "vty.h"
+#include "command.h"
+#include "log.h"
+#include "memory.h"
+#include "time.h"
+#include "linklist.h"
+#include "if.h"
+#include "hash.h"
+#include "filter.h"
+#include "plist.h"
+#include "stream.h"
+#include "prefix.h"
+#include "table.h"
+#include "qobj.h"
+#include "zclient.h"
+#include "vrf.h"
+#include "spf_backoff.h"
+#include "flex_algo.h"
+#include "lib/northbound_cli.h"
+#include "bfd.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_csm.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_dynhn.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_spf.h"
+#include "isisd/isis_route.h"
+#include "isisd/isis_zebra.h"
+#include "isisd/isis_events.h"
+#include "isisd/isis_te.h"
+#include "isisd/isis_mt.h"
+#include "isisd/isis_sr.h"
+#include "isisd/isis_flex_algo.h"
+#include "isisd/fabricd.h"
+#include "isisd/isis_nb.h"
+
+/* For debug statement. */
+unsigned long debug_adj_pkt;
+unsigned long debug_snp_pkt;
+unsigned long debug_update_pkt;
+unsigned long debug_spf_events;
+unsigned long debug_rte_events;
+unsigned long debug_events;
+unsigned long debug_pkt_dump;
+unsigned long debug_lsp_gen;
+unsigned long debug_lsp_sched;
+unsigned long debug_flooding;
+unsigned long debug_bfd;
+unsigned long debug_tx_queue;
+unsigned long debug_sr;
+unsigned long debug_ldp_sync;
+unsigned long debug_lfa;
+unsigned long debug_te;
+
+DEFINE_MGROUP(ISISD, "isisd");
+
+DEFINE_MTYPE_STATIC(ISISD, ISIS, "ISIS process");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_NAME, "ISIS process name");
+DEFINE_MTYPE_STATIC(ISISD, ISIS_AREA, "ISIS area");
+DEFINE_MTYPE(ISISD, ISIS_AREA_ADDR, "ISIS area address");
+DEFINE_MTYPE(ISISD, ISIS_ACL_NAME, "ISIS access-list name");
+DEFINE_MTYPE(ISISD, ISIS_PLIST_NAME, "ISIS prefix-list name");
+
+DEFINE_QOBJ_TYPE(isis_area);
+
+/* ISIS process wide configuration. */
+static struct isis_master isis_master;
+
+/* ISIS process wide configuration pointer to export. */
+struct isis_master *im;
+
+/* ISIS config processing thread */
+struct event *t_isis_cfg;
+
+#ifndef FABRICD
+DEFINE_HOOK(isis_hook_db_overload, (const struct isis_area *area), (area));
+#endif /* ifndef FABRICD */
+
+/*
+ * Prototypes.
+ */
+int isis_area_get(struct vty *, const char *);
+int area_net_title(struct vty *, const char *);
+int area_clear_net_title(struct vty *, const char *);
+int show_isis_interface_common(struct vty *, struct json_object *json,
+ const char *ifname, char, const char *vrf_name,
+ bool all_vrf);
+int show_isis_interface_common_vty(struct vty *, const char *ifname, char,
+ const char *vrf_name, bool all_vrf);
+int show_isis_interface_common_json(struct json_object *json,
+ const char *ifname, char,
+ const char *vrf_name, bool all_vrf);
+int show_isis_neighbor_common(struct vty *, struct json_object *json,
+ const char *id, char, const char *vrf_name,
+ bool all_vrf);
+int clear_isis_neighbor_common(struct vty *, const char *id,
+ const char *vrf_name, bool all_vrf);
+
+/* Link ISIS instance to VRF. */
+void isis_vrf_link(struct isis *isis, struct vrf *vrf)
+{
+ isis->vrf_id = vrf->vrf_id;
+ if (vrf->info != (void *)isis)
+ vrf->info = (void *)isis;
+}
+
+/* Unlink ISIS instance to VRF. */
+void isis_vrf_unlink(struct isis *isis, struct vrf *vrf)
+{
+ if (vrf->info == (void *)isis)
+ vrf->info = NULL;
+ isis->vrf_id = VRF_UNKNOWN;
+}
+
+struct isis *isis_lookup_by_vrfid(vrf_id_t vrf_id)
+{
+ struct isis *isis;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
+ if (isis->vrf_id == vrf_id)
+ return isis;
+
+ return NULL;
+}
+
+struct isis *isis_lookup_by_vrfname(const char *vrfname)
+{
+ struct isis *isis;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
+ if (isis->name && vrfname && strcmp(isis->name, vrfname) == 0)
+ return isis;
+
+ return NULL;
+}
+
+struct isis *isis_lookup_by_sysid(const uint8_t *sysid)
+{
+ struct isis *isis;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
+ if (!memcmp(isis->sysid, sysid, ISIS_SYS_ID_LEN))
+ return isis;
+
+ return NULL;
+}
+
+void isis_master_init(struct event_loop *master)
+{
+ memset(&isis_master, 0, sizeof(isis_master));
+ im = &isis_master;
+ im->isis = list_new();
+ im->master = master;
+}
+
+struct isis *isis_new(const char *vrf_name)
+{
+ struct vrf *vrf;
+ struct isis *isis;
+
+ isis = XCALLOC(MTYPE_ISIS, sizeof(struct isis));
+
+ isis->name = XSTRDUP(MTYPE_ISIS_NAME, vrf_name);
+
+ vrf = vrf_lookup_by_name(vrf_name);
+
+ if (vrf)
+ isis_vrf_link(isis, vrf);
+ else
+ isis->vrf_id = VRF_UNKNOWN;
+
+ isis_zebra_vrf_register(isis);
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug(
+ "%s: Create new isis instance with vrf_name %s vrf_id %u",
+ __func__, isis->name, isis->vrf_id);
+
+ /*
+ * Default values
+ */
+ isis->max_area_addrs = ISIS_DEFAULT_MAX_AREA_ADDRESSES;
+ isis->process_id = getpid();
+ isis->router_id = 0;
+ isis->area_list = list_new();
+ isis->uptime = time(NULL);
+ isis->snmp_notifications = 1;
+ dyn_cache_init(isis);
+
+ listnode_add(im->isis, isis);
+
+ return isis;
+}
+
+void isis_finish(struct isis *isis)
+{
+ struct isis_area *area;
+ struct listnode *node, *nnode;
+
+ for (ALL_LIST_ELEMENTS(isis->area_list, node, nnode, area))
+ isis_area_destroy(area);
+
+ struct vrf *vrf = NULL;
+
+ listnode_delete(im->isis, isis);
+
+ isis_zebra_vrf_deregister(isis);
+
+ vrf = vrf_lookup_by_name(isis->name);
+ if (vrf)
+ isis_vrf_unlink(isis, vrf);
+ XFREE(MTYPE_ISIS_NAME, isis->name);
+
+ isis_redist_free(isis);
+ list_delete(&isis->area_list);
+ dyn_cache_finish(isis);
+ XFREE(MTYPE_ISIS, isis);
+}
+
+void isis_area_add_circuit(struct isis_area *area, struct isis_circuit *circuit)
+{
+ isis_csm_state_change(ISIS_ENABLE, circuit, area);
+
+ area->ip_circuits += circuit->ip_router;
+ area->ipv6_circuits += circuit->ipv6_router;
+
+ area->lfa_protected_links[0] += circuit->lfa_protection[0];
+ area->rlfa_protected_links[0] += circuit->rlfa_protection[0];
+ area->tilfa_protected_links[0] += circuit->tilfa_protection[0];
+
+ area->lfa_protected_links[1] += circuit->lfa_protection[1];
+ area->rlfa_protected_links[1] += circuit->rlfa_protection[1];
+ area->tilfa_protected_links[1] += circuit->tilfa_protection[1];
+}
+
+void isis_area_del_circuit(struct isis_area *area, struct isis_circuit *circuit)
+{
+ area->ip_circuits -= circuit->ip_router;
+ area->ipv6_circuits -= circuit->ipv6_router;
+
+ area->lfa_protected_links[0] -= circuit->lfa_protection[0];
+ area->rlfa_protected_links[0] -= circuit->rlfa_protection[0];
+ area->tilfa_protected_links[0] -= circuit->tilfa_protection[0];
+
+ area->lfa_protected_links[1] -= circuit->lfa_protection[1];
+ area->rlfa_protected_links[1] -= circuit->rlfa_protection[1];
+ area->tilfa_protected_links[1] -= circuit->tilfa_protection[1];
+
+ isis_csm_state_change(ISIS_DISABLE, circuit, area);
+}
+
+static void delete_area_addr(void *arg)
+{
+ struct iso_address *addr = (struct iso_address *)arg;
+
+ XFREE(MTYPE_ISIS_AREA_ADDR, addr);
+}
+
+struct isis_area *isis_area_create(const char *area_tag, const char *vrf_name)
+{
+ struct isis_area *area;
+ struct isis *isis = NULL;
+ struct vrf *vrf = NULL;
+ struct interface *ifp;
+ struct isis_circuit *circuit;
+
+ area = XCALLOC(MTYPE_ISIS_AREA, sizeof(struct isis_area));
+
+ if (!vrf_name)
+ vrf_name = VRF_DEFAULT_NAME;
+
+ vrf = vrf_lookup_by_name(vrf_name);
+ isis = isis_lookup_by_vrfname(vrf_name);
+
+ if (isis == NULL)
+ isis = isis_new(vrf_name);
+
+ listnode_add(isis->area_list, area);
+ area->isis = isis;
+
+ /*
+ * Fabricd runs only as level-2.
+ * For IS-IS, the default is level-1-2
+ */
+ if (fabricd)
+ area->is_type = IS_LEVEL_2;
+ else
+ area->is_type = yang_get_default_enum(
+ "/frr-isisd:isis/instance/is-type");
+
+ /*
+ * intialize the databases
+ */
+ if (area->is_type & IS_LEVEL_1)
+ lsp_db_init(&area->lspdb[0]);
+ if (area->is_type & IS_LEVEL_2)
+ lsp_db_init(&area->lspdb[1]);
+
+#ifndef FABRICD
+ /* Flex-Algo */
+ area->flex_algos = flex_algos_alloc(isis_flex_algo_data_alloc,
+ isis_flex_algo_data_free);
+#endif /* ifndef FABRICD */
+
+ spftree_area_init(area);
+
+ area->circuit_list = list_new();
+ area->adjacency_list = list_new();
+ area->area_addrs = list_new();
+ area->area_addrs->del = delete_area_addr;
+
+ if (!CHECK_FLAG(im->options, F_ISIS_UNIT_TEST))
+ event_add_timer(master, lsp_tick, area, 1, &area->t_tick);
+ flags_initialize(&area->flags);
+
+ isis_sr_area_init(area);
+ isis_srv6_area_init(area);
+
+ /*
+ * Default values
+ */
+#ifndef FABRICD
+ enum isis_metric_style default_style;
+
+ area->max_lsp_lifetime[0] = yang_get_default_uint16(
+ "/frr-isisd:isis/instance/lsp/timers/level-1/maximum-lifetime");
+ area->max_lsp_lifetime[1] = yang_get_default_uint16(
+ "/frr-isisd:isis/instance/lsp/timers/level-2/maximum-lifetime");
+ area->lsp_refresh[0] = yang_get_default_uint16(
+ "/frr-isisd:isis/instance/lsp/timers/level-1/refresh-interval");
+ area->lsp_refresh[1] = yang_get_default_uint16(
+ "/frr-isisd:isis/instance/lsp/timers/level-2/refresh-interval");
+ area->lsp_gen_interval[0] = yang_get_default_uint16(
+ "/frr-isisd:isis/instance/lsp/timers/level-1/generation-interval");
+ area->lsp_gen_interval[1] = yang_get_default_uint16(
+ "/frr-isisd:isis/instance/lsp/timers/level-2/generation-interval");
+ area->min_spf_interval[0] = yang_get_default_uint16(
+ "/frr-isisd:isis/instance/spf/minimum-interval/level-1");
+ area->min_spf_interval[1] = yang_get_default_uint16(
+ "/frr-isisd:isis/instance/spf/minimum-interval/level-1");
+ area->dynhostname = yang_get_default_bool(
+ "/frr-isisd:isis/instance/dynamic-hostname");
+ default_style =
+ yang_get_default_enum("/frr-isisd:isis/instance/metric-style");
+ area->oldmetric = default_style == ISIS_WIDE_METRIC ? 0 : 1;
+ area->newmetric = default_style == ISIS_NARROW_METRIC ? 0 : 1;
+ area->lsp_frag_threshold = 90; /* not currently configurable */
+ area->lsp_mtu =
+ yang_get_default_uint16("/frr-isisd:isis/instance/lsp/mtu");
+ area->lfa_load_sharing[0] = yang_get_default_bool(
+ "/frr-isisd:isis/instance/fast-reroute/level-1/lfa/load-sharing");
+ area->lfa_load_sharing[1] = yang_get_default_bool(
+ "/frr-isisd:isis/instance/fast-reroute/level-2/lfa/load-sharing");
+ area->attached_bit_send =
+ yang_get_default_bool("/frr-isisd:isis/instance/attach-send");
+ area->attached_bit_rcv_ignore = yang_get_default_bool(
+ "/frr-isisd:isis/instance/attach-receive-ignore");
+
+#else
+ area->max_lsp_lifetime[0] = DEFAULT_LSP_LIFETIME; /* 1200 */
+ area->max_lsp_lifetime[1] = DEFAULT_LSP_LIFETIME; /* 1200 */
+ area->lsp_refresh[0] = DEFAULT_MAX_LSP_GEN_INTERVAL; /* 900 */
+ area->lsp_refresh[1] = DEFAULT_MAX_LSP_GEN_INTERVAL; /* 900 */
+ area->lsp_gen_interval[0] = DEFAULT_MIN_LSP_GEN_INTERVAL;
+ area->lsp_gen_interval[1] = DEFAULT_MIN_LSP_GEN_INTERVAL;
+ area->min_spf_interval[0] = MINIMUM_SPF_INTERVAL;
+ area->min_spf_interval[1] = MINIMUM_SPF_INTERVAL;
+ area->dynhostname = 1;
+ area->oldmetric = 0;
+ area->newmetric = 1;
+ area->lsp_frag_threshold = 90;
+ area->lsp_mtu = DEFAULT_LSP_MTU;
+ area->lfa_load_sharing[0] = true;
+ area->lfa_load_sharing[1] = true;
+ area->attached_bit_send = true;
+ area->attached_bit_rcv_ignore = false;
+#endif /* ifndef FABRICD */
+ area->lfa_priority_limit[0] = SPF_PREFIX_PRIO_LOW;
+ area->lfa_priority_limit[1] = SPF_PREFIX_PRIO_LOW;
+ isis_lfa_tiebreakers_init(area, ISIS_LEVEL1);
+ isis_lfa_tiebreakers_init(area, ISIS_LEVEL2);
+
+ area_mt_init(area);
+
+ area->area_tag = strdup(area_tag);
+
+ if (fabricd)
+ area->fabricd = fabricd_new(area);
+
+ area->lsp_refresh_arg[0].area = area;
+ area->lsp_refresh_arg[0].level = IS_LEVEL_1;
+ area->lsp_refresh_arg[1].area = area;
+ area->lsp_refresh_arg[1].level = IS_LEVEL_2;
+
+ area->bfd_signalled_down = false;
+ area->bfd_force_spf_refresh = false;
+
+ QOBJ_REG(area, isis_area);
+
+ if (vrf) {
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ if (ifp->ifindex == IFINDEX_INTERNAL)
+ continue;
+
+ circuit = ifp->info;
+ if (circuit && strmatch(circuit->tag, area->area_tag))
+ isis_area_add_circuit(area, circuit);
+ }
+ }
+
+ return area;
+}
+
+struct isis_area *isis_area_lookup_by_vrf(const char *area_tag,
+ const char *vrf_name)
+{
+ struct isis_area *area;
+ struct listnode *node;
+ struct isis *isis = NULL;
+
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis == NULL)
+ return NULL;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area))
+ if (strcmp(area->area_tag, area_tag) == 0)
+ return area;
+
+ return NULL;
+}
+
+struct isis_area *isis_area_lookup(const char *area_tag, vrf_id_t vrf_id)
+{
+ struct isis_area *area;
+ struct listnode *node;
+ struct isis *isis;
+
+ isis = isis_lookup_by_vrfid(vrf_id);
+ if (isis == NULL)
+ return NULL;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area))
+ if ((area->area_tag == NULL && area_tag == NULL)
+ || (area->area_tag && area_tag
+ && strcmp(area->area_tag, area_tag) == 0))
+ return area;
+
+ return NULL;
+}
+
+int isis_area_get(struct vty *vty, const char *area_tag)
+{
+ struct isis_area *area;
+
+ area = isis_area_lookup(area_tag, VRF_DEFAULT);
+
+ if (area) {
+ VTY_PUSH_CONTEXT(ROUTER_NODE, area);
+ return CMD_SUCCESS;
+ }
+
+ area = isis_area_create(area_tag, VRF_DEFAULT_NAME);
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("New IS-IS area instance %s", area->area_tag);
+
+ VTY_PUSH_CONTEXT(ROUTER_NODE, area);
+
+ return CMD_SUCCESS;
+}
+
+void isis_area_destroy(struct isis_area *area)
+{
+ struct listnode *node, *nnode;
+ struct isis_circuit *circuit;
+
+ QOBJ_UNREG(area);
+
+ if (fabricd)
+ fabricd_finish(area->fabricd);
+
+ if (area->circuit_list) {
+ for (ALL_LIST_ELEMENTS(area->circuit_list, node, nnode,
+ circuit))
+ isis_area_del_circuit(area, circuit);
+
+ list_delete(&area->circuit_list);
+ }
+ if (area->flags.free_idcs)
+ list_delete(&area->flags.free_idcs);
+
+ list_delete(&area->adjacency_list);
+
+ lsp_db_fini(&area->lspdb[0]);
+ lsp_db_fini(&area->lspdb[1]);
+
+ /* invalidate and verify to delete all routes from zebra */
+ isis_area_invalidate_routes(area, area->is_type);
+ isis_area_verify_routes(area);
+
+#ifndef FABRICD
+ flex_algos_free(area->flex_algos);
+#endif /* ifndef FABRICD */
+
+ isis_sr_area_term(area);
+ isis_srv6_area_term(area);
+
+ isis_mpls_te_term(area);
+
+ spftree_area_del(area);
+
+ if (area->spf_timer[0])
+ isis_spf_timer_free(EVENT_ARG(area->spf_timer[0]));
+ EVENT_OFF(area->spf_timer[0]);
+ if (area->spf_timer[1])
+ isis_spf_timer_free(EVENT_ARG(area->spf_timer[1]));
+ EVENT_OFF(area->spf_timer[1]);
+
+ spf_backoff_free(area->spf_delay_ietf[0]);
+ spf_backoff_free(area->spf_delay_ietf[1]);
+
+ if (!CHECK_FLAG(im->options, F_ISIS_UNIT_TEST))
+ isis_redist_area_finish(area);
+
+ list_delete(&area->area_addrs);
+
+ for (int i = SPF_PREFIX_PRIO_CRITICAL; i <= SPF_PREFIX_PRIO_MEDIUM;
+ i++) {
+ struct spf_prefix_priority_acl *ppa;
+
+ ppa = &area->spf_prefix_priorities[i];
+ XFREE(MTYPE_ISIS_ACL_NAME, ppa->name);
+ }
+ isis_lfa_tiebreakers_clear(area, ISIS_LEVEL1);
+ isis_lfa_tiebreakers_clear(area, ISIS_LEVEL2);
+
+ EVENT_OFF(area->t_tick);
+ EVENT_OFF(area->t_lsp_refresh[0]);
+ EVENT_OFF(area->t_lsp_refresh[1]);
+ EVENT_OFF(area->t_rlfa_rib_update);
+
+ event_cancel_event(master, area);
+
+ listnode_delete(area->isis->area_list, area);
+
+ free(area->area_tag);
+
+ area_mt_finish(area);
+
+ if (area->rlfa_plist_name[0])
+ XFREE(MTYPE_ISIS_PLIST_NAME, area->rlfa_plist_name[0]);
+ if (area->rlfa_plist_name[1])
+ XFREE(MTYPE_ISIS_PLIST_NAME, area->rlfa_plist_name[1]);
+
+ XFREE(MTYPE_ISIS_AREA, area);
+
+}
+
+/* This is hook function for vrf create called as part of vrf_init */
+static int isis_vrf_new(struct vrf *vrf)
+{
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("%s: VRF Created: %s(%u)", __func__, vrf->name,
+ vrf->vrf_id);
+
+ return 0;
+}
+
+/* This is hook function for vrf delete call as part of vrf_init */
+static int isis_vrf_delete(struct vrf *vrf)
+{
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("%s: VRF Deletion: %s(%u)", __func__, vrf->name,
+ vrf->vrf_id);
+
+ return 0;
+}
+
+static void isis_set_redist_vrf_bitmaps(struct isis *isis, bool set)
+{
+ struct listnode *node, *lnode;
+ struct isis_area *area;
+ int type;
+ int level;
+ int protocol;
+ struct isis_redist *redist;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area))
+ for (protocol = 0; protocol < REDIST_PROTOCOL_COUNT; protocol++)
+ for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++)
+ for (level = 0; level < ISIS_LEVELS; level++) {
+ if (area->redist_settings[protocol][type]
+ [level] == NULL)
+ continue;
+ for (ALL_LIST_ELEMENTS_RO(area->redist_settings
+ [protocol]
+ [type]
+ [level],
+ lnode,
+ redist)) {
+ if (redist->redist == 0)
+ continue;
+ /* This field is actually
+ * controlling transmission of
+ * the IS-IS
+ * routes to Zebra and has
+ * nothing to do with
+ * redistribution,
+ * so skip it. */
+ afi_t afi =
+ afi_for_redist_protocol(
+ protocol);
+
+ if (type == DEFAULT_ROUTE) {
+ if (set)
+ vrf_bitmap_set(
+ &zclient->default_information
+ [afi],
+ isis->vrf_id);
+ else
+ vrf_bitmap_unset(
+ &zclient->default_information
+ [afi],
+ isis->vrf_id);
+ } else {
+ if (set)
+ vrf_bitmap_set(
+ &zclient->redist
+ [afi]
+ [type],
+ isis->vrf_id);
+ else
+ vrf_bitmap_unset(
+ &zclient->redist
+ [afi]
+ [type],
+ isis->vrf_id);
+ }
+ }
+ }
+}
+
+static int isis_vrf_enable(struct vrf *vrf)
+{
+ struct isis *isis;
+ vrf_id_t old_vrf_id;
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("%s: VRF %s id %u enabled", __func__, vrf->name,
+ vrf->vrf_id);
+
+ isis = isis_lookup_by_vrfname(vrf->name);
+ if (isis && isis->vrf_id != vrf->vrf_id) {
+ old_vrf_id = isis->vrf_id;
+ /* We have instance configured, link to VRF and make it "up". */
+ isis_vrf_link(isis, vrf);
+ if (IS_DEBUG_EVENTS)
+ zlog_debug(
+ "%s: isis linked to vrf %s vrf_id %u (old id %u)",
+ __func__, vrf->name, isis->vrf_id, old_vrf_id);
+ /* start zebra redist to us for new vrf */
+ isis_set_redist_vrf_bitmaps(isis, true);
+
+ isis_zebra_vrf_register(isis);
+ }
+
+ return 0;
+}
+
+static int isis_vrf_disable(struct vrf *vrf)
+{
+ struct isis *isis;
+ vrf_id_t old_vrf_id = VRF_UNKNOWN;
+
+ if (vrf->vrf_id == VRF_DEFAULT)
+ return 0;
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("%s: VRF %s id %d disabled.", __func__, vrf->name,
+ vrf->vrf_id);
+ isis = isis_lookup_by_vrfname(vrf->name);
+ if (isis) {
+ old_vrf_id = isis->vrf_id;
+
+ isis_zebra_vrf_deregister(isis);
+
+ isis_set_redist_vrf_bitmaps(isis, false);
+
+ /* We have instance configured, unlink
+ * from VRF and make it "down".
+ */
+ isis_vrf_unlink(isis, vrf);
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("%s: isis old_vrf_id %d unlinked", __func__,
+ old_vrf_id);
+ }
+
+ return 0;
+}
+
+void isis_vrf_init(void)
+{
+ vrf_init(isis_vrf_new, isis_vrf_enable, isis_vrf_disable,
+ isis_vrf_delete);
+
+ vrf_cmd_init(NULL);
+}
+
+void isis_terminate(void)
+{
+ struct isis *isis;
+ struct listnode *node, *nnode;
+
+ bfd_protocol_integration_set_shutdown(true);
+
+ if (listcount(im->isis) == 0)
+ return;
+
+ for (ALL_LIST_ELEMENTS(im->isis, node, nnode, isis))
+ isis_finish(isis);
+}
+
+void isis_filter_update(struct access_list *access)
+{
+ struct isis *isis;
+ struct isis_area *area;
+ struct listnode *node, *anode;
+
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) {
+ for (int i = SPF_PREFIX_PRIO_CRITICAL;
+ i <= SPF_PREFIX_PRIO_MEDIUM; i++) {
+ struct spf_prefix_priority_acl *ppa;
+
+ ppa = &area->spf_prefix_priorities[i];
+ ppa->list_v4 =
+ access_list_lookup(AFI_IP, ppa->name);
+ ppa->list_v6 =
+ access_list_lookup(AFI_IP6, ppa->name);
+ }
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ }
+ }
+}
+
+void isis_prefix_list_update(struct prefix_list *plist)
+{
+ struct isis *isis;
+ struct isis_area *area;
+ struct listnode *node, *anode;
+
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) {
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS;
+ level++) {
+ const char *plist_name =
+ prefix_list_name(plist);
+
+ if (!area->rlfa_plist_name[level - 1])
+ continue;
+
+ if (!strmatch(area->rlfa_plist_name[level - 1],
+ plist_name))
+ continue;
+
+ area->rlfa_plist[level - 1] =
+ prefix_list_lookup(AFI_IP, plist_name);
+ lsp_regenerate_schedule(area, area->is_type, 0);
+ }
+ }
+ }
+}
+
+#ifdef FABRICD
+static void area_set_mt_enabled(struct isis_area *area, uint16_t mtid,
+ bool enabled)
+{
+ struct isis_area_mt_setting *setting;
+
+ setting = area_get_mt_setting(area, mtid);
+ if (setting->enabled != enabled) {
+ setting->enabled = enabled;
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0);
+ }
+}
+
+static void area_set_mt_overload(struct isis_area *area, uint16_t mtid,
+ bool overload)
+{
+ struct isis_area_mt_setting *setting;
+
+ setting = area_get_mt_setting(area, mtid);
+ if (setting->overload != overload) {
+ setting->overload = overload;
+ if (setting->enabled)
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2,
+ 0);
+ }
+}
+#endif /* ifdef FABRICD */
+
+int area_net_title(struct vty *vty, const char *net_title)
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+ struct iso_address *addr;
+ struct iso_address *addrp;
+ struct listnode *node;
+
+ uint8_t buff[255];
+
+ /* We check that we are not over the maximal number of addresses */
+ if (listcount(area->area_addrs) >= area->isis->max_area_addrs) {
+ vty_out(vty,
+ "Maximum of area addresses (%d) already reached \n",
+ area->isis->max_area_addrs);
+ return CMD_ERR_NOTHING_TODO;
+ }
+
+ addr = XMALLOC(MTYPE_ISIS_AREA_ADDR, sizeof(struct iso_address));
+ addr->addr_len = dotformat2buff(buff, net_title);
+ memcpy(addr->area_addr, buff, addr->addr_len);
+#ifdef EXTREME_DEBUG
+ zlog_debug("added area address %s for area %s (address length %d)",
+ net_title, area->area_tag, addr->addr_len);
+#endif /* EXTREME_DEBUG */
+ if (addr->addr_len < ISO_ADDR_MIN || addr->addr_len > ISO_ADDR_SIZE) {
+ vty_out(vty,
+ "area address must be at least 8..20 octets long (%d)\n",
+ addr->addr_len);
+ XFREE(MTYPE_ISIS_AREA_ADDR, addr);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (addr->area_addr[addr->addr_len - 1] != 0) {
+ vty_out(vty,
+ "nsel byte (last byte) in area address must be 0\n");
+ XFREE(MTYPE_ISIS_AREA_ADDR, addr);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (area->isis->sysid_set == 0) {
+ /*
+ * First area address - get the SystemID for this router
+ */
+ memcpy(area->isis->sysid, GETSYSID(addr), ISIS_SYS_ID_LEN);
+ area->isis->sysid_set = 1;
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("Router has SystemID %pSY",
+ area->isis->sysid);
+ } else {
+ /*
+ * Check that the SystemID portions match
+ */
+ if (memcmp(area->isis->sysid, GETSYSID(addr),
+ ISIS_SYS_ID_LEN)) {
+ vty_out(vty,
+ "System ID must not change when defining additional area addresses\n");
+ XFREE(MTYPE_ISIS_AREA_ADDR, addr);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ /* now we see that we don't already have this address */
+ for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) {
+ if ((addrp->addr_len + ISIS_SYS_ID_LEN + ISIS_NSEL_LEN)
+ != (addr->addr_len))
+ continue;
+ if (!memcmp(addrp->area_addr, addr->area_addr,
+ addr->addr_len)) {
+ XFREE(MTYPE_ISIS_AREA_ADDR, addr);
+ return CMD_SUCCESS; /* silent fail */
+ }
+ }
+ }
+
+ /*
+ * Forget the systemID part of the address
+ */
+ addr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN);
+ listnode_add(area->area_addrs, addr);
+
+ /* only now we can safely generate our LSPs for this area */
+ if (listcount(area->area_addrs) > 0) {
+ if (area->is_type & IS_LEVEL_1)
+ lsp_generate(area, IS_LEVEL_1);
+ if (area->is_type & IS_LEVEL_2)
+ lsp_generate(area, IS_LEVEL_2);
+ }
+
+ return CMD_SUCCESS;
+}
+
+int area_clear_net_title(struct vty *vty, const char *net_title)
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+ struct iso_address addr, *addrp = NULL;
+ struct listnode *node;
+ uint8_t buff[255];
+
+ addr.addr_len = dotformat2buff(buff, net_title);
+ if (addr.addr_len < ISO_ADDR_MIN || addr.addr_len > ISO_ADDR_SIZE) {
+ vty_out(vty,
+ "Unsupported area address length %d, should be 8...20 \n",
+ addr.addr_len);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ memcpy(addr.area_addr, buff, (int)addr.addr_len);
+
+ for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp))
+ if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len
+ && !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len))
+ break;
+
+ if (!addrp) {
+ vty_out(vty, "No area address %s for area %s \n", net_title,
+ area->area_tag);
+ return CMD_ERR_NO_MATCH;
+ }
+
+ listnode_delete(area->area_addrs, addrp);
+ XFREE(MTYPE_ISIS_AREA_ADDR, addrp);
+
+ /*
+ * Last area address - reset the SystemID for this router
+ */
+ if (listcount(area->area_addrs) == 0) {
+ memset(area->isis->sysid, 0, ISIS_SYS_ID_LEN);
+ area->isis->sysid_set = 0;
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("Router has no SystemID");
+ }
+
+ return CMD_SUCCESS;
+}
+
+/*
+ * 'show isis interface' command
+ */
+int show_isis_interface_common(struct vty *vty, struct json_object *json,
+ const char *ifname, char detail,
+ const char *vrf_name, bool all_vrf)
+{
+ if (json) {
+ return show_isis_interface_common_json(json, ifname, detail,
+ vrf_name, all_vrf);
+ } else {
+ return show_isis_interface_common_vty(vty, ifname, detail,
+ vrf_name, all_vrf);
+ }
+}
+
+int show_isis_interface_common_json(struct json_object *json,
+ const char *ifname, char detail,
+ const char *vrf_name, bool all_vrf)
+{
+ struct listnode *anode, *cnode, *inode;
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ struct isis *isis;
+ struct json_object *areas_json, *area_json;
+ struct json_object *circuits_json, *circuit_json;
+ if (!im) {
+ // IS-IS Routing Process not enabled
+ json_object_string_add(json, "is-is-routing-process-enabled",
+ "no");
+ return CMD_SUCCESS;
+ }
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
+ areas_json = json_object_new_array();
+ json_object_object_add(json, "areas",
+ areas_json);
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list,
+ anode, area)) {
+ area_json = json_object_new_object();
+ json_object_string_add(
+ area_json, "area",
+ area->area_tag ? area->area_tag
+ : "null");
+ circuits_json = json_object_new_array();
+ json_object_object_add(area_json,
+ "circuits",
+ circuits_json);
+ for (ALL_LIST_ELEMENTS_RO(
+ area->circuit_list, cnode,
+ circuit)) {
+ circuit_json =
+ json_object_new_object();
+ json_object_int_add(
+ circuit_json, "circuit",
+ circuit->circuit_id);
+ if (!ifname)
+ isis_circuit_print_json(
+ circuit,
+ circuit_json,
+ detail);
+ else if (strcmp(circuit->interface->name, ifname) == 0)
+ isis_circuit_print_json(
+ circuit,
+ circuit_json,
+ detail);
+ json_object_array_add(
+ circuits_json,
+ circuit_json);
+ }
+ json_object_array_add(areas_json,
+ area_json);
+ }
+ }
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL) {
+ areas_json = json_object_new_array();
+ json_object_object_add(json, "areas", areas_json);
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode,
+ area)) {
+ area_json = json_object_new_object();
+ json_object_string_add(area_json, "area",
+ area->area_tag
+ ? area->area_tag
+ : "null");
+
+ circuits_json = json_object_new_array();
+ json_object_object_add(area_json, "circuits",
+ circuits_json);
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list,
+ cnode, circuit)) {
+ circuit_json = json_object_new_object();
+ json_object_int_add(
+ circuit_json, "circuit",
+ circuit->circuit_id);
+ if (!ifname)
+ isis_circuit_print_json(
+ circuit, circuit_json,
+ detail);
+ else if (
+ strcmp(circuit->interface->name,
+ ifname) == 0)
+ isis_circuit_print_json(
+ circuit, circuit_json,
+ detail);
+ json_object_array_add(circuits_json,
+ circuit_json);
+ }
+ json_object_array_add(areas_json, area_json);
+ }
+ }
+ }
+ return CMD_SUCCESS;
+}
+
+int show_isis_interface_common_vty(struct vty *vty, const char *ifname,
+ char detail, const char *vrf_name,
+ bool all_vrf)
+{
+ struct listnode *anode, *cnode, *inode;
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ struct isis *isis;
+
+ if (!im) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list,
+ anode, area)) {
+ vty_out(vty, "Area %s:\n",
+ area->area_tag);
+
+ if (detail == ISIS_UI_LEVEL_BRIEF)
+ vty_out(vty,
+ " Interface CircId State Type Level\n");
+
+ for (ALL_LIST_ELEMENTS_RO(
+ area->circuit_list, cnode,
+ circuit))
+ if (!ifname)
+ isis_circuit_print_vty(
+ circuit, vty,
+ detail);
+ else if (strcmp(circuit->interface->name, ifname) == 0)
+ isis_circuit_print_vty(
+ circuit, vty,
+ detail);
+ }
+ }
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode,
+ area)) {
+ vty_out(vty, "Area %s:\n", area->area_tag);
+
+ if (detail == ISIS_UI_LEVEL_BRIEF)
+ vty_out(vty,
+ " Interface CircId State Type Level\n");
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list,
+ cnode, circuit))
+ if (!ifname)
+ isis_circuit_print_vty(
+ circuit, vty, detail);
+ else if (
+ strcmp(circuit->interface->name,
+ ifname) == 0)
+ isis_circuit_print_vty(
+ circuit, vty, detail);
+ }
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_isis_interface,
+ show_isis_interface_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] interface [json]",
+ SHOW_STR
+ PROTO_HELP
+ VRF_CMD_HELP_STR
+ "All VRFs\n"
+ "json output\n"
+ "IS-IS interface\n")
+{
+ int res = CMD_SUCCESS;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx_vrf = 0;
+ bool uj = use_json(argc, argv);
+ json_object *json = NULL;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ if (uj)
+ json = json_object_new_object();
+ res = show_isis_interface_common(vty, json, NULL, ISIS_UI_LEVEL_BRIEF,
+ vrf_name, all_vrf);
+ if (uj)
+ vty_json(vty, json);
+ return res;
+}
+
+DEFUN(show_isis_interface_detail,
+ show_isis_interface_detail_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] interface detail [json]",
+ SHOW_STR
+ PROTO_HELP
+ VRF_CMD_HELP_STR
+ "All VRFs\n"
+ "IS-IS interface\n"
+ "show detailed information\n"
+ "json output\n")
+{
+ int res = CMD_SUCCESS;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx_vrf = 0;
+ bool uj = use_json(argc, argv);
+ json_object *json = NULL;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ if (uj)
+ json = json_object_new_object();
+ res = show_isis_interface_common(vty, json, NULL, ISIS_UI_LEVEL_DETAIL,
+ vrf_name, all_vrf);
+ if (uj)
+ vty_json(vty, json);
+ return res;
+}
+
+DEFUN(show_isis_interface_arg,
+ show_isis_interface_arg_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] interface WORD [json]",
+ SHOW_STR
+ PROTO_HELP
+ VRF_CMD_HELP_STR
+ "All VRFs\n"
+ "IS-IS interface\n"
+ "IS-IS interface name\n"
+ "json output\n")
+{
+ int res = CMD_SUCCESS;
+ int idx_word = 0;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx_vrf = 0;
+ bool uj = use_json(argc, argv);
+ json_object *json = NULL;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ if (uj)
+ json = json_object_new_object();
+
+ char *ifname = argv_find(argv, argc, "WORD", &idx_word)
+ ? argv[idx_word]->arg
+ : NULL;
+ res = show_isis_interface_common(
+ vty, json, ifname, ISIS_UI_LEVEL_DETAIL, vrf_name, all_vrf);
+ if (uj)
+ vty_json(vty, json);
+ return res;
+}
+
+static int id_to_sysid(struct isis *isis, const char *id, uint8_t *sysid)
+{
+ struct isis_dynhn *dynhn;
+
+ memset(sysid, 0, ISIS_SYS_ID_LEN);
+ if (id) {
+ if (sysid2buff(sysid, id) == 0) {
+ dynhn = dynhn_find_by_name(isis, id);
+ if (dynhn == NULL)
+ return -1;
+ memcpy(sysid, dynhn->id, ISIS_SYS_ID_LEN);
+ }
+ }
+
+ return 0;
+}
+
+static void isis_neighbor_common_json(struct json_object *json, const char *id,
+ char detail, struct isis *isis,
+ uint8_t *sysid)
+{
+ struct listnode *anode, *cnode, *node;
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ struct list *adjdb;
+ struct isis_adjacency *adj;
+ struct json_object *areas_json, *area_json;
+ struct json_object *circuits_json, *circuit_json;
+ int i;
+
+ areas_json = json_object_new_array();
+ json_object_object_add(json, "areas", areas_json);
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) {
+ area_json = json_object_new_object();
+ json_object_string_add(area_json, "area",
+ area->area_tag ? area->area_tag
+ : "null");
+ circuits_json = json_object_new_array();
+ json_object_object_add(area_json, "circuits", circuits_json);
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) {
+ circuit_json = json_object_new_object();
+ json_object_int_add(circuit_json, "circuit",
+ circuit->circuit_id);
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ for (i = 0; i < 2; i++) {
+ adjdb = circuit->u.bc.adjdb[i];
+ if (adjdb && adjdb->count) {
+ for (ALL_LIST_ELEMENTS_RO(
+ adjdb, node, adj))
+ if (!id ||
+ !memcmp(adj->sysid,
+ sysid,
+ ISIS_SYS_ID_LEN))
+ isis_adj_print_json(
+ adj,
+ circuit_json,
+ detail);
+ }
+ }
+ } else if (circuit->circ_type == CIRCUIT_T_P2P &&
+ circuit->u.p2p.neighbor) {
+ adj = circuit->u.p2p.neighbor;
+ if (!id ||
+ !memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN))
+ isis_adj_print_json(adj, circuit_json,
+ detail);
+ }
+ json_object_array_add(circuits_json, circuit_json);
+ }
+ json_object_array_add(areas_json, area_json);
+ }
+}
+
+static void isis_neighbor_common_vty(struct vty *vty, const char *id,
+ char detail, struct isis *isis,
+ uint8_t *sysid)
+{
+ struct listnode *anode, *cnode, *node;
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ struct list *adjdb;
+ struct isis_adjacency *adj;
+ int i;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) {
+ vty_out(vty, "Area %s:\n", area->area_tag);
+
+ if (detail == ISIS_UI_LEVEL_BRIEF)
+ vty_out(vty,
+ " System Id Interface L State Holdtime SNPA\n");
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) {
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ for (i = 0; i < 2; i++) {
+ adjdb = circuit->u.bc.adjdb[i];
+ if (adjdb && adjdb->count) {
+ for (ALL_LIST_ELEMENTS_RO(
+ adjdb, node, adj))
+ if (!id ||
+ !memcmp(adj->sysid,
+ sysid,
+ ISIS_SYS_ID_LEN))
+ isis_adj_print_vty(
+ adj,
+ vty,
+ detail);
+ }
+ }
+ } else if (circuit->circ_type == CIRCUIT_T_P2P &&
+ circuit->u.p2p.neighbor) {
+ adj = circuit->u.p2p.neighbor;
+ if (!id ||
+ !memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN))
+ isis_adj_print_vty(adj, vty, detail);
+ }
+ }
+ }
+}
+
+static void isis_neighbor_common(struct vty *vty, struct json_object *json,
+ const char *id, char detail, struct isis *isis,
+ uint8_t *sysid)
+{
+ if (json) {
+ isis_neighbor_common_json(json, id, detail,isis,sysid);
+ } else {
+ isis_neighbor_common_vty(vty, id, detail,isis,sysid);
+ }
+}
+
+/*
+ * 'show isis neighbor' command
+ */
+
+int show_isis_neighbor_common(struct vty *vty, struct json_object *json,
+ const char *id, char detail, const char *vrf_name,
+ bool all_vrf)
+{
+ struct listnode *node;
+ uint8_t sysid[ISIS_SYS_ID_LEN];
+ struct isis *isis;
+
+ if (!im) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) {
+ if (id_to_sysid(isis, id, sysid)) {
+ vty_out(vty, "Invalid system id %s\n",
+ id);
+ return CMD_SUCCESS;
+ }
+ isis_neighbor_common(vty, json, id, detail,
+ isis, sysid);
+ }
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL) {
+ if (id_to_sysid(isis, id, sysid)) {
+ vty_out(vty, "Invalid system id %s\n", id);
+ return CMD_SUCCESS;
+ }
+ isis_neighbor_common(vty, json, id, detail, isis,
+ sysid);
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void isis_neighbor_common_clear(struct vty *vty, const char *id,
+ uint8_t *sysid, struct isis *isis)
+{
+ struct listnode *anode, *cnode, *node, *nnode;
+ struct isis_area *area;
+ struct isis_circuit *circuit;
+ struct list *adjdb;
+ struct isis_adjacency *adj;
+ int i;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) {
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) {
+ if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+ for (i = 0; i < 2; i++) {
+ adjdb = circuit->u.bc.adjdb[i];
+ if (adjdb && adjdb->count) {
+ for (ALL_LIST_ELEMENTS(
+ adjdb, node, nnode,
+ adj))
+ if (!id
+ || !memcmp(
+ adj->sysid,
+ sysid,
+ ISIS_SYS_ID_LEN))
+ isis_adj_state_change(
+ &adj,
+ ISIS_ADJ_DOWN,
+ "clear user request");
+ }
+ }
+ } else if (circuit->circ_type == CIRCUIT_T_P2P
+ && circuit->u.p2p.neighbor) {
+ adj = circuit->u.p2p.neighbor;
+ if (!id
+ || !memcmp(adj->sysid, sysid,
+ ISIS_SYS_ID_LEN))
+ isis_adj_state_change(
+ &adj, ISIS_ADJ_DOWN,
+ "clear user request");
+ }
+ }
+ }
+}
+/*
+ * 'clear isis neighbor' command
+ */
+int clear_isis_neighbor_common(struct vty *vty, const char *id, const char *vrf_name,
+ bool all_vrf)
+{
+ struct listnode *node;
+ uint8_t sysid[ISIS_SYS_ID_LEN];
+ struct isis *isis;
+
+ if (!im) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) {
+ if (id_to_sysid(isis, id, sysid)) {
+ vty_out(vty, "Invalid system id %s\n",
+ id);
+ return CMD_SUCCESS;
+ }
+ isis_neighbor_common_clear(vty, id, sysid,
+ isis);
+ }
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL) {
+ if (id_to_sysid(isis, id, sysid)) {
+ vty_out(vty, "Invalid system id %s\n", id);
+ return CMD_SUCCESS;
+ }
+ isis_neighbor_common_clear(vty, id, sysid, isis);
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_isis_neighbor,
+ show_isis_neighbor_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] neighbor [json]",
+ SHOW_STR
+ PROTO_HELP
+ VRF_CMD_HELP_STR
+ "All vrfs\n"
+ "IS-IS neighbor adjacencies\n"
+ "json output\n")
+{
+ int res = CMD_SUCCESS;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx_vrf = 0;
+ bool uj = use_json(argc, argv);
+ json_object *json = NULL;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ if (uj)
+ json = json_object_new_object();
+ res = show_isis_neighbor_common(vty, json, NULL, ISIS_UI_LEVEL_BRIEF,
+ vrf_name, all_vrf);
+ if (uj)
+ vty_json(vty, json);
+ return res;
+}
+
+DEFUN(show_isis_neighbor_detail,
+ show_isis_neighbor_detail_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] neighbor detail [json]",
+ SHOW_STR
+ PROTO_HELP
+ VRF_CMD_HELP_STR
+ "all vrfs\n"
+ "IS-IS neighbor adjacencies\n"
+ "show detailed information\n"
+ "json output\n")
+{
+ int res = CMD_SUCCESS;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx_vrf = 0;
+ bool uj = use_json(argc, argv);
+ json_object *json = NULL;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ if (uj)
+ json = json_object_new_object();
+
+ res = show_isis_neighbor_common(vty, json, NULL, ISIS_UI_LEVEL_DETAIL,
+ vrf_name, all_vrf);
+ if (uj)
+ vty_json(vty, json);
+ return res;
+}
+
+DEFUN(show_isis_neighbor_arg,
+ show_isis_neighbor_arg_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] neighbor WORD [json]",
+ SHOW_STR
+ PROTO_HELP
+ VRF_CMD_HELP_STR
+ "All vrfs\n"
+ "IS-IS neighbor adjacencies\n"
+ "System id\n"
+ "json output\n")
+{
+ int res = CMD_SUCCESS;
+ int idx_word = 0;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx_vrf = 0;
+ bool uj = use_json(argc, argv);
+ json_object *json = NULL;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ if (uj)
+ json = json_object_new_object();
+ char *id = argv_find(argv, argc, "WORD", &idx_word)
+ ? argv[idx_word]->arg
+ : NULL;
+
+ res = show_isis_neighbor_common(vty, json, id, ISIS_UI_LEVEL_DETAIL,
+ vrf_name, all_vrf);
+ if (uj)
+ vty_json(vty, json);
+ return res;
+}
+
+DEFUN(clear_isis_neighbor,
+ clear_isis_neighbor_cmd,
+ "clear " PROTO_NAME " [vrf <NAME|all>] neighbor",
+ CLEAR_STR
+ PROTO_HELP
+ VRF_CMD_HELP_STR
+ "All vrfs\n"
+ "IS-IS neighbor adjacencies\n")
+{
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx_vrf = 0;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ return clear_isis_neighbor_common(vty, NULL, vrf_name, all_vrf);
+}
+
+DEFUN(clear_isis_neighbor_arg,
+ clear_isis_neighbor_arg_cmd,
+ "clear " PROTO_NAME " [vrf <NAME|all>] neighbor WORD",
+ CLEAR_STR
+ PROTO_HELP
+ VRF_CMD_HELP_STR
+ "All vrfs\n"
+ "IS-IS neighbor adjacencies\n"
+ "System id\n")
+{
+ int idx_word = 0;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx_vrf = 0;
+
+ char *id = argv_find(argv, argc, "WORD", &idx_word)
+ ? argv[idx_word]->arg
+ : NULL;
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ return clear_isis_neighbor_common(vty, id, vrf_name, all_vrf);
+}
+
+/*
+ * 'isis debug', 'show debugging'
+ */
+void print_debug(struct vty *vty, int flags, int onoff)
+{
+ const char *onoffs = onoff ? "on" : "off";
+
+ if (flags & DEBUG_ADJ_PACKETS)
+ vty_out(vty,
+ "IS-IS Adjacency related packets debugging is %s\n",
+ onoffs);
+ if (flags & DEBUG_TX_QUEUE)
+ vty_out(vty, "IS-IS TX queue debugging is %s\n",
+ onoffs);
+ if (flags & DEBUG_SNP_PACKETS)
+ vty_out(vty, "IS-IS CSNP/PSNP packets debugging is %s\n",
+ onoffs);
+ if (flags & DEBUG_SPF_EVENTS)
+ vty_out(vty, "IS-IS SPF events debugging is %s\n", onoffs);
+ if (flags & DEBUG_SR)
+ vty_out(vty, "IS-IS Segment Routing events debugging is %s\n",
+ onoffs);
+ if (flags & DEBUG_TE)
+ vty_out(vty,
+ "IS-IS Traffic Engineering events debugging is %s\n",
+ onoffs);
+ if (flags & DEBUG_LFA)
+ vty_out(vty, "IS-IS LFA events debugging is %s\n", onoffs);
+ if (flags & DEBUG_UPDATE_PACKETS)
+ vty_out(vty, "IS-IS Update related packet debugging is %s\n",
+ onoffs);
+ if (flags & DEBUG_RTE_EVENTS)
+ vty_out(vty, "IS-IS Route related debugging is %s\n", onoffs);
+ if (flags & DEBUG_EVENTS)
+ vty_out(vty, "IS-IS Event debugging is %s\n", onoffs);
+ if (flags & DEBUG_PACKET_DUMP)
+ vty_out(vty, "IS-IS Packet dump debugging is %s\n", onoffs);
+ if (flags & DEBUG_LSP_GEN)
+ vty_out(vty, "IS-IS LSP generation debugging is %s\n", onoffs);
+ if (flags & DEBUG_LSP_SCHED)
+ vty_out(vty, "IS-IS LSP scheduling debugging is %s\n", onoffs);
+ if (flags & DEBUG_FLOODING)
+ vty_out(vty, "IS-IS Flooding debugging is %s\n", onoffs);
+ if (flags & DEBUG_BFD)
+ vty_out(vty, "IS-IS BFD debugging is %s\n", onoffs);
+ if (flags & DEBUG_LDP_SYNC)
+ vty_out(vty, "IS-IS ldp-sync debugging is %s\n", onoffs);
+}
+
+DEFUN_NOSH (show_debugging,
+ show_debugging_isis_cmd,
+ "show debugging [" PROTO_NAME "]",
+ SHOW_STR
+ "State of each debugging option\n"
+ PROTO_HELP)
+{
+ vty_out(vty, PROTO_NAME " debugging status:\n");
+
+ if (IS_DEBUG_ADJ_PACKETS)
+ print_debug(vty, DEBUG_ADJ_PACKETS, 1);
+ if (IS_DEBUG_TX_QUEUE)
+ print_debug(vty, DEBUG_TX_QUEUE, 1);
+ if (IS_DEBUG_SNP_PACKETS)
+ print_debug(vty, DEBUG_SNP_PACKETS, 1);
+ if (IS_DEBUG_SPF_EVENTS)
+ print_debug(vty, DEBUG_SPF_EVENTS, 1);
+ if (IS_DEBUG_SR)
+ print_debug(vty, DEBUG_SR, 1);
+ if (IS_DEBUG_TE)
+ print_debug(vty, DEBUG_TE, 1);
+ if (IS_DEBUG_UPDATE_PACKETS)
+ print_debug(vty, DEBUG_UPDATE_PACKETS, 1);
+ if (IS_DEBUG_RTE_EVENTS)
+ print_debug(vty, DEBUG_RTE_EVENTS, 1);
+ if (IS_DEBUG_EVENTS)
+ print_debug(vty, DEBUG_EVENTS, 1);
+ if (IS_DEBUG_PACKET_DUMP)
+ print_debug(vty, DEBUG_PACKET_DUMP, 1);
+ if (IS_DEBUG_LSP_GEN)
+ print_debug(vty, DEBUG_LSP_GEN, 1);
+ if (IS_DEBUG_LSP_SCHED)
+ print_debug(vty, DEBUG_LSP_SCHED, 1);
+ if (IS_DEBUG_FLOODING)
+ print_debug(vty, DEBUG_FLOODING, 1);
+ if (IS_DEBUG_BFD)
+ print_debug(vty, DEBUG_BFD, 1);
+ if (IS_DEBUG_LDP_SYNC)
+ print_debug(vty, DEBUG_LDP_SYNC, 1);
+ if (IS_DEBUG_LFA)
+ print_debug(vty, DEBUG_LFA, 1);
+
+ cmd_show_lib_debugs(vty);
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_debug(struct vty *vty);
+/* Debug node. */
+static struct cmd_node debug_node = {
+ .name = "debug",
+ .node = DEBUG_NODE,
+ .prompt = "",
+ .config_write = config_write_debug,
+};
+
+static int config_write_debug(struct vty *vty)
+{
+ int write = 0;
+
+ if (IS_DEBUG_ADJ_PACKETS) {
+ vty_out(vty, "debug " PROTO_NAME " adj-packets\n");
+ write++;
+ }
+ if (IS_DEBUG_TX_QUEUE) {
+ vty_out(vty, "debug " PROTO_NAME " tx-queue\n");
+ write++;
+ }
+ if (IS_DEBUG_SNP_PACKETS) {
+ vty_out(vty, "debug " PROTO_NAME " snp-packets\n");
+ write++;
+ }
+ if (IS_DEBUG_SPF_EVENTS) {
+ vty_out(vty, "debug " PROTO_NAME " spf-events\n");
+ write++;
+ }
+ if (IS_DEBUG_SR) {
+ vty_out(vty, "debug " PROTO_NAME " sr-events\n");
+ write++;
+ }
+ if (IS_DEBUG_TE) {
+ vty_out(vty, "debug " PROTO_NAME " te-events\n");
+ write++;
+ }
+ if (IS_DEBUG_LFA) {
+ vty_out(vty, "debug " PROTO_NAME " lfa\n");
+ write++;
+ }
+ if (IS_DEBUG_UPDATE_PACKETS) {
+ vty_out(vty, "debug " PROTO_NAME " update-packets\n");
+ write++;
+ }
+ if (IS_DEBUG_RTE_EVENTS) {
+ vty_out(vty, "debug " PROTO_NAME " route-events\n");
+ write++;
+ }
+ if (IS_DEBUG_EVENTS) {
+ vty_out(vty, "debug " PROTO_NAME " events\n");
+ write++;
+ }
+ if (IS_DEBUG_PACKET_DUMP) {
+ vty_out(vty, "debug " PROTO_NAME " packet-dump\n");
+ write++;
+ }
+ if (IS_DEBUG_LSP_GEN) {
+ vty_out(vty, "debug " PROTO_NAME " lsp-gen\n");
+ write++;
+ }
+ if (IS_DEBUG_LSP_SCHED) {
+ vty_out(vty, "debug " PROTO_NAME " lsp-sched\n");
+ write++;
+ }
+ if (IS_DEBUG_FLOODING) {
+ vty_out(vty, "debug " PROTO_NAME " flooding\n");
+ write++;
+ }
+ if (IS_DEBUG_BFD) {
+ vty_out(vty, "debug " PROTO_NAME " bfd\n");
+ write++;
+ }
+ if (IS_DEBUG_LDP_SYNC) {
+ vty_out(vty, "debug " PROTO_NAME " ldp-sync\n");
+ write++;
+ }
+ write += spf_backoff_write_config(vty);
+
+ return write;
+}
+
+DEFUN (debug_isis_adj,
+ debug_isis_adj_cmd,
+ "debug " PROTO_NAME " adj-packets",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS Adjacency related packets\n")
+{
+ debug_adj_pkt |= DEBUG_ADJ_PACKETS;
+ print_debug(vty, DEBUG_ADJ_PACKETS, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_adj,
+ no_debug_isis_adj_cmd,
+ "no debug " PROTO_NAME " adj-packets",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS Adjacency related packets\n")
+{
+ debug_adj_pkt &= ~DEBUG_ADJ_PACKETS;
+ print_debug(vty, DEBUG_ADJ_PACKETS, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_tx_queue,
+ debug_isis_tx_queue_cmd,
+ "debug " PROTO_NAME " tx-queue",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS TX queues\n")
+{
+ debug_tx_queue |= DEBUG_TX_QUEUE;
+ print_debug(vty, DEBUG_TX_QUEUE, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_tx_queue,
+ no_debug_isis_tx_queue_cmd,
+ "no debug " PROTO_NAME " tx-queue",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS TX queues\n")
+{
+ debug_tx_queue &= ~DEBUG_TX_QUEUE;
+ print_debug(vty, DEBUG_TX_QUEUE, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_flooding,
+ debug_isis_flooding_cmd,
+ "debug " PROTO_NAME " flooding",
+ DEBUG_STR
+ PROTO_HELP
+ "Flooding algorithm\n")
+{
+ debug_flooding |= DEBUG_FLOODING;
+ print_debug(vty, DEBUG_FLOODING, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_flooding,
+ no_debug_isis_flooding_cmd,
+ "no debug " PROTO_NAME " flooding",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "Flooding algorithm\n")
+{
+ debug_flooding &= ~DEBUG_FLOODING;
+ print_debug(vty, DEBUG_FLOODING, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_snp,
+ debug_isis_snp_cmd,
+ "debug " PROTO_NAME " snp-packets",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS CSNP/PSNP packets\n")
+{
+ debug_snp_pkt |= DEBUG_SNP_PACKETS;
+ print_debug(vty, DEBUG_SNP_PACKETS, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_snp,
+ no_debug_isis_snp_cmd,
+ "no debug " PROTO_NAME " snp-packets",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS CSNP/PSNP packets\n")
+{
+ debug_snp_pkt &= ~DEBUG_SNP_PACKETS;
+ print_debug(vty, DEBUG_SNP_PACKETS, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_upd,
+ debug_isis_upd_cmd,
+ "debug " PROTO_NAME " update-packets",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS Update related packets\n")
+{
+ debug_update_pkt |= DEBUG_UPDATE_PACKETS;
+ print_debug(vty, DEBUG_UPDATE_PACKETS, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_upd,
+ no_debug_isis_upd_cmd,
+ "no debug " PROTO_NAME " update-packets",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS Update related packets\n")
+{
+ debug_update_pkt &= ~DEBUG_UPDATE_PACKETS;
+ print_debug(vty, DEBUG_UPDATE_PACKETS, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_spfevents,
+ debug_isis_spfevents_cmd,
+ "debug " PROTO_NAME " spf-events",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS Shortest Path First Events\n")
+{
+ debug_spf_events |= DEBUG_SPF_EVENTS;
+ print_debug(vty, DEBUG_SPF_EVENTS, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_spfevents,
+ no_debug_isis_spfevents_cmd,
+ "no debug " PROTO_NAME " spf-events",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS Shortest Path First Events\n")
+{
+ debug_spf_events &= ~DEBUG_SPF_EVENTS;
+ print_debug(vty, DEBUG_SPF_EVENTS, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_srevents,
+ debug_isis_srevents_cmd,
+ "debug " PROTO_NAME " sr-events",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS Segment Routing Events\n")
+{
+ debug_sr |= DEBUG_SR;
+ print_debug(vty, DEBUG_SR, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_srevents,
+ no_debug_isis_srevents_cmd,
+ "no debug " PROTO_NAME " sr-events",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS Segment Routing Events\n")
+{
+ debug_sr &= ~DEBUG_SR;
+ print_debug(vty, DEBUG_SR, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_teevents,
+ debug_isis_teevents_cmd,
+ "debug " PROTO_NAME " te-events",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS Traffic Engineering Events\n")
+{
+ debug_te |= DEBUG_TE;
+ print_debug(vty, DEBUG_TE, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_teevents,
+ no_debug_isis_teevents_cmd,
+ "no debug " PROTO_NAME " te-events",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS Traffic Engineering Events\n")
+{
+ debug_te &= ~DEBUG_TE;
+ print_debug(vty, DEBUG_TE, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_lfa,
+ debug_isis_lfa_cmd,
+ "debug " PROTO_NAME " lfa",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS LFA Events\n")
+{
+ debug_lfa |= DEBUG_LFA;
+ print_debug(vty, DEBUG_LFA, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_lfa,
+ no_debug_isis_lfa_cmd,
+ "no debug " PROTO_NAME " lfa",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS LFA Events\n")
+{
+ debug_lfa &= ~DEBUG_LFA;
+ print_debug(vty, DEBUG_LFA, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_rtevents,
+ debug_isis_rtevents_cmd,
+ "debug " PROTO_NAME " route-events",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS Route related events\n")
+{
+ debug_rte_events |= DEBUG_RTE_EVENTS;
+ print_debug(vty, DEBUG_RTE_EVENTS, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_rtevents,
+ no_debug_isis_rtevents_cmd,
+ "no debug " PROTO_NAME " route-events",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS Route related events\n")
+{
+ debug_rte_events &= ~DEBUG_RTE_EVENTS;
+ print_debug(vty, DEBUG_RTE_EVENTS, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_events,
+ debug_isis_events_cmd,
+ "debug " PROTO_NAME " events",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS Events\n")
+{
+ debug_events |= DEBUG_EVENTS;
+ print_debug(vty, DEBUG_EVENTS, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_events,
+ no_debug_isis_events_cmd,
+ "no debug " PROTO_NAME " events",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS Events\n")
+{
+ debug_events &= ~DEBUG_EVENTS;
+ print_debug(vty, DEBUG_EVENTS, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_packet_dump,
+ debug_isis_packet_dump_cmd,
+ "debug " PROTO_NAME " packet-dump",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS packet dump\n")
+{
+ debug_pkt_dump |= DEBUG_PACKET_DUMP;
+ print_debug(vty, DEBUG_PACKET_DUMP, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_packet_dump,
+ no_debug_isis_packet_dump_cmd,
+ "no debug " PROTO_NAME " packet-dump",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS packet dump\n")
+{
+ debug_pkt_dump &= ~DEBUG_PACKET_DUMP;
+ print_debug(vty, DEBUG_PACKET_DUMP, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_lsp_gen,
+ debug_isis_lsp_gen_cmd,
+ "debug " PROTO_NAME " lsp-gen",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS generation of own LSPs\n")
+{
+ debug_lsp_gen |= DEBUG_LSP_GEN;
+ print_debug(vty, DEBUG_LSP_GEN, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_lsp_gen,
+ no_debug_isis_lsp_gen_cmd,
+ "no debug " PROTO_NAME " lsp-gen",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS generation of own LSPs\n")
+{
+ debug_lsp_gen &= ~DEBUG_LSP_GEN;
+ print_debug(vty, DEBUG_LSP_GEN, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_lsp_sched,
+ debug_isis_lsp_sched_cmd,
+ "debug " PROTO_NAME " lsp-sched",
+ DEBUG_STR
+ PROTO_HELP
+ "IS-IS scheduling of LSP generation\n")
+{
+ debug_lsp_sched |= DEBUG_LSP_SCHED;
+ print_debug(vty, DEBUG_LSP_SCHED, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_lsp_sched,
+ no_debug_isis_lsp_sched_cmd,
+ "no debug " PROTO_NAME " lsp-sched",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ "IS-IS scheduling of LSP generation\n")
+{
+ debug_lsp_sched &= ~DEBUG_LSP_SCHED;
+ print_debug(vty, DEBUG_LSP_SCHED, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_isis_bfd,
+ debug_isis_bfd_cmd,
+ "debug " PROTO_NAME " bfd",
+ DEBUG_STR
+ PROTO_HELP
+ PROTO_NAME " interaction with BFD\n")
+{
+ debug_bfd |= DEBUG_BFD;
+ bfd_protocol_integration_set_debug(true);
+ print_debug(vty, DEBUG_BFD, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_isis_bfd,
+ no_debug_isis_bfd_cmd,
+ "no debug " PROTO_NAME " bfd",
+ NO_STR
+ UNDEBUG_STR
+ PROTO_HELP
+ PROTO_NAME " interaction with BFD\n")
+{
+ debug_bfd &= ~DEBUG_BFD;
+ bfd_protocol_integration_set_debug(false);
+ print_debug(vty, DEBUG_BFD, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(debug_isis_ldp_sync, debug_isis_ldp_sync_cmd,
+ "debug " PROTO_NAME " ldp-sync",
+ DEBUG_STR PROTO_HELP PROTO_NAME " interaction with LDP-Sync\n")
+{
+ debug_ldp_sync |= DEBUG_LDP_SYNC;
+ print_debug(vty, DEBUG_LDP_SYNC, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_debug_isis_ldp_sync, no_debug_isis_ldp_sync_cmd,
+ "no debug " PROTO_NAME " ldp-sync",
+ NO_STR UNDEBUG_STR PROTO_HELP PROTO_NAME " interaction with LDP-Sync\n")
+{
+ debug_ldp_sync &= ~DEBUG_LDP_SYNC;
+ print_debug(vty, DEBUG_LDP_SYNC, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_hostname,
+ show_hostname_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] hostname",
+ SHOW_STR PROTO_HELP VRF_CMD_HELP_STR
+ "All VRFs\n"
+ "IS-IS Dynamic hostname mapping\n")
+{
+ struct listnode *node;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int idx_vrf = 0;
+ struct isis *isis;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
+ dynhn_print_all(vty, isis);
+
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL)
+ dynhn_print_all(vty, isis);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void isis_spf_ietf_common(struct vty *vty, struct isis *isis)
+{
+ struct listnode *node;
+ struct isis_area *area;
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+
+ vty_out(vty, "vrf : %s\n", isis->name);
+ vty_out(vty, "Area %s:\n",
+ area->area_tag ? area->area_tag : "null");
+
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) {
+ if ((area->is_type & level) == 0)
+ continue;
+
+ vty_out(vty, " Level-%d:\n", level);
+ vty_out(vty, " SPF delay status: ");
+ if (area->spf_timer[level - 1]) {
+ struct timeval remain = event_timer_remain(
+ area->spf_timer[level - 1]);
+ vty_out(vty, "Pending, due in %lld msec\n",
+ (long long)remain.tv_sec * 1000
+ + remain.tv_usec / 1000);
+ } else {
+ vty_out(vty, "Not scheduled\n");
+ }
+
+ if (area->spf_delay_ietf[level - 1]) {
+ vty_out(vty,
+ " Using draft-ietf-rtgwg-backoff-algo-04\n");
+ spf_backoff_show(
+ area->spf_delay_ietf[level - 1], vty,
+ " ");
+ } else {
+ vty_out(vty, " Using legacy backoff algo\n");
+ }
+ }
+ }
+}
+
+DEFUN(show_isis_spf_ietf, show_isis_spf_ietf_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] spf-delay-ietf",
+ SHOW_STR PROTO_HELP VRF_CMD_HELP_STR
+ "All VRFs\n"
+ "SPF delay IETF information\n")
+{
+ struct listnode *node;
+ struct isis *isis;
+ int idx_vrf = 0;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf)
+
+ if (!im) {
+ vty_out(vty, "ISIS is not running\n");
+ return CMD_SUCCESS;
+ }
+
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
+ isis_spf_ietf_common(vty, isis);
+
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL)
+ isis_spf_ietf_common(vty, isis);
+ }
+
+ return CMD_SUCCESS;
+}
+
+
+static const char *pdu_counter_index_to_name_json(enum pdu_counter_index index)
+{
+ switch (index) {
+ case L1_LAN_HELLO_INDEX:
+ return "l1-iih";
+ case L2_LAN_HELLO_INDEX:
+ return "l2-iih";
+ case P2P_HELLO_INDEX:
+ return "p2p-iih";
+ case L1_LINK_STATE_INDEX:
+ return "l1-lsp";
+ case L2_LINK_STATE_INDEX:
+ return "l2-lsp";
+ case FS_LINK_STATE_INDEX:
+ return "fs-lsp";
+ case L1_COMPLETE_SEQ_NUM_INDEX:
+ return "l1-csnp";
+ case L2_COMPLETE_SEQ_NUM_INDEX:
+ return "l2-csnp";
+ case L1_PARTIAL_SEQ_NUM_INDEX:
+ return "l1-psnp";
+ case L2_PARTIAL_SEQ_NUM_INDEX:
+ return "l2-psnp";
+ case PDU_COUNTER_SIZE:
+ return "???????";
+ }
+
+ assert(!"Reached end of function where we are not expecting to");
+}
+
+static void common_isis_summary_json(struct json_object *json,
+ struct isis *isis)
+{
+ int level;
+ json_object *areas_json, *area_json, *tx_pdu_json, *rx_pdu_json,
+ *levels_json, *level_json;
+ struct listnode *node, *node2;
+ struct isis_area *area;
+ time_t cur;
+ char uptime[MONOTIME_STRLEN];
+ char stier[5];
+
+ json_object_string_add(json, "vrf", isis->name);
+ json_object_int_add(json, "process-id", isis->process_id);
+ if (isis->sysid_set)
+ json_object_string_addf(json, "system-id", "%pSY", isis->sysid);
+
+ cur = time(NULL);
+ cur -= isis->uptime;
+ frrtime_to_interval(cur, uptime, sizeof(uptime));
+ json_object_string_add(json, "up-time", uptime);
+ if (isis->area_list)
+ json_object_int_add(json, "number-areas",
+ isis->area_list->count);
+ areas_json = json_object_new_array();
+ json_object_object_add(json, "areas", areas_json);
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ area_json = json_object_new_object();
+ json_object_string_add(area_json, "area",
+ area->area_tag ? area->area_tag
+ : "null");
+
+
+ if (fabricd) {
+ uint8_t tier = fabricd_tier(area);
+ snprintfrr(stier, sizeof(stier), "%s", &tier);
+ json_object_string_add(area_json, "tier",
+ tier == ISIS_TIER_UNDEFINED
+ ? "undefined"
+ : stier);
+ }
+
+ if (listcount(area->area_addrs) > 0) {
+ struct iso_address *area_addr;
+ for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node2,
+ area_addr))
+ json_object_string_addf(area_json, "net",
+ "%pISl", area_addr);
+ }
+
+ tx_pdu_json = json_object_new_object();
+ json_object_object_add(area_json, "tx-pdu-type", tx_pdu_json);
+ for (int i = 0; i < PDU_COUNTER_SIZE; i++) {
+ if (!area->pdu_tx_counters[i])
+ continue;
+ json_object_int_add(tx_pdu_json,
+ pdu_counter_index_to_name_json(i),
+ area->pdu_tx_counters[i]);
+ }
+ json_object_int_add(tx_pdu_json, "lsp-rxmt",
+ area->lsp_rxmt_count);
+
+ rx_pdu_json = json_object_new_object();
+ json_object_object_add(area_json, "rx-pdu-type", rx_pdu_json);
+ for (int i = 0; i < PDU_COUNTER_SIZE; i++) {
+ if (!area->pdu_rx_counters[i])
+ continue;
+ json_object_int_add(rx_pdu_json,
+ pdu_counter_index_to_name_json(i),
+ area->pdu_rx_counters[i]);
+ }
+
+ levels_json = json_object_new_array();
+ json_object_object_add(area_json, "levels", levels_json);
+ for (level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) {
+ if ((area->is_type & level) == 0)
+ continue;
+ level_json = json_object_new_object();
+ json_object_int_add(level_json, "id", level);
+ json_object_int_add(level_json, "lsp0-regenerated",
+ area->lsp_gen_count[level - 1]);
+ json_object_int_add(level_json, "lsp-purged",
+ area->lsp_purge_count[level - 1]);
+ if (area->spf_timer[level - 1])
+ json_object_string_add(level_json, "spf",
+ "pending");
+ else
+ json_object_string_add(level_json, "spf",
+ "no pending");
+ json_object_int_add(level_json, "minimum-interval",
+ area->min_spf_interval[level - 1]);
+ if (area->spf_delay_ietf[level - 1])
+ json_object_string_add(
+ level_json, "ietf-spf-delay-activated",
+ "not used");
+ if (area->ip_circuits) {
+ isis_spf_print_json(
+ area->spftree[SPFTREE_IPV4][level - 1],
+ level_json);
+ }
+ if (area->ipv6_circuits) {
+ isis_spf_print_json(
+ area->spftree[SPFTREE_IPV6][level - 1],
+ level_json);
+ }
+ json_object_array_add(levels_json, level_json);
+ }
+ json_object_array_add(areas_json, area_json);
+ }
+}
+
+static void common_isis_summary_vty(struct vty *vty, struct isis *isis)
+{
+ struct listnode *node, *node2;
+ struct isis_area *area;
+ int level;
+
+ vty_out(vty, "vrf : %s\n", isis->name);
+ vty_out(vty, "Process Id : %ld\n", isis->process_id);
+ if (isis->sysid_set)
+ vty_out(vty, "System Id : %pSY\n", isis->sysid);
+
+ vty_out(vty, "Up time : ");
+ vty_out_timestr(vty, isis->uptime);
+ vty_out(vty, "\n");
+
+ if (isis->area_list)
+ vty_out(vty, "Number of areas : %d\n", isis->area_list->count);
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ vty_out(vty, "Area %s:\n",
+ area->area_tag ? area->area_tag : "null");
+
+ if (fabricd) {
+ uint8_t tier = fabricd_tier(area);
+ if (tier == ISIS_TIER_UNDEFINED)
+ vty_out(vty, " Tier: undefined\n");
+ else
+ vty_out(vty, " Tier: %hhu\n", tier);
+ }
+
+ if (listcount(area->area_addrs) > 0) {
+ struct iso_address *area_addr;
+ for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node2,
+ area_addr))
+ vty_out(vty, " Net: %pISl\n", area_addr);
+ }
+
+ vty_out(vty, " TX counters per PDU type:\n");
+ pdu_counter_print(vty, " ", area->pdu_tx_counters);
+ vty_out(vty, " LSP RXMT: %" PRIu64 "\n",
+ area->lsp_rxmt_count);
+ vty_out(vty, " RX counters per PDU type:\n");
+ pdu_counter_print(vty, " ", area->pdu_rx_counters);
+
+ vty_out(vty, " Drop counters per PDU type:\n");
+ pdu_counter_print(vty, " ", area->pdu_drop_counters);
+
+ vty_out(vty, " Advertise high metrics: %s\n",
+ area->advertise_high_metrics ? "Enabled" : "Disabled");
+
+ for (level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) {
+ if ((area->is_type & level) == 0)
+ continue;
+
+ vty_out(vty, " Level-%d:\n", level);
+
+ vty_out(vty, " LSP0 regenerated: %" PRIu64 "\n",
+ area->lsp_gen_count[level - 1]);
+
+ vty_out(vty, " LSPs purged: %" PRIu64 "\n",
+ area->lsp_purge_count[level - 1]);
+
+ if (area->spf_timer[level - 1])
+ vty_out(vty, " SPF: (pending)\n");
+ else
+ vty_out(vty, " SPF:\n");
+
+ vty_out(vty, " minimum interval : %d",
+ area->min_spf_interval[level - 1]);
+ if (area->spf_delay_ietf[level - 1])
+ vty_out(vty,
+ " (not used, IETF SPF delay activated)");
+ vty_out(vty, "\n");
+
+ if (area->ip_circuits) {
+ vty_out(vty, " IPv4 route computation:\n");
+ isis_spf_print(
+ area->spftree[SPFTREE_IPV4][level - 1],
+ vty);
+ }
+
+ if (area->ipv6_circuits) {
+ vty_out(vty, " IPv6 route computation:\n");
+ isis_spf_print(
+ area->spftree[SPFTREE_IPV6][level - 1],
+ vty);
+ }
+
+ if (area->ipv6_circuits
+ && isis_area_ipv6_dstsrc_enabled(area)) {
+ vty_out(vty,
+ " IPv6 dst-src route computation:\n");
+ isis_spf_print(area->spftree[SPFTREE_DSTSRC]
+ [level - 1],
+ vty);
+ }
+ }
+ }
+}
+
+static void common_isis_summary(struct vty *vty, struct json_object *json,
+ struct isis *isis)
+{
+ if (json) {
+ common_isis_summary_json(json, isis);
+ } else {
+ common_isis_summary_vty(vty, isis);
+ }
+}
+
+DEFUN(show_isis_summary, show_isis_summary_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] summary [json]",
+ SHOW_STR PROTO_HELP VRF_CMD_HELP_STR
+ "All VRFs\n"
+ "json output\n"
+ "summary\n")
+{
+ struct listnode *node;
+ int idx_vrf = 0;
+ struct isis *isis;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ bool uj = use_json(argc, argv);
+ json_object *json = NULL;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf)
+ if (!im) {
+ vty_out(vty, PROTO_NAME " is not running\n");
+ return CMD_SUCCESS;
+ }
+ if (uj)
+ json = json_object_new_object();
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
+ common_isis_summary(vty, json, isis);
+
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis != NULL)
+ common_isis_summary(vty, json, isis);
+ }
+
+ if (uj)
+ vty_json(vty, json);
+
+ return CMD_SUCCESS;
+}
+
+struct isis_lsp *lsp_for_sysid(struct lspdb_head *head, const char *sysid_str,
+ struct isis *isis)
+{
+ char sysid[255] = {0};
+ uint8_t number[3] = {0};
+ const char *pos;
+ uint8_t lspid[ISIS_SYS_ID_LEN + 2] = {0};
+ struct isis_dynhn *dynhn;
+ struct isis_lsp *lsp = NULL;
+
+ if (!sysid_str)
+ return NULL;
+
+ /*
+ * extract fragment and pseudo id from the string sysid_str
+ * in the forms:
+ * (a) <systemid/hostname>.<pseudo-id>-<framenent> or
+ * (b) <systemid/hostname>.<pseudo-id> or
+ * (c) <systemid/hostname> or
+ * Where systemid is in the form:
+ * xxxx.xxxx.xxxx
+ */
+ strlcpy(sysid, sysid_str, sizeof(sysid));
+
+ if (strlen(sysid_str) > 3) {
+ pos = sysid_str + strlen(sysid_str) - 3;
+ if (strncmp(pos, "-", 1) == 0) {
+ memcpy(number, ++pos, 2);
+ lspid[ISIS_SYS_ID_LEN + 1] =
+ (uint8_t)strtol((char *)number, NULL, 16);
+ pos -= 4;
+ if (strncmp(pos, ".", 1) != 0)
+ return NULL;
+ }
+ if (strncmp(pos, ".", 1) == 0) {
+ memcpy(number, ++pos, 2);
+ lspid[ISIS_SYS_ID_LEN] =
+ (uint8_t)strtol((char *)number, NULL, 16);
+ sysid[pos - sysid_str - 1] = '\0';
+ }
+ }
+
+ /*
+ * Try to find the lsp-id if the sysid_str
+ * is in the form
+ * hostname.<pseudo-id>-<fragment>
+ */
+ if (sysid2buff(lspid, sysid)) {
+ lsp = lsp_search(head, lspid);
+ } else if ((dynhn = dynhn_find_by_name(isis, sysid))) {
+ memcpy(lspid, dynhn->id, ISIS_SYS_ID_LEN);
+ lsp = lsp_search(head, lspid);
+ } else if (strncmp(cmd_hostname_get(), sysid, 15) == 0) {
+ memcpy(lspid, isis->sysid, ISIS_SYS_ID_LEN);
+ lsp = lsp_search(head, lspid);
+ }
+
+ return lsp;
+}
+
+void show_isis_database_lspdb_json(struct json_object *json,
+ struct isis_area *area, int level,
+ struct lspdb_head *lspdb,
+ const char *sysid_str, int ui_level)
+{
+ struct isis_lsp *lsp;
+ int lsp_count;
+
+ if (lspdb_count(lspdb) > 0) {
+ lsp = lsp_for_sysid(lspdb, sysid_str, area->isis);
+
+ if (lsp != NULL || sysid_str == NULL) {
+ json_object_int_add(json, "id", level + 1);
+ }
+
+ if (lsp) {
+ if (ui_level == ISIS_UI_LEVEL_DETAIL)
+ lsp_print_detail(lsp, NULL, json,
+ area->dynhostname, area->isis);
+ else
+ lsp_print_json(lsp, json, area->dynhostname,
+ area->isis);
+ } else if (sysid_str == NULL) {
+ lsp_count =
+ lsp_print_all(NULL, json, lspdb, ui_level,
+ area->dynhostname, area->isis);
+
+ json_object_int_add(json, "count", lsp_count);
+ }
+ }
+}
+void show_isis_database_lspdb_vty(struct vty *vty, struct isis_area *area,
+ int level, struct lspdb_head *lspdb,
+ const char *sysid_str, int ui_level)
+{
+ struct isis_lsp *lsp;
+ int lsp_count;
+
+ if (lspdb_count(lspdb) > 0) {
+ lsp = lsp_for_sysid(lspdb, sysid_str, area->isis);
+
+ if (lsp != NULL || sysid_str == NULL) {
+ vty_out(vty, "IS-IS Level-%d link-state database:\n",
+ level + 1);
+
+ /* print the title in all cases */
+ vty_out(vty,
+ "LSP ID PduLen SeqNumber Chksum Holdtime ATT/P/OL\n");
+ }
+
+ if (lsp) {
+ if (ui_level == ISIS_UI_LEVEL_DETAIL)
+ lsp_print_detail(lsp, vty, NULL,
+ area->dynhostname, area->isis);
+ else
+ lsp_print_vty(lsp, vty, area->dynhostname,
+ area->isis);
+ } else if (sysid_str == NULL) {
+ lsp_count =
+ lsp_print_all(vty, NULL, lspdb, ui_level,
+ area->dynhostname, area->isis);
+
+ vty_out(vty, " %u LSPs\n\n", lsp_count);
+ }
+ }
+}
+
+static void show_isis_database_json(struct json_object *json, const char *sysid_str,
+ int ui_level, struct isis *isis)
+{
+ struct listnode *node;
+ struct isis_area *area;
+ int level;
+ struct json_object *tag_area_json,*area_json, *lsp_json, *area_arr_json, *arr_json;
+
+ if (isis->area_list->count == 0)
+ return;
+
+ area_arr_json = json_object_new_array();
+ json_object_object_add(json, "areas", area_arr_json);
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ area_json = json_object_new_object();
+ tag_area_json = json_object_new_object();
+ json_object_string_add(tag_area_json, "name",
+ area->area_tag ? area->area_tag
+ : "null");
+
+ arr_json = json_object_new_array();
+ json_object_object_add(area_json,"area",tag_area_json);
+ json_object_object_add(area_json,"levels",arr_json);
+ for (level = 0; level < ISIS_LEVELS; level++) {
+ lsp_json = json_object_new_object();
+ show_isis_database_lspdb_json(lsp_json, area, level,
+ &area->lspdb[level],
+ sysid_str, ui_level);
+ json_object_array_add(arr_json, lsp_json);
+ }
+ json_object_array_add(area_arr_json, area_json);
+ }
+}
+
+static void show_isis_database_vty(struct vty *vty, const char *sysid_str,
+ int ui_level, struct isis *isis)
+{
+ struct listnode *node;
+ struct isis_area *area;
+ int level;
+
+ if (isis->area_list->count == 0)
+ return;
+
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ vty_out(vty, "Area %s:\n",
+ area->area_tag ? area->area_tag : "null");
+
+ for (level = 0; level < ISIS_LEVELS; level++)
+ show_isis_database_lspdb_vty(vty, area, level,
+ &area->lspdb[level], sysid_str,
+ ui_level);
+ }
+}
+
+static void show_isis_database_common(struct vty *vty, struct json_object *json, const char *sysid_str,
+ int ui_level, struct isis *isis)
+{
+ if (json) {
+ show_isis_database_json(json, sysid_str, ui_level, isis);
+ } else {
+ show_isis_database_vty(vty, sysid_str, ui_level, isis);
+ }
+}
+
+/*
+ * This function supports following display options:
+ * [ show isis database [detail] ]
+ * [ show isis database <sysid> [detail] ]
+ * [ show isis database <hostname> [detail] ]
+ * [ show isis database <sysid>.<pseudo-id> [detail] ]
+ * [ show isis database <hostname>.<pseudo-id> [detail] ]
+ * [ show isis database <sysid>.<pseudo-id>-<fragment-number> [detail] ]
+ * [ show isis database <hostname>.<pseudo-id>-<fragment-number> [detail] ]
+ * [ show isis database detail <sysid> ]
+ * [ show isis database detail <hostname> ]
+ * [ show isis database detail <sysid>.<pseudo-id> ]
+ * [ show isis database detail <hostname>.<pseudo-id> ]
+ * [ show isis database detail <sysid>.<pseudo-id>-<fragment-number> ]
+ * [ show isis database detail <hostname>.<pseudo-id>-<fragment-number> ]
+ */
+static int show_isis_database(struct vty *vty, struct json_object *json, const char *sysid_str,
+ int ui_level, const char *vrf_name, bool all_vrf)
+{
+ struct listnode *node;
+ struct isis *isis;
+
+ if (vrf_name) {
+ if (all_vrf) {
+ for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
+ show_isis_database_common(vty, json, sysid_str,
+ ui_level, isis);
+
+ return CMD_SUCCESS;
+ }
+ isis = isis_lookup_by_vrfname(vrf_name);
+ if (isis)
+ show_isis_database_common(vty, json, sysid_str,
+ ui_level, isis);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_database, show_database_cmd,
+ "show " PROTO_NAME " [vrf <NAME|all>] database [detail] [WORD] [json]",
+ SHOW_STR PROTO_HELP VRF_CMD_HELP_STR
+ "All VRFs\n"
+ "Link state database\n"
+ "Detailed information\n"
+ "LSP ID\n"
+ "json output\n")
+{
+ int res = CMD_SUCCESS;
+ int idx = 0;
+ int idx_vrf = 0;
+ const char *vrf_name = VRF_DEFAULT_NAME;
+ bool all_vrf = false;
+ int uilevel = argv_find(argv, argc, "detail", &idx)
+ ? ISIS_UI_LEVEL_DETAIL
+ : ISIS_UI_LEVEL_BRIEF;
+ char *id = argv_find(argv, argc, "WORD", &idx) ? argv[idx]->arg : NULL;
+ bool uj = use_json(argc, argv);
+ json_object *json = NULL;
+
+ ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);
+ if (uj)
+ json = json_object_new_object();
+
+ res = show_isis_database(vty, json, id, uilevel, vrf_name, all_vrf);
+ if (uj)
+ vty_json(vty, json);
+ return res;
+}
+
+#ifdef FABRICD
+/*
+ * 'router openfabric' command
+ */
+DEFUN_NOSH (router_openfabric,
+ router_openfabric_cmd,
+ "router openfabric WORD",
+ ROUTER_STR
+ PROTO_HELP
+ "ISO Routing area tag\n")
+{
+ int idx_word = 2;
+ return isis_area_get(vty, argv[idx_word]->arg);
+}
+
+/*
+ *'no router openfabric' command
+ */
+DEFUN (no_router_openfabric,
+ no_router_openfabric_cmd,
+ "no router openfabric WORD",
+ NO_STR
+ ROUTER_STR
+ PROTO_HELP
+ "ISO Routing area tag\n")
+{
+ struct isis_area *area;
+ const char *area_tag;
+ int idx_word = 3;
+
+ area_tag = argv[idx_word]->arg;
+ area = isis_area_lookup(area_tag, VRF_DEFAULT);
+ if (area == NULL) {
+ zlog_warn("%s: could not find area with area-tag %s",
+ __func__, area_tag);
+ return CMD_ERR_NO_MATCH;
+ }
+
+ isis_area_destroy(area);
+ return CMD_SUCCESS;
+}
+#endif /* ifdef FABRICD */
+#ifdef FABRICD
+/*
+ * 'net' command
+ */
+DEFUN (net,
+ net_cmd,
+ "net WORD",
+ "A Network Entity Title for this process (OSI only)\n"
+ "XX.XXXX. ... .XXX.XX Network entity title (NET)\n")
+{
+ int idx_word = 1;
+ return area_net_title(vty, argv[idx_word]->arg);
+}
+
+/*
+ * 'no net' command
+ */
+DEFUN (no_net,
+ no_net_cmd,
+ "no net WORD",
+ NO_STR
+ "A Network Entity Title for this process (OSI only)\n"
+ "XX.XXXX. ... .XXX.XX Network entity title (NET)\n")
+{
+ int idx_word = 2;
+ return area_clear_net_title(vty, argv[idx_word]->arg);
+}
+#endif /* ifdef FABRICD */
+#ifdef FABRICD
+DEFUN (isis_topology,
+ isis_topology_cmd,
+ "topology " ISIS_MT_NAMES " [overload]",
+ "Configure IS-IS topologies\n"
+ ISIS_MT_DESCRIPTIONS
+ "Set overload bit for topology\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ const char *arg = argv[1]->arg;
+ uint16_t mtid = isis_str2mtid(arg);
+
+ if (area->oldmetric) {
+ vty_out(vty,
+ "Multi topology IS-IS can only be used with wide metrics\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (mtid == (uint16_t)-1) {
+ vty_out(vty, "Don't know topology '%s'\n", arg);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ if (mtid == ISIS_MT_IPV4_UNICAST) {
+ vty_out(vty, "Cannot configure IPv4 unicast topology\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ area_set_mt_enabled(area, mtid, true);
+ area_set_mt_overload(area, mtid, (argc == 3));
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_isis_topology,
+ no_isis_topology_cmd,
+ "no topology " ISIS_MT_NAMES " [overload]",
+ NO_STR
+ "Configure IS-IS topologies\n"
+ ISIS_MT_DESCRIPTIONS
+ "Set overload bit for topology\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ const char *arg = argv[2]->arg;
+ uint16_t mtid = isis_str2mtid(arg);
+
+ if (area->oldmetric) {
+ vty_out(vty,
+ "Multi topology IS-IS can only be used with wide metrics\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (mtid == (uint16_t)-1) {
+ vty_out(vty, "Don't know topology '%s'\n", arg);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ if (mtid == ISIS_MT_IPV4_UNICAST) {
+ vty_out(vty, "Cannot configure IPv4 unicast topology\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ area_set_mt_enabled(area, mtid, false);
+ area_set_mt_overload(area, mtid, false);
+ return CMD_SUCCESS;
+}
+#endif /* ifdef FABRICD */
+
+void isis_area_lsp_mtu_set(struct isis_area *area, unsigned int lsp_mtu)
+{
+ area->lsp_mtu = lsp_mtu;
+ lsp_regenerate_schedule(area, IS_LEVEL_1_AND_2, 1);
+}
+
+static int isis_area_passwd_set(struct isis_area *area, int level,
+ uint8_t passwd_type, const char *passwd,
+ uint8_t snp_auth)
+{
+ struct isis_passwd *dest;
+ struct isis_passwd modified;
+ int len;
+
+ assert((level == IS_LEVEL_1) || (level == IS_LEVEL_2));
+ dest = (level == IS_LEVEL_1) ? &area->area_passwd
+ : &area->domain_passwd;
+ memset(&modified, 0, sizeof(modified));
+
+ if (passwd_type != ISIS_PASSWD_TYPE_UNUSED) {
+ if (!passwd)
+ return -1;
+
+ len = strlen(passwd);
+ if (len > 254)
+ return -1;
+
+ modified.len = len;
+ strlcpy((char *)modified.passwd, passwd,
+ sizeof(modified.passwd));
+ modified.type = passwd_type;
+ modified.snp_auth = snp_auth;
+ }
+
+ if (memcmp(&modified, dest, sizeof(modified))) {
+ memcpy(dest, &modified, sizeof(modified));
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1);
+ }
+
+ return 0;
+}
+
+int isis_area_passwd_unset(struct isis_area *area, int level)
+{
+ return isis_area_passwd_set(area, level, ISIS_PASSWD_TYPE_UNUSED, NULL,
+ 0);
+}
+
+int isis_area_passwd_cleartext_set(struct isis_area *area, int level,
+ const char *passwd, uint8_t snp_auth)
+{
+ return isis_area_passwd_set(area, level, ISIS_PASSWD_TYPE_CLEARTXT,
+ passwd, snp_auth);
+}
+
+int isis_area_passwd_hmac_md5_set(struct isis_area *area, int level,
+ const char *passwd, uint8_t snp_auth)
+{
+ return isis_area_passwd_set(area, level, ISIS_PASSWD_TYPE_HMAC_MD5,
+ passwd, snp_auth);
+}
+
+void isis_area_invalidate_routes(struct isis_area *area, int levels)
+{
+#ifndef FABRICD
+ struct flex_algo *fa;
+ struct listnode *node;
+ struct isis_flex_algo_data *data;
+#endif /* ifndef FABRICD */
+
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+ if (!(level & levels))
+ continue;
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
+ isis_spf_invalidate_routes(
+ area->spftree[tree][level - 1]);
+
+#ifndef FABRICD
+ for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos,
+ node, fa)) {
+ data = fa->data;
+ isis_spf_invalidate_routes(
+ data->spftree[tree][level - 1]);
+ }
+#endif /* ifndef FABRICD */
+ }
+ }
+}
+
+void isis_area_verify_routes(struct isis_area *area)
+{
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++)
+ isis_spf_verify_routes(area, area->spftree[tree], tree);
+}
+
+void isis_area_switchover_routes(struct isis_area *area, int family,
+ union g_addr *nexthop_ip, ifindex_t ifindex,
+ int level)
+{
+ int tree;
+
+ /* TODO SPFTREE_DSTSRC */
+ if (family == AF_INET)
+ tree = SPFTREE_IPV4;
+ else if (family == AF_INET6)
+ tree = SPFTREE_IPV6;
+ else
+ return;
+
+ isis_spf_switchover_routes(area, area->spftree[tree], family,
+ nexthop_ip, ifindex, level);
+}
+
+
+static void area_resign_level(struct isis_area *area, int level)
+{
+#ifndef FABRICD
+ struct flex_algo *fa;
+ struct listnode *node;
+ struct isis_flex_algo_data *data;
+#endif /* ifndef FABRICD */
+
+ isis_area_invalidate_routes(area, level);
+ isis_area_verify_routes(area);
+
+ lsp_db_fini(&area->lspdb[level - 1]);
+
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
+ if (area->spftree[tree][level - 1]) {
+ isis_spftree_del(area->spftree[tree][level - 1]);
+ area->spftree[tree][level - 1] = NULL;
+ }
+ }
+
+#ifndef FABRICD
+ for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
+ for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node,
+ fa)) {
+ data = fa->data;
+ if (data->spftree[tree][level - 1]) {
+ isis_spftree_del(
+ data->spftree[tree][level - 1]);
+ data->spftree[tree][level - 1] = NULL;
+ }
+ }
+ }
+#endif /* ifndef FABRICD */
+
+ if (area->spf_timer[level - 1])
+ isis_spf_timer_free(EVENT_ARG(area->spf_timer[level - 1]));
+
+ EVENT_OFF(area->spf_timer[level - 1]);
+
+ sched_debug(
+ "ISIS (%s): Resigned from L%d - canceling LSP regeneration timer.",
+ area->area_tag, level);
+ EVENT_OFF(area->t_lsp_refresh[level - 1]);
+ area->lsp_regenerate_pending[level - 1] = 0;
+}
+
+void isis_area_is_type_set(struct isis_area *area, int is_type)
+{
+ struct listnode *node;
+ struct isis_circuit *circuit;
+
+ if (IS_DEBUG_EVENTS)
+ zlog_debug("ISIS-Evt (%s) system type change %s -> %s",
+ area->area_tag, circuit_t2string(area->is_type),
+ circuit_t2string(is_type));
+
+ if (area->is_type == is_type)
+ return; /* No change */
+
+ switch (area->is_type) {
+ case IS_LEVEL_1:
+ if (is_type == IS_LEVEL_2)
+ area_resign_level(area, IS_LEVEL_1);
+
+ lsp_db_init(&area->lspdb[1]);
+ break;
+
+ case IS_LEVEL_1_AND_2:
+ if (is_type == IS_LEVEL_1)
+ area_resign_level(area, IS_LEVEL_2);
+ else
+ area_resign_level(area, IS_LEVEL_1);
+ break;
+
+ case IS_LEVEL_2:
+ if (is_type == IS_LEVEL_1)
+ area_resign_level(area, IS_LEVEL_2);
+
+ lsp_db_init(&area->lspdb[0]);
+ break;
+
+ default:
+ break;
+ }
+
+ area->is_type = is_type;
+
+ /*
+ * If area's IS type is strict Level-1 or Level-2, override circuit's
+ * IS type. Otherwise use circuit's configured IS type.
+ */
+ if (area->is_type != IS_LEVEL_1_AND_2) {
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit))
+ isis_circuit_is_type_set(circuit, is_type);
+ } else {
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit))
+ isis_circuit_is_type_set(circuit, circuit->is_type_config);
+ }
+
+ spftree_area_init(area);
+
+ if (listcount(area->area_addrs) > 0) {
+ if (is_type & IS_LEVEL_1)
+ lsp_generate(area, IS_LEVEL_1);
+ if (is_type & IS_LEVEL_2)
+ lsp_generate(area, IS_LEVEL_2);
+ }
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1);
+
+ return;
+}
+
+void isis_area_metricstyle_set(struct isis_area *area, bool old_metric,
+ bool new_metric)
+{
+ area->oldmetric = old_metric;
+ area->newmetric = new_metric;
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1);
+}
+
+void isis_area_overload_bit_set(struct isis_area *area, bool overload_bit)
+{
+ char new_overload_bit = overload_bit ? LSPBIT_OL : 0;
+
+ if (new_overload_bit != area->overload_bit) {
+ area->overload_bit = new_overload_bit;
+ if (new_overload_bit) {
+ area->overload_counter++;
+ } else {
+ /* Cancel overload on startup timer if it's running */
+ if (area->t_overload_on_startup_timer) {
+ EVENT_OFF(area->t_overload_on_startup_timer);
+ area->t_overload_on_startup_timer = NULL;
+ }
+ }
+
+#ifndef FABRICD
+ hook_call(isis_hook_db_overload, area);
+#endif /* ifndef FABRICD */
+
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1);
+ }
+#ifndef FABRICD
+ isis_notif_db_overload(area, overload_bit);
+#endif /* ifndef FABRICD */
+}
+
+void isis_area_overload_on_startup_set(struct isis_area *area,
+ uint32_t startup_time)
+{
+ if (area->overload_on_startup_time != startup_time) {
+ area->overload_on_startup_time = startup_time;
+ isis_restart_write_overload_time(area, startup_time);
+ }
+}
+
+void config_end_lsp_generate(struct isis_area *area)
+{
+ if (listcount(area->area_addrs) > 0) {
+ if (CHECK_FLAG(area->is_type, IS_LEVEL_1))
+ lsp_generate(area, IS_LEVEL_1);
+ if (CHECK_FLAG(area->is_type, IS_LEVEL_2))
+ lsp_generate(area, IS_LEVEL_2);
+ }
+}
+
+void isis_area_advertise_high_metrics_set(struct isis_area *area,
+ bool advertise_high_metrics)
+{
+ struct listnode *node;
+ struct isis_circuit *circuit;
+ int max_metric;
+ char xpath[XPATH_MAXLEN];
+ struct lyd_node *dnode;
+ int configured_metric_l1;
+ int configured_metric_l2;
+
+ if (area->advertise_high_metrics == advertise_high_metrics)
+ return;
+
+ if (advertise_high_metrics) {
+ if (area->oldmetric && area->newmetric)
+ max_metric = ISIS_NARROW_METRIC_INFINITY;
+ else if (area->newmetric)
+ max_metric = MAX_WIDE_LINK_METRIC;
+ else
+ max_metric = MAX_NARROW_LINK_METRIC;
+
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
+ isis_circuit_metric_set(circuit, IS_LEVEL_1,
+ max_metric);
+ isis_circuit_metric_set(circuit, IS_LEVEL_2,
+ max_metric);
+ }
+
+ area->advertise_high_metrics = true;
+ } else {
+ area->advertise_high_metrics = false;
+ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
+ /* Get configured metric */
+ snprintf(xpath, XPATH_MAXLEN,
+ "/frr-interface:lib/interface[name='%s']",
+ circuit->interface->name);
+ dnode = yang_dnode_get(running_config->dnode, xpath);
+
+ configured_metric_l1 = yang_dnode_get_uint32(
+ dnode, "./frr-isisd:isis/metric/level-1");
+ configured_metric_l2 = yang_dnode_get_uint32(
+ dnode, "./frr-isisd:isis/metric/level-2");
+
+ isis_circuit_metric_set(circuit, IS_LEVEL_1,
+ configured_metric_l1);
+ isis_circuit_metric_set(circuit, IS_LEVEL_2,
+ configured_metric_l2);
+ }
+ }
+}
+
+/*
+ * Returns the path of the file (non-volatile memory) that contains restart
+ * information.
+ */
+char *isis_restart_filepath(void)
+{
+ static char filepath[MAXPATHLEN];
+ snprintf(filepath, sizeof(filepath), ISISD_RESTART, "");
+ return filepath;
+}
+
+/*
+ * Record in non-volatile memory the overload on startup time.
+ */
+void isis_restart_write_overload_time(struct isis_area *isis_area,
+ uint32_t overload_time)
+{
+ char *filepath;
+ const char *area_name;
+ json_object *json;
+ json_object *json_areas;
+ json_object *json_area;
+
+ filepath = isis_restart_filepath();
+ area_name = isis_area->area_tag;
+
+ json = json_object_from_file(filepath);
+ if (json == NULL)
+ json = json_object_new_object();
+
+ json_object_object_get_ex(json, "areas", &json_areas);
+ if (!json_areas) {
+ json_areas = json_object_new_object();
+ json_object_object_add(json, "areas", json_areas);
+ }
+
+ json_object_object_get_ex(json_areas, area_name, &json_area);
+ if (!json_area) {
+ json_area = json_object_new_object();
+ json_object_object_add(json_areas, area_name, json_area);
+ }
+
+ json_object_int_add(json_area, "overload_time",
+ isis_area->overload_on_startup_time);
+ json_object_to_file_ext(filepath, json, JSON_C_TO_STRING_PRETTY);
+ json_object_free(json);
+}
+
+/*
+ * Fetch from non-volatile memory the overload on startup time.
+ */
+uint32_t isis_restart_read_overload_time(struct isis_area *isis_area)
+{
+ char *filepath;
+ const char *area_name;
+ json_object *json;
+ json_object *json_areas;
+ json_object *json_area;
+ json_object *json_overload_time;
+ uint32_t overload_time = 0;
+
+ filepath = isis_restart_filepath();
+ area_name = isis_area->area_tag;
+
+ json = json_object_from_file(filepath);
+ if (json == NULL)
+ json = json_object_new_object();
+
+ json_object_object_get_ex(json, "areas", &json_areas);
+ if (!json_areas) {
+ json_areas = json_object_new_object();
+ json_object_object_add(json, "areas", json_areas);
+ }
+
+ json_object_object_get_ex(json_areas, area_name, &json_area);
+ if (!json_area) {
+ json_area = json_object_new_object();
+ json_object_object_add(json_areas, area_name, json_area);
+ }
+
+ json_object_object_get_ex(json_area, "overload_time",
+ &json_overload_time);
+ if (json_overload_time) {
+ overload_time = json_object_get_int(json_overload_time);
+ }
+
+ json_object_object_del(json_areas, area_name);
+
+ json_object_to_file_ext(filepath, json, JSON_C_TO_STRING_PRETTY);
+ json_object_free(json);
+
+ return overload_time;
+}
+
+void isis_area_attached_bit_send_set(struct isis_area *area, bool attached_bit)
+{
+
+ if (attached_bit != area->attached_bit_send) {
+ area->attached_bit_send = attached_bit;
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1);
+ }
+}
+
+void isis_area_attached_bit_receive_set(struct isis_area *area,
+ bool attached_bit)
+{
+
+ if (attached_bit != area->attached_bit_rcv_ignore) {
+ area->attached_bit_rcv_ignore = attached_bit;
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1);
+ }
+}
+
+void isis_area_dynhostname_set(struct isis_area *area, bool dynhostname)
+{
+ if (area->dynhostname != dynhostname) {
+ area->dynhostname = dynhostname;
+ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0);
+ }
+}
+
+void isis_area_max_lsp_lifetime_set(struct isis_area *area, int level,
+ uint16_t max_lsp_lifetime)
+{
+ assert((level == IS_LEVEL_1) || (level == IS_LEVEL_2));
+
+ if (area->max_lsp_lifetime[level - 1] == max_lsp_lifetime)
+ return;
+
+ area->max_lsp_lifetime[level - 1] = max_lsp_lifetime;
+ lsp_regenerate_schedule(area, level, 1);
+}
+
+void isis_area_lsp_refresh_set(struct isis_area *area, int level,
+ uint16_t lsp_refresh)
+{
+ assert((level == IS_LEVEL_1) || (level == IS_LEVEL_2));
+
+ if (area->lsp_refresh[level - 1] == lsp_refresh)
+ return;
+
+ area->lsp_refresh[level - 1] = lsp_refresh;
+ lsp_regenerate_schedule(area, level, 1);
+}
+
+#ifdef FABRICD
+DEFUN (log_adj_changes,
+ log_adj_changes_cmd,
+ "log-adjacency-changes",
+ "Log changes in adjacency state\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ area->log_adj_changes = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_log_adj_changes,
+ no_log_adj_changes_cmd,
+ "no log-adjacency-changes",
+ NO_STR
+ "Stop logging changes in adjacency state\n")
+{
+ VTY_DECLVAR_CONTEXT(isis_area, area);
+
+ area->log_adj_changes = 0;
+
+ return CMD_SUCCESS;
+}
+#endif /* ifdef FABRICD */
+#ifdef FABRICD
+/* IS-IS configuration write function */
+static int isis_config_write(struct vty *vty)
+{
+ int write = 0;
+ struct isis_area *area;
+ struct listnode *node, *node2, *inode;
+ struct isis *isis;
+
+ if (!im) {
+ vty_out(vty, "IS-IS Routing Process not enabled\n");
+ return CMD_SUCCESS;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
+ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
+ /* ISIS - Area name */
+ vty_out(vty, "router " PROTO_NAME " %s\n", area->area_tag);
+ write++;
+ /* ISIS - Net */
+ if (listcount(area->area_addrs) > 0) {
+ struct iso_address *area_addr;
+ for (ALL_LIST_ELEMENTS_RO(area->area_addrs,
+ node2, area_addr)) {
+ vty_out(vty, " net %pISl\n", area_addr);
+ write++;
+ }
+ }
+ /* ISIS - Dynamic hostname - Defaults to true so only
+ * display if
+ * false. */
+ if (!area->dynhostname) {
+ vty_out(vty, " no hostname dynamic\n");
+ write++;
+ }
+ /* ISIS - Metric-Style - when true displays wide */
+ if (!fabricd) {
+ if (area->newmetric) {
+ if (!area->oldmetric)
+ vty_out(vty, " metric-style wide\n");
+ else
+ vty_out(vty,
+ " metric-style transition\n");
+ write++;
+ } else {
+ vty_out(vty, " metric-style narrow\n");
+ write++;
+ }
+ }
+ /* ISIS - overload-bit */
+ if (area->overload_bit) {
+ vty_out(vty, " set-overload-bit\n");
+ write++;
+ }
+ /* ISIS - Area is-type (level-1-2 is default) */
+ if (!fabricd) {
+ if (area->is_type == IS_LEVEL_1) {
+ vty_out(vty, " is-type level-1\n");
+ write++;
+ } else if (area->is_type == IS_LEVEL_2) {
+ vty_out(vty, " is-type level-2-only\n");
+ write++;
+ }
+ }
+ write += isis_redist_config_write(vty, area, AF_INET);
+ write += isis_redist_config_write(vty, area, AF_INET6);
+ /* ISIS - Lsp generation interval */
+ if (area->lsp_gen_interval[0]
+ == area->lsp_gen_interval[1]) {
+ if (area->lsp_gen_interval[0]
+ != DEFAULT_MIN_LSP_GEN_INTERVAL) {
+ vty_out(vty, " lsp-gen-interval %d\n",
+ area->lsp_gen_interval[0]);
+ write++;
+ }
+ } else {
+ if (area->lsp_gen_interval[0]
+ != DEFAULT_MIN_LSP_GEN_INTERVAL) {
+ vty_out(vty,
+ " lsp-gen-interval level-1 %d\n",
+ area->lsp_gen_interval[0]);
+ write++;
+ }
+ if (area->lsp_gen_interval[1]
+ != DEFAULT_MIN_LSP_GEN_INTERVAL) {
+ vty_out(vty,
+ " lsp-gen-interval level-2 %d\n",
+ area->lsp_gen_interval[1]);
+ write++;
+ }
+ }
+ /* ISIS - LSP lifetime */
+ if (area->max_lsp_lifetime[0]
+ == area->max_lsp_lifetime[1]) {
+ if (area->max_lsp_lifetime[0]
+ != DEFAULT_LSP_LIFETIME) {
+ vty_out(vty, " max-lsp-lifetime %u\n",
+ area->max_lsp_lifetime[0]);
+ write++;
+ }
+ } else {
+ if (area->max_lsp_lifetime[0]
+ != DEFAULT_LSP_LIFETIME) {
+ vty_out(vty,
+ " max-lsp-lifetime level-1 %u\n",
+ area->max_lsp_lifetime[0]);
+ write++;
+ }
+ if (area->max_lsp_lifetime[1]
+ != DEFAULT_LSP_LIFETIME) {
+ vty_out(vty,
+ " max-lsp-lifetime level-2 %u\n",
+ area->max_lsp_lifetime[1]);
+ write++;
+ }
+ }
+ /* ISIS - LSP refresh interval */
+ if (area->lsp_refresh[0] == area->lsp_refresh[1]) {
+ if (area->lsp_refresh[0]
+ != DEFAULT_MAX_LSP_GEN_INTERVAL) {
+ vty_out(vty,
+ " lsp-refresh-interval %u\n",
+ area->lsp_refresh[0]);
+ write++;
+ }
+ } else {
+ if (area->lsp_refresh[0]
+ != DEFAULT_MAX_LSP_GEN_INTERVAL) {
+ vty_out(vty,
+ " lsp-refresh-interval level-1 %u\n",
+ area->lsp_refresh[0]);
+ write++;
+ }
+ if (area->lsp_refresh[1]
+ != DEFAULT_MAX_LSP_GEN_INTERVAL) {
+ vty_out(vty,
+ " lsp-refresh-interval level-2 %u\n",
+ area->lsp_refresh[1]);
+ write++;
+ }
+ }
+ if (area->lsp_mtu != DEFAULT_LSP_MTU) {
+ vty_out(vty, " lsp-mtu %u\n", area->lsp_mtu);
+ write++;
+ }
+ if (area->purge_originator) {
+ vty_out(vty, " purge-originator\n");
+ write++;
+ }
+
+ /* Minimum SPF interval. */
+ if (area->min_spf_interval[0]
+ == area->min_spf_interval[1]) {
+ if (area->min_spf_interval[0]
+ != MINIMUM_SPF_INTERVAL) {
+ vty_out(vty, " spf-interval %d\n",
+ area->min_spf_interval[0]);
+ write++;
+ }
+ } else {
+ if (area->min_spf_interval[0]
+ != MINIMUM_SPF_INTERVAL) {
+ vty_out(vty,
+ " spf-interval level-1 %d\n",
+ area->min_spf_interval[0]);
+ write++;
+ }
+ if (area->min_spf_interval[1]
+ != MINIMUM_SPF_INTERVAL) {
+ vty_out(vty,
+ " spf-interval level-2 %d\n",
+ area->min_spf_interval[1]);
+ write++;
+ }
+ }
+
+ /* IETF SPF interval */
+ if (area->spf_delay_ietf[0]) {
+ vty_out(vty,
+ " spf-delay-ietf init-delay %ld short-delay %ld long-delay %ld holddown %ld time-to-learn %ld\n",
+ spf_backoff_init_delay(
+ area->spf_delay_ietf[0]),
+ spf_backoff_short_delay(
+ area->spf_delay_ietf[0]),
+ spf_backoff_long_delay(
+ area->spf_delay_ietf[0]),
+ spf_backoff_holddown(
+ area->spf_delay_ietf[0]),
+ spf_backoff_timetolearn(
+ area->spf_delay_ietf[0]));
+ write++;
+ }
+
+ /* Authentication passwords. */
+ if (area->area_passwd.type
+ == ISIS_PASSWD_TYPE_HMAC_MD5) {
+ vty_out(vty, " area-password md5 %s",
+ area->area_passwd.passwd);
+ if (CHECK_FLAG(area->area_passwd.snp_auth,
+ SNP_AUTH_SEND)) {
+ vty_out(vty, " authenticate snp ");
+ if (CHECK_FLAG(
+ area->area_passwd.snp_auth,
+ SNP_AUTH_RECV))
+ vty_out(vty, "validate");
+ else
+ vty_out(vty, "send-only");
+ }
+ vty_out(vty, "\n");
+ write++;
+ } else if (area->area_passwd.type
+ == ISIS_PASSWD_TYPE_CLEARTXT) {
+ vty_out(vty, " area-password clear %s",
+ area->area_passwd.passwd);
+ if (CHECK_FLAG(area->area_passwd.snp_auth,
+ SNP_AUTH_SEND)) {
+ vty_out(vty, " authenticate snp ");
+ if (CHECK_FLAG(
+ area->area_passwd.snp_auth,
+ SNP_AUTH_RECV))
+ vty_out(vty, "validate");
+ else
+ vty_out(vty, "send-only");
+ }
+ vty_out(vty, "\n");
+ write++;
+ }
+ if (area->domain_passwd.type
+ == ISIS_PASSWD_TYPE_HMAC_MD5) {
+ vty_out(vty, " domain-password md5 %s",
+ area->domain_passwd.passwd);
+ if (CHECK_FLAG(area->domain_passwd.snp_auth,
+ SNP_AUTH_SEND)) {
+ vty_out(vty, " authenticate snp ");
+ if (CHECK_FLAG(area->domain_passwd
+ .snp_auth,
+ SNP_AUTH_RECV))
+ vty_out(vty, "validate");
+ else
+ vty_out(vty, "send-only");
+ }
+ vty_out(vty, "\n");
+ write++;
+ } else if (area->domain_passwd.type
+ == ISIS_PASSWD_TYPE_CLEARTXT) {
+ vty_out(vty, " domain-password clear %s",
+ area->domain_passwd.passwd);
+ if (CHECK_FLAG(area->domain_passwd.snp_auth,
+ SNP_AUTH_SEND)) {
+ vty_out(vty, " authenticate snp ");
+ if (CHECK_FLAG(area->domain_passwd
+ .snp_auth,
+ SNP_AUTH_RECV))
+ vty_out(vty, "validate");
+ else
+ vty_out(vty, "send-only");
+ }
+ vty_out(vty, "\n");
+ write++;
+ }
+
+ if (area->log_adj_changes) {
+ vty_out(vty, " log-adjacency-changes\n");
+ write++;
+ }
+
+ write += area_write_mt_settings(area, vty);
+ write += fabricd_write_settings(area, vty);
+
+ vty_out(vty, "exit\n");
+ }
+ }
+
+ return write;
+}
+
+struct cmd_node router_node = {
+ .name = "openfabric",
+ .node = OPENFABRIC_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-router)# ",
+ .config_write = isis_config_write,
+};
+#endif /* ifdef FABRICD */
+#ifndef FABRICD
+/* IS-IS configuration write function */
+static int isis_config_write(struct vty *vty)
+{
+ int write = 0;
+ struct lyd_node *dnode;
+
+ dnode = yang_dnode_get(running_config->dnode, "/frr-isisd:isis");
+ if (dnode) {
+ nb_cli_show_dnode_cmds(vty, dnode, false);
+ write++;
+ }
+
+ return write;
+}
+
+struct cmd_node router_node = {
+ .name = "isis",
+ .node = ISIS_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-router)# ",
+ .config_write = isis_config_write,
+};
+
+struct cmd_node isis_flex_algo_node = {
+ .name = "isis-flex-algo",
+ .node = ISIS_FLEX_ALGO_NODE,
+ .parent_node = ISIS_NODE,
+ .prompt = "%s(config-router-flex-algo)# ",
+};
+#endif /* ifdnef FABRICD */
+
+struct cmd_node isis_srv6_node = {
+ .name = "isis-srv6",
+ .node = ISIS_SRV6_NODE,
+ .parent_node = ISIS_NODE,
+ .prompt = "%s(config-router-srv6)# ",
+};
+
+struct cmd_node isis_srv6_node_msd_node = {
+ .name = "isis-srv6-node-msd",
+ .node = ISIS_SRV6_NODE_MSD_NODE,
+ .parent_node = ISIS_SRV6_NODE,
+ .prompt = "%s(config-router-srv6-node-msd)# ",
+};
+
+void isis_init(void)
+{
+ /* Install IS-IS top node */
+ install_node(&router_node);
+
+ install_element(VIEW_NODE, &show_isis_summary_cmd);
+
+ install_element(VIEW_NODE, &show_isis_spf_ietf_cmd);
+
+ install_element(VIEW_NODE, &show_isis_interface_cmd);
+ install_element(VIEW_NODE, &show_isis_interface_detail_cmd);
+ install_element(VIEW_NODE, &show_isis_interface_arg_cmd);
+
+ install_element(VIEW_NODE, &show_isis_neighbor_cmd);
+ install_element(VIEW_NODE, &show_isis_neighbor_detail_cmd);
+ install_element(VIEW_NODE, &show_isis_neighbor_arg_cmd);
+ install_element(ENABLE_NODE, &clear_isis_neighbor_cmd);
+ install_element(ENABLE_NODE, &clear_isis_neighbor_arg_cmd);
+
+ install_element(VIEW_NODE, &show_hostname_cmd);
+ install_element(VIEW_NODE, &show_database_cmd);
+
+ install_element(ENABLE_NODE, &show_debugging_isis_cmd);
+
+ install_node(&debug_node);
+
+ install_element(ENABLE_NODE, &debug_isis_adj_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_adj_cmd);
+ install_element(ENABLE_NODE, &debug_isis_tx_queue_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_tx_queue_cmd);
+ install_element(ENABLE_NODE, &debug_isis_flooding_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_flooding_cmd);
+ install_element(ENABLE_NODE, &debug_isis_snp_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_snp_cmd);
+ install_element(ENABLE_NODE, &debug_isis_upd_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_upd_cmd);
+ install_element(ENABLE_NODE, &debug_isis_spfevents_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_spfevents_cmd);
+ install_element(ENABLE_NODE, &debug_isis_srevents_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_srevents_cmd);
+ install_element(ENABLE_NODE, &debug_isis_teevents_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_teevents_cmd);
+ install_element(ENABLE_NODE, &debug_isis_lfa_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_lfa_cmd);
+ install_element(ENABLE_NODE, &debug_isis_rtevents_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_rtevents_cmd);
+ install_element(ENABLE_NODE, &debug_isis_events_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_events_cmd);
+ install_element(ENABLE_NODE, &debug_isis_packet_dump_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_packet_dump_cmd);
+ install_element(ENABLE_NODE, &debug_isis_lsp_gen_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_lsp_gen_cmd);
+ install_element(ENABLE_NODE, &debug_isis_lsp_sched_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_lsp_sched_cmd);
+ install_element(ENABLE_NODE, &debug_isis_bfd_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_bfd_cmd);
+ install_element(ENABLE_NODE, &debug_isis_ldp_sync_cmd);
+ install_element(ENABLE_NODE, &no_debug_isis_ldp_sync_cmd);
+
+ install_element(CONFIG_NODE, &debug_isis_adj_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_adj_cmd);
+ install_element(CONFIG_NODE, &debug_isis_tx_queue_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_tx_queue_cmd);
+ install_element(CONFIG_NODE, &debug_isis_flooding_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_flooding_cmd);
+ install_element(CONFIG_NODE, &debug_isis_snp_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_snp_cmd);
+ install_element(CONFIG_NODE, &debug_isis_upd_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_upd_cmd);
+ install_element(CONFIG_NODE, &debug_isis_spfevents_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_spfevents_cmd);
+ install_element(CONFIG_NODE, &debug_isis_srevents_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_srevents_cmd);
+ install_element(CONFIG_NODE, &debug_isis_teevents_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_teevents_cmd);
+ install_element(CONFIG_NODE, &debug_isis_lfa_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_lfa_cmd);
+ install_element(CONFIG_NODE, &debug_isis_rtevents_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_rtevents_cmd);
+ install_element(CONFIG_NODE, &debug_isis_events_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_events_cmd);
+ install_element(CONFIG_NODE, &debug_isis_packet_dump_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_packet_dump_cmd);
+ install_element(CONFIG_NODE, &debug_isis_lsp_gen_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_lsp_gen_cmd);
+ install_element(CONFIG_NODE, &debug_isis_lsp_sched_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_lsp_sched_cmd);
+ install_element(CONFIG_NODE, &debug_isis_bfd_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_bfd_cmd);
+ install_element(CONFIG_NODE, &debug_isis_ldp_sync_cmd);
+ install_element(CONFIG_NODE, &no_debug_isis_ldp_sync_cmd);
+
+ install_default(ROUTER_NODE);
+
+#ifdef FABRICD
+ install_element(CONFIG_NODE, &router_openfabric_cmd);
+ install_element(CONFIG_NODE, &no_router_openfabric_cmd);
+
+ install_element(ROUTER_NODE, &net_cmd);
+ install_element(ROUTER_NODE, &no_net_cmd);
+
+ install_element(ROUTER_NODE, &isis_topology_cmd);
+ install_element(ROUTER_NODE, &no_isis_topology_cmd);
+
+ install_element(ROUTER_NODE, &log_adj_changes_cmd);
+ install_element(ROUTER_NODE, &no_log_adj_changes_cmd);
+#endif /* ifdef FABRICD */
+#ifndef FABRICD
+ install_node(&isis_flex_algo_node);
+ install_default(ISIS_FLEX_ALGO_NODE);
+#endif /* ifdnef FABRICD */
+
+ install_node(&isis_srv6_node);
+ install_default(ISIS_SRV6_NODE);
+
+ install_node(&isis_srv6_node_msd_node);
+ install_default(ISIS_SRV6_NODE_MSD_NODE);
+
+ spf_backoff_cmd_init();
+}
diff --git a/isisd/isisd.h b/isisd/isisd.h
new file mode 100644
index 0000000..f5042e4
--- /dev/null
+++ b/isisd/isisd.h
@@ -0,0 +1,426 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - isisd.h
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#ifndef ISISD_H
+#define ISISD_H
+
+#include "vty.h"
+#include "memory.h"
+
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_redist.h"
+#include "isisd/isis_pdu_counter.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_sr.h"
+#include "isisd/isis_srv6.h"
+#include "isis_flags.h"
+#include "isis_lsp.h"
+#include "isis_lfa.h"
+#include "qobj.h"
+#include "ldp_sync.h"
+#include "iso.h"
+
+DECLARE_MGROUP(ISISD);
+
+#ifdef FABRICD
+static const bool fabricd = true;
+#define PROTO_TYPE ZEBRA_ROUTE_OPENFABRIC
+#define PROTO_NAME "openfabric"
+#define PROTO_HELP "OpenFabric routing protocol\n"
+#define PROTO_REDIST_STR FRR_REDIST_STR_FABRICD
+#define PROTO_IP_REDIST_STR FRR_IP_REDIST_STR_FABRICD
+#define PROTO_IP6_REDIST_STR FRR_IP6_REDIST_STR_FABRICD
+#define PROTO_REDIST_HELP FRR_REDIST_HELP_STR_FABRICD
+#define PROTO_IP_REDIST_HELP FRR_IP_REDIST_HELP_STR_FABRICD
+#define PROTO_IP6_REDIST_HELP FRR_IP6_REDIST_HELP_STR_FABRICD
+#define ROUTER_NODE OPENFABRIC_NODE
+#else
+static const bool fabricd = false;
+#define PROTO_TYPE ZEBRA_ROUTE_ISIS
+#define PROTO_NAME "isis"
+#define PROTO_HELP "IS-IS routing protocol\n"
+#define PROTO_REDIST_STR FRR_REDIST_STR_ISISD
+#define PROTO_IP_REDIST_STR FRR_IP_REDIST_STR_ISISD
+#define PROTO_IP6_REDIST_STR FRR_IP6_REDIST_STR_ISISD
+#define PROTO_REDIST_HELP FRR_REDIST_HELP_STR_ISISD
+#define PROTO_IP_REDIST_HELP FRR_IP_REDIST_HELP_STR_ISISD
+#define PROTO_IP6_REDIST_HELP FRR_IP6_REDIST_HELP_STR_ISISD
+#define ROUTER_NODE ISIS_NODE
+extern void isis_cli_init(void);
+#endif
+
+#define ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf) \
+ if (argv_find(argv, argc, "vrf", &idx_vrf)) { \
+ vrf_name = argv[idx_vrf + 1]->arg; \
+ all_vrf = strmatch(vrf_name, "all"); \
+ }
+
+extern struct zebra_privs_t isisd_privs;
+
+/* uncomment if you are a developer in bug hunt */
+/* #define EXTREME_DEBUG */
+
+struct fabricd;
+
+struct isis_master {
+ /* ISIS instance. */
+ struct list *isis;
+ /* ISIS thread master. */
+ struct event_loop *master;
+ uint8_t options;
+};
+#define F_ISIS_UNIT_TEST 0x01
+
+#define ISIS_DEFAULT_MAX_AREA_ADDRESSES 3
+
+struct isis {
+ vrf_id_t vrf_id;
+ char *name;
+ unsigned long process_id;
+ int sysid_set;
+ uint8_t sysid[ISIS_SYS_ID_LEN]; /* SystemID for this IS */
+ uint32_t router_id; /* Router ID from zebra */
+ struct list *area_list; /* list of IS-IS areas */
+ uint8_t max_area_addrs; /* maximumAreaAdresses */
+ struct iso_address *man_area_addrs; /* manualAreaAddresses */
+ time_t uptime; /* when did we start */
+ struct event *t_dync_clean; /* dynamic hostname cache cleanup thread */
+ uint32_t circuit_ids_used[8]; /* 256 bits to track circuit ids 1 through 255 */
+ int snmp_notifications;
+ struct list *dyn_cache;
+
+ struct route_table *ext_info[REDIST_PROTOCOL_COUNT];
+};
+
+extern struct isis_master *im;
+
+extern struct event *t_isis_cfg;
+
+enum spf_tree_id {
+ SPFTREE_IPV4 = 0,
+ SPFTREE_IPV6,
+ SPFTREE_DSTSRC,
+ SPFTREE_COUNT
+};
+
+struct lsp_refresh_arg {
+ struct isis_area *area;
+ int level;
+};
+
+/* for yang configuration */
+enum isis_metric_style {
+ ISIS_NARROW_METRIC = 0,
+ ISIS_WIDE_METRIC,
+ ISIS_TRANSITION_METRIC,
+};
+
+struct isis_area {
+ struct isis *isis; /* back pointer */
+ struct lspdb_head lspdb[ISIS_LEVELS]; /* link-state dbs */
+ struct isis_spftree *spftree[SPFTREE_COUNT][ISIS_LEVELS];
+#define DEFAULT_LSP_MTU 1497
+ unsigned int lsp_mtu; /* Size of LSPs to generate */
+ struct list *circuit_list; /* IS-IS circuits */
+ struct list *adjacency_list; /* IS-IS adjacencies */
+ struct flags flags;
+ struct event *t_tick; /* LSP walker */
+ struct event *t_lsp_refresh[ISIS_LEVELS];
+ struct event *t_overload_on_startup_timer;
+ struct timeval last_lsp_refresh_event[ISIS_LEVELS];
+ struct event *t_rlfa_rib_update;
+ /* t_lsp_refresh is used in two ways:
+ * a) regular refresh of LSPs
+ * b) (possibly throttled) updates to LSPs
+ *
+ * The lsp_regenerate_pending flag tracks whether the timer is active
+ * for the a) or the b) case.
+ *
+ * It is of utmost importance to clear this flag when the timer is
+ * rescheduled for normal refresh, because otherwise, updates will
+ * be delayed until the next regular refresh.
+ */
+ int lsp_regenerate_pending[ISIS_LEVELS];
+
+ bool bfd_signalled_down;
+ bool bfd_force_spf_refresh;
+
+ struct fabricd *fabricd;
+
+ /*
+ * Configurables
+ */
+ struct isis_passwd area_passwd;
+ struct isis_passwd domain_passwd;
+ /* do we support dynamic hostnames? */
+ char dynhostname;
+ /* do we support new style metrics? */
+ char newmetric;
+ char oldmetric;
+ /* Allow sending the default admin-group value of 0x00000000. */
+ bool admin_group_send_zero;
+ /* Set the legacy flag (aka. L-FLAG) in the ASLA Sub-TLV */
+ bool asla_legacy_flag;
+ /* identifies the routing instance */
+ char *area_tag;
+ /* area addresses for this area */
+ struct list *area_addrs;
+ uint16_t max_lsp_lifetime[ISIS_LEVELS];
+ char is_type; /* level-1 level-1-2 or level-2-only */
+ /* are we overloaded? */
+ char overload_bit;
+ bool overload_configured;
+ uint32_t overload_counter;
+ uint32_t overload_on_startup_time;
+ /* advertise prefixes of passive interfaces only? */
+ bool advertise_passive_only;
+ /* Are we advertising high metrics? */
+ bool advertise_high_metrics;
+ /* L1/L2 router identifier for inter-area traffic */
+ char attached_bit_send;
+ char attached_bit_rcv_ignore;
+ uint16_t lsp_refresh[ISIS_LEVELS];
+ /* minimum time allowed before lsp retransmission */
+ uint16_t lsp_gen_interval[ISIS_LEVELS];
+ /* min interval between between consequtive SPFs */
+ uint16_t min_spf_interval[ISIS_LEVELS];
+ /* the percentage of LSP mtu size used, before generating a new frag */
+ int lsp_frag_threshold;
+ uint64_t lsp_gen_count[ISIS_LEVELS];
+ uint64_t lsp_purge_count[ISIS_LEVELS];
+ uint32_t lsp_exceeded_max_counter;
+ uint32_t lsp_seqno_skipped_counter;
+ uint64_t spf_run_count[ISIS_LEVELS];
+ int ip_circuits;
+ /* logging adjacency changes? */
+ uint8_t log_adj_changes;
+ /* logging pdu drops? */
+ uint8_t log_pdu_drops;
+ /* multi topology settings */
+ struct list *mt_settings;
+ /* MPLS-TE settings */
+ struct mpls_te_area *mta;
+ /* Segment Routing information */
+ struct isis_sr_db srdb;
+ /* Segment Routing over IPv6 (SRv6) information */
+ struct isis_srv6_db srv6db;
+ int ipv6_circuits;
+ bool purge_originator;
+ /* SPF prefix priorities. */
+ struct spf_prefix_priority_acl
+ spf_prefix_priorities[SPF_PREFIX_PRIO_MAX];
+ /* Fast Re-Route information. */
+ size_t lfa_protected_links[ISIS_LEVELS];
+ size_t lfa_load_sharing[ISIS_LEVELS];
+ enum spf_prefix_priority lfa_priority_limit[ISIS_LEVELS];
+ struct lfa_tiebreaker_tree_head lfa_tiebreakers[ISIS_LEVELS];
+ char *rlfa_plist_name[ISIS_LEVELS];
+ struct prefix_list *rlfa_plist[ISIS_LEVELS];
+ size_t rlfa_protected_links[ISIS_LEVELS];
+ size_t tilfa_protected_links[ISIS_LEVELS];
+ /* MPLS LDP-IGP Sync */
+ struct ldp_sync_info_cmd ldp_sync_cmd;
+#ifndef FABRICD
+ /* Flex-Algo */
+ struct flex_algos *flex_algos;
+#endif /* ifndef FABRICD */
+ /* Counters */
+ uint32_t circuit_state_changes;
+ struct list *redist_settings[REDIST_PROTOCOL_COUNT][ZEBRA_ROUTE_MAX + 1]
+ [ISIS_LEVELS];
+ struct route_table *ext_reach[REDIST_PROTOCOL_COUNT][ISIS_LEVELS];
+
+ struct spf_backoff *spf_delay_ietf[ISIS_LEVELS]; /*Structure with IETF
+ SPF algo
+ parameters*/
+ struct event *spf_timer[ISIS_LEVELS];
+
+ struct lsp_refresh_arg lsp_refresh_arg[ISIS_LEVELS];
+
+ pdu_counter_t pdu_tx_counters;
+ pdu_counter_t pdu_rx_counters;
+ pdu_counter_t pdu_drop_counters;
+ uint64_t lsp_rxmt_count;
+
+ /* Area counters */
+ uint64_t rej_adjacencies[2];
+ uint64_t auth_type_failures[2];
+ uint64_t auth_failures[2];
+ uint64_t id_len_mismatches[2];
+ uint64_t lsp_error_counter[2];
+
+ QOBJ_FIELDS;
+};
+DECLARE_QOBJ_TYPE(isis_area);
+
+DECLARE_MTYPE(ISIS_ACL_NAME); /* isis_area->spf_prefix_prioritites */
+DECLARE_MTYPE(ISIS_AREA_ADDR); /* isis_area->area_addrs */
+DECLARE_MTYPE(ISIS_PLIST_NAME);
+
+DECLARE_HOOK(isis_area_overload_bit_update, (struct isis_area * area), (area));
+
+void isis_terminate(void);
+void isis_master_init(struct event_loop *master);
+void isis_vrf_link(struct isis *isis, struct vrf *vrf);
+void isis_vrf_unlink(struct isis *isis, struct vrf *vrf);
+struct isis *isis_lookup_by_vrfid(vrf_id_t vrf_id);
+struct isis *isis_lookup_by_vrfname(const char *vrfname);
+struct isis *isis_lookup_by_sysid(const uint8_t *sysid);
+
+void isis_init(void);
+void isis_vrf_init(void);
+
+struct isis *isis_new(const char *vrf_name);
+void isis_finish(struct isis *isis);
+
+void isis_area_add_circuit(struct isis_area *area,
+ struct isis_circuit *circuit);
+void isis_area_del_circuit(struct isis_area *area,
+ struct isis_circuit *circuit);
+
+struct isis_area *isis_area_create(const char *, const char *);
+struct isis_area *isis_area_lookup(const char *, vrf_id_t vrf_id);
+struct isis_area *isis_area_lookup_by_vrf(const char *area_tag,
+ const char *vrf_name);
+int isis_area_get(struct vty *vty, const char *area_tag);
+void isis_area_destroy(struct isis_area *area);
+void isis_filter_update(struct access_list *access);
+void isis_prefix_list_update(struct prefix_list *plist);
+void print_debug(struct vty *, int, int);
+struct isis_lsp *lsp_for_sysid(struct lspdb_head *head, const char *sysid_str,
+ struct isis *isis);
+
+void isis_area_invalidate_routes(struct isis_area *area, int levels);
+void isis_area_verify_routes(struct isis_area *area);
+void isis_area_switchover_routes(struct isis_area *area, int family,
+ union g_addr *nexthop_ip, ifindex_t ifindex,
+ int level);
+
+void isis_area_overload_bit_set(struct isis_area *area, bool overload_bit);
+void isis_area_overload_on_startup_set(struct isis_area *area,
+ uint32_t startup_time);
+void isis_area_advertise_high_metrics_set(struct isis_area *area,
+ bool advertise_high_metrics);
+void isis_area_attached_bit_send_set(struct isis_area *area, bool attached_bit);
+void isis_area_attached_bit_receive_set(struct isis_area *area,
+ bool attached_bit);
+void isis_area_dynhostname_set(struct isis_area *area, bool dynhostname);
+void isis_area_metricstyle_set(struct isis_area *area, bool old_metric,
+ bool new_metric);
+void isis_area_lsp_mtu_set(struct isis_area *area, unsigned int lsp_mtu);
+void isis_area_is_type_set(struct isis_area *area, int is_type);
+void isis_area_max_lsp_lifetime_set(struct isis_area *area, int level,
+ uint16_t max_lsp_lifetime);
+void isis_area_lsp_refresh_set(struct isis_area *area, int level,
+ uint16_t lsp_refresh);
+/* IS_LEVEL_1 sets area_passwd, IS_LEVEL_2 domain_passwd */
+int isis_area_passwd_unset(struct isis_area *area, int level);
+int isis_area_passwd_cleartext_set(struct isis_area *area, int level,
+ const char *passwd, uint8_t snp_auth);
+int isis_area_passwd_hmac_md5_set(struct isis_area *area, int level,
+ const char *passwd, uint8_t snp_auth);
+void show_isis_database_lspdb_json(struct json_object *json,
+ struct isis_area *area, int level,
+ struct lspdb_head *lspdb, const char *argv,
+ int ui_level);
+void show_isis_database_lspdb_vty(struct vty *vty, struct isis_area *area,
+ int level, struct lspdb_head *lspdb,
+ const char *argv, int ui_level);
+char *isis_restart_filepath(void);
+void isis_restart_write_overload_time(struct isis_area *isis_area,
+ uint32_t overload_time);
+uint32_t isis_restart_read_overload_time(struct isis_area *isis_area);
+void config_end_lsp_generate(struct isis_area *area);
+
+/* YANG paths */
+#define ISIS_INSTANCE "/frr-isisd:isis/instance"
+#define ISIS_SR "/frr-isisd:isis/instance/segment-routing"
+#define ISIS_SRV6 "/frr-isisd:isis/instance/segment-routing-srv6"
+
+/* Master of threads. */
+extern struct event_loop *master;
+
+extern unsigned long debug_adj_pkt;
+extern unsigned long debug_snp_pkt;
+extern unsigned long debug_update_pkt;
+extern unsigned long debug_spf_events;
+extern unsigned long debug_rte_events;
+extern unsigned long debug_events;
+extern unsigned long debug_pkt_dump;
+extern unsigned long debug_lsp_gen;
+extern unsigned long debug_lsp_sched;
+extern unsigned long debug_flooding;
+extern unsigned long debug_bfd;
+extern unsigned long debug_tx_queue;
+extern unsigned long debug_sr;
+extern unsigned long debug_ldp_sync;
+extern unsigned long debug_lfa;
+extern unsigned long debug_te;
+
+#define DEBUG_ADJ_PACKETS (1<<0)
+#define DEBUG_SNP_PACKETS (1<<1)
+#define DEBUG_UPDATE_PACKETS (1<<2)
+#define DEBUG_SPF_EVENTS (1<<3)
+#define DEBUG_RTE_EVENTS (1<<4)
+#define DEBUG_EVENTS (1<<5)
+#define DEBUG_PACKET_DUMP (1<<6)
+#define DEBUG_LSP_GEN (1<<7)
+#define DEBUG_LSP_SCHED (1<<8)
+#define DEBUG_FLOODING (1<<9)
+#define DEBUG_BFD (1<<10)
+#define DEBUG_TX_QUEUE (1<<11)
+#define DEBUG_SR (1<<12)
+#define DEBUG_LDP_SYNC (1<<13)
+#define DEBUG_LFA (1<<14)
+#define DEBUG_TE (1<<15)
+
+/* Debug related macro. */
+#define IS_DEBUG_ADJ_PACKETS (debug_adj_pkt & DEBUG_ADJ_PACKETS)
+#define IS_DEBUG_SNP_PACKETS (debug_snp_pkt & DEBUG_SNP_PACKETS)
+#define IS_DEBUG_UPDATE_PACKETS (debug_update_pkt & DEBUG_UPDATE_PACKETS)
+#define IS_DEBUG_SPF_EVENTS (debug_spf_events & DEBUG_SPF_EVENTS)
+#define IS_DEBUG_RTE_EVENTS (debug_rte_events & DEBUG_RTE_EVENTS)
+#define IS_DEBUG_EVENTS (debug_events & DEBUG_EVENTS)
+#define IS_DEBUG_PACKET_DUMP (debug_pkt_dump & DEBUG_PACKET_DUMP)
+#define IS_DEBUG_LSP_GEN (debug_lsp_gen & DEBUG_LSP_GEN)
+#define IS_DEBUG_LSP_SCHED (debug_lsp_sched & DEBUG_LSP_SCHED)
+#define IS_DEBUG_FLOODING (debug_flooding & DEBUG_FLOODING)
+#define IS_DEBUG_BFD (debug_bfd & DEBUG_BFD)
+#define IS_DEBUG_TX_QUEUE (debug_tx_queue & DEBUG_TX_QUEUE)
+#define IS_DEBUG_SR (debug_sr & DEBUG_SR)
+#define IS_DEBUG_LDP_SYNC (debug_ldp_sync & DEBUG_LDP_SYNC)
+#define IS_DEBUG_LFA (debug_lfa & DEBUG_LFA)
+#define IS_DEBUG_TE (debug_te & DEBUG_TE)
+
+#define lsp_debug(...) \
+ do { \
+ if (IS_DEBUG_LSP_GEN) \
+ zlog_debug(__VA_ARGS__); \
+ } while (0)
+
+#define sched_debug(...) \
+ do { \
+ if (IS_DEBUG_LSP_SCHED) \
+ zlog_debug(__VA_ARGS__); \
+ } while (0)
+
+#define sr_debug(...) \
+ do { \
+ if (IS_DEBUG_SR) \
+ zlog_debug(__VA_ARGS__); \
+ } while (0)
+
+#define te_debug(...) \
+ do { \
+ if (IS_DEBUG_TE) \
+ zlog_debug(__VA_ARGS__); \
+ } while (0)
+
+#endif /* ISISD_H */
diff --git a/isisd/iso_checksum.c b/isisd/iso_checksum.c
new file mode 100644
index 0000000..f12c195
--- /dev/null
+++ b/isisd/iso_checksum.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - iso_checksum.c
+ * ISO checksum related routines
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+
+#include <zebra.h>
+#include "iso_checksum.h"
+#include "checksum.h"
+
+/*
+ * Calculations of the OSI checksum.
+ * ISO/IEC 8473 defines the sum as
+ *
+ * L
+ * sum a (mod 255) = 0
+ * 1 i
+ *
+ * L
+ * sum (L-i+1)a (mod 255) = 0
+ * 1 i
+ *
+ */
+
+/*
+ * Verifies that the checksum is correct.
+ * Return 0 on correct and 1 on invalid checksum.
+ * Based on Annex C.4 of ISO/IEC 8473
+ */
+
+int iso_csum_verify(uint8_t *buffer, int len, uint16_t csum, int offset)
+{
+ uint16_t checksum;
+ uint32_t c0;
+ uint32_t c1;
+
+ c0 = csum & 0xff00;
+ c1 = csum & 0x00ff;
+
+ /*
+ * If both are zero return correct
+ */
+ if (c0 == 0 && c1 == 0)
+ return 0;
+
+ /*
+ * If either, but not both are zero return incorrect
+ */
+ if (c0 == 0 || c1 == 0)
+ return 1;
+
+ checksum = fletcher_checksum(buffer, len, offset);
+ if (checksum == htons(csum))
+ return 0;
+ return 1;
+}
diff --git a/isisd/iso_checksum.h b/isisd/iso_checksum.h
new file mode 100644
index 0000000..9dcb039
--- /dev/null
+++ b/isisd/iso_checksum.h
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - iso_checksum.c
+ * ISO checksum related routines
+ *
+ * Copyright (C) 2001,2002 Sampo Saaristo
+ * Tampere University of Technology
+ * Institute of Communications Engineering
+ */
+#ifndef _ZEBRA_ISO_CSUM_H
+#define _ZEBRA_ISO_CSUM_H
+
+int iso_csum_verify(uint8_t *buffer, int len, uint16_t csum, int offset);
+
+#endif /* _ZEBRA_ISO_CSUM_H */
diff --git a/isisd/subdir.am b/isisd/subdir.am
new file mode 100644
index 0000000..e33cb76
--- /dev/null
+++ b/isisd/subdir.am
@@ -0,0 +1,140 @@
+#
+# isisd
+#
+
+if ISISD
+noinst_LIBRARIES += isisd/libisis.a
+sbin_PROGRAMS += isisd/isisd
+vtysh_daemons += isisd
+if SNMP
+module_LTLIBRARIES += isisd/isisd_snmp.la
+endif
+man8 += $(MANBUILD)/frr-isisd.8
+endif
+
+if FABRICD
+noinst_LIBRARIES += isisd/libfabric.a
+sbin_PROGRAMS += isisd/fabricd
+vtysh_daemons += fabricd
+endif
+
+noinst_HEADERS += \
+ isisd/isis_affinitymap.h \
+ isisd/isis_adjacency.h \
+ isisd/isis_bfd.h \
+ isisd/isis_circuit.h \
+ isisd/isis_common.h \
+ isisd/isis_constants.h \
+ isisd/isis_csm.h \
+ isisd/isis_dr.h \
+ isisd/isis_dynhn.h \
+ isisd/isis_errors.h \
+ isisd/isis_events.h \
+ isisd/isis_flags.h \
+ isisd/isis_ldp_sync.h \
+ isisd/isis_lfa.h \
+ isisd/isis_lsp.h \
+ isisd/isis_misc.h \
+ isisd/isis_mt.h \
+ isisd/isis_nb.h \
+ isisd/isis_network.h \
+ isisd/isis_pdu.h \
+ isisd/isis_pdu_counter.h \
+ isisd/isis_redist.h \
+ isisd/isis_route.h \
+ isisd/isis_routemap.h \
+ isisd/isis_spf.h \
+ isisd/isis_spf_private.h \
+ isisd/isis_sr.h \
+ isisd/isis_flex_algo.h \
+ isisd/isis_srv6.h \
+ isisd/isis_te.h \
+ isisd/isis_tlvs.h \
+ isisd/isis_tx_queue.h \
+ isisd/isis_zebra.h \
+ isisd/isisd.h \
+ isisd/iso_checksum.h \
+ isisd/fabricd.h \
+ # end
+
+LIBISIS_SOURCES = \
+ isisd/isis_affinitymap.c \
+ isisd/isis_adjacency.c \
+ isisd/isis_bfd.c \
+ isisd/isis_circuit.c \
+ isisd/isis_csm.c \
+ isisd/isis_dr.c \
+ isisd/isis_dynhn.c \
+ isisd/isis_errors.c \
+ isisd/isis_events.c \
+ isisd/isis_flags.c \
+ isisd/isis_ldp_sync.c \
+ isisd/isis_lfa.c \
+ isisd/isis_lsp.c \
+ isisd/isis_misc.c \
+ isisd/isis_mt.c \
+ isisd/isis_pdu.c \
+ isisd/isis_pdu_counter.c \
+ isisd/isis_redist.c \
+ isisd/isis_route.c \
+ isisd/isis_routemap.c \
+ isisd/isis_spf.c \
+ isisd/isis_sr.c \
+ isisd/isis_flex_algo.c \
+ isisd/isis_srv6.c \
+ isisd/isis_te.c \
+ isisd/isis_tlvs.c \
+ isisd/isis_tx_queue.c \
+ isisd/isis_zebra.c \
+ isisd/isisd.c \
+ isisd/iso_checksum.c \
+ isisd/fabricd.c \
+ # end
+
+ISIS_SOURCES = \
+ isisd/isis_bpf.c \
+ isisd/isis_dlpi.c \
+ isisd/isis_main.c \
+ isisd/isis_pfpacket.c \
+ # end
+
+ISIS_LDADD_COMMON = lib/libfrr.la $(LIBCAP) $(LIBYANG_LIBS)
+
+# Building isisd
+
+isisd_libisis_a_SOURCES = \
+ $(LIBISIS_SOURCES) \
+ isisd/isis_nb.c \
+ isisd/isis_nb_config.c \
+ isisd/isis_nb_notifications.c \
+ isisd/isis_nb_state.c \
+ isisd/isis_cli.c \
+ #end
+
+clippy_scan += \
+ isisd/isis_cli.c \
+ # end
+
+isisd_isisd_LDADD = isisd/libisis.a $(ISIS_LDADD_COMMON)
+isisd_isisd_SOURCES = $(ISIS_SOURCES)
+nodist_isisd_isisd_SOURCES = \
+ yang/frr-isisd.yang.c \
+ # end
+
+isisd_isisd_snmp_la_SOURCES = isisd/isis_snmp.c
+isisd_isisd_snmp_la_CFLAGS = $(AM_CFLAGS) $(SNMP_CFLAGS) -std=gnu11
+isisd_isisd_snmp_la_LDFLAGS = $(MODULE_LDFLAGS)
+isisd_isisd_snmp_la_LIBADD = lib/libfrrsnmp.la
+
+# Building fabricd
+
+FABRICD_CPPFLAGS = -DFABRICD=1 $(AM_CPPFLAGS)
+
+isisd_libfabric_a_SOURCES = \
+ $(LIBISIS_SOURCES) \
+ isisd/isis_vty_fabricd.c \
+ #end
+isisd_libfabric_a_CPPFLAGS = $(FABRICD_CPPFLAGS)
+isisd_fabricd_LDADD = isisd/libfabric.a $(ISIS_LDADD_COMMON)
+isisd_fabricd_SOURCES = $(ISIS_SOURCES)
+isisd_fabricd_CPPFLAGS = $(FABRICD_CPPFLAGS)