diff options
Diffstat (limited to 'isisd/isis_route.c')
-rw-r--r-- | isisd/isis_route.c | 729 |
1 files changed, 729 insertions, 0 deletions
diff --git a/isisd/isis_route.c b/isisd/isis_route.c new file mode 100644 index 0000000..34d7e7e --- /dev/null +++ b/isisd/isis_route.c @@ -0,0 +1,729 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public Licenseas 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 "thread.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" + +DEFINE_MTYPE_STATIC(ISISD, ISIS_NEXTHOP, "ISIS nexthop"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_ROUTE_INFO, "ISIS route 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 isis_nexthop *isis_nexthop_create(int family, union g_addr *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; +} + +void isis_nexthop_delete(struct isis_nexthop *nexthop) +{ + XFREE(MTYPE_ISIS_NEXTHOP_LABELS, nexthop->label_stack); + XFREE(MTYPE_ISIS_NEXTHOP, nexthop); +} + +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->family != family) + continue; + if (nh->ifindex != ifindex) + 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 = *sr; + + return rinfo; +} + +static void isis_route_info_delete(struct isis_route_info *route_info) +{ + 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); +} + +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; + + 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; + } + + if (!isis_sr_psid_info_same(&new->sr, &old->sr)) { + if (buf) + snprintf(buf, buf_size, "SR input label"); + 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); + rinfo_new->sr_previous = rinfo_old->sr; + 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_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; + + /* + * Explicitly uninstall previous Prefix-SID label if it has + * changed or was removed. + */ + if (route_info->sr_previous.present + && (!route_info->sr.present + || route_info->sr_previous.label + != route_info->sr.label)) + isis_zebra_prefix_sid_uninstall( + area, prefix, route_info, + &route_info->sr_previous); + + /* Install route. */ + isis_zebra_route_add_route(area->isis, prefix, src_p, + route_info); + /* Install/reinstall Prefix-SID label. */ + if (route_info->sr.present) + isis_zebra_prefix_sid_install(area, prefix, route_info, + &route_info->sr); + 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. */ + if (route_info->sr.present) + isis_zebra_prefix_sid_uninstall( + area, prefix, route_info, &route_info->sr); + /* 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 */ + + 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; + UNSET_FLAG(rinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + } else if (rinfo->backup) { + rinfo->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; + + /* Area is either L1 or L2 => we use level route tables + * directly for + * validating => no problems with deleting routes. */ + if (!tables) { + isis_route_delete(area, rnode, table); + continue; + } + + /* If area is L1L2, we work with merge table and + * therefore must + * delete node from level tables as well before deleting + * route info. */ + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + drnode = srcdest_rnode_lookup(tables[level - 1], + 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); + } +} + +void isis_route_verify_table(struct isis_area *area, struct route_table *table, + struct route_table *table_backup) +{ + _isis_route_verify_table(area, table, table_backup, NULL); +} + +/* 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) +{ + struct route_table *tables[] = {level1_table, level2_table}; + struct route_table *tables_backup[] = {level1_table_backup, + level2_table_backup}; + struct route_table *merge; + struct route_node *rnode, *mrnode; + + merge = srcdest_table_init(); + + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + for (rnode = route_top(tables[level - 1]); 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[level - 1], prefix, src_p); + if (rnode_bck) { + rinfo->backup = rnode_bck->info; + UNSET_FLAG(rinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + } else if (rinfo->backup) { + rinfo->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); + 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; + } + } + 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; + 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; + /* + * 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); + } +} |