diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
commit | 2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch) | |
tree | c05dc0f8e6aa3accc84e3e5cffc933ed94941383 /isisd/isis_sr.c | |
parent | Initial commit. (diff) | |
download | frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.tar.xz frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.zip |
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'isisd/isis_sr.c')
-rw-r--r-- | isisd/isis_sr.c | 1292 |
1 files changed, 1292 insertions, 0 deletions
diff --git a/isisd/isis_sr.c b/isisd/isis_sr.c new file mode 100644 index 0000000..f70840a --- /dev/null +++ b/isisd/isis_sr.c @@ -0,0 +1,1292 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <zebra.h> + +#include "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) +{ + return prefix_cmp(&a->prefix, &b->prefix); +} +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) +{ + 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; + + /* 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) +{ + struct sr_prefix_cfg pcfg = {}; + + prefix_copy(&pcfg.prefix, prefix.p); + 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 = SR_ALGORITHM_SPF; + + /* 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; +} + +/** + * Activate local Prefix-SID when loopback interface goes up for IS-IS. + * + * @param ifp Loopback Interface + * + * @return 0 + */ +static int sr_if_new_hook(struct interface *ifp) +{ + struct isis_circuit *circuit; + struct isis_area *area; + struct connected *connected; + struct listnode *node; + + /* Get corresponding circuit */ + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return 0; + + area = circuit->area; + if (!area) + return 0; + + /* + * Update the Node-SID flag of the configured Prefix-SID mappings if + * necessary. This needs to be done here since isisd reads the startup + * configuration before receiving interface information from zebra. + */ + FOR_ALL_INTERFACES_ADDRESSES (ifp, connected, node) { + struct sr_prefix_cfg *pcfg; + + pcfg = isis_sr_cfg_prefix_find(area, connected->address); + if (!pcfg) + continue; + + if (sr_prefix_is_node_sid(ifp, &pcfg->prefix)) { + pcfg->node_sid = true; + 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) +{ + struct isis_lsp *lsp; + struct ttable *tt; + + 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; + + ttable_add_row( + tt, "%s|%u - %u|%u - %u|%s|%u", + sysid_print(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, + cap->algo[0] == SR_ALGORITHM_SPF ? "SPF" : "S-SPF", + 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 isis segment-routing node", + SHOW_STR PROTO_HELP + "Segment-Routing\n" + "Segment-Routing 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->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); + } + } + + 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 thread *start) +{ + struct isis_area *area; + + area = THREAD_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. */ + thread_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 */ + THREAD_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); + hook_register(isis_if_new_hook, sr_if_new_hook); +} + +/** + * 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); + hook_unregister(isis_if_new_hook, sr_if_new_hook); +} |