diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
commit | e2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch) | |
tree | f0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /isisd | |
parent | Initial commit. (diff) | |
download | frr-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')
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(¬ification_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( + ¬ification_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( + ¬ification_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(¬ification_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( + ¬ification_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( + ¬ification_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(¬ification_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( + ¬ification_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(¬ification_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( + ¬ification_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(¬ification_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(¬ification_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( + ¬ification_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( + ¬ification_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(¬ification_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(¬ification_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(¬ification_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( + ¬ification_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( + ¬ification_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(¬ification_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( + ¬ification_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( + ¬ification_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( + ¬ification_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( + ¬ification_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( + ¬ification_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( + ¬ification_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) |