diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c index 97c3e5740f6e..512b7a13254a 100644 --- a/bgpd/bgp_debug.c +++ b/bgpd/bgp_debug.c @@ -60,6 +60,7 @@ unsigned long conf_bgp_debug_graceful_restart; unsigned long conf_bgp_debug_evpn_mh; unsigned long conf_bgp_debug_bfd; unsigned long conf_bgp_debug_cond_adv; +unsigned long conf_bgp_debug_nexthop_group; unsigned long term_bgp_debug_as4; unsigned long term_bgp_debug_neighbor_events; @@ -80,6 +81,7 @@ unsigned long term_bgp_debug_graceful_restart; unsigned long term_bgp_debug_evpn_mh; unsigned long term_bgp_debug_bfd; unsigned long term_bgp_debug_cond_adv; +unsigned long term_bgp_debug_nexthop_group; struct list *bgp_debug_neighbor_events_peers = NULL; struct list *bgp_debug_keepalive_peers = NULL; @@ -2040,6 +2042,40 @@ DEFPY (debug_bgp_evpn_mh, return CMD_SUCCESS; } +DEFPY (debug_bgp_nexthop_group, + debug_bgp_nexthop_group_cmd, + "[no$no] debug bgp nexthop-group [detail$detail]", + NO_STR + DEBUG_STR + BGP_STR + "Nexthop Group debugging\n" + "Detailed Nexthop Group debugging\n") +{ + if (vty->node == CONFIG_NODE) { + if (no) { + DEBUG_OFF(nexthop_group, NEXTHOP_GROUP); + DEBUG_OFF(nexthop_group, NEXTHOP_GROUP_DETAIL); + } else { + DEBUG_ON(nexthop_group, NEXTHOP_GROUP); + if (detail) + DEBUG_ON(nexthop_group, NEXTHOP_GROUP_DETAIL); + } + } else { + if (no) { + TERM_DEBUG_OFF(nexthop_group, NEXTHOP_GROUP); + TERM_DEBUG_OFF(nexthop_group, NEXTHOP_GROUP_DETAIL); + vty_out(vty, "BGP nexthop group debugging is off\n"); + } else { + TERM_DEBUG_ON(nexthop_group, NEXTHOP_GROUP); + if (detail) + TERM_DEBUG_ON(nexthop_group, NEXTHOP_GROUP_DETAIL); + vty_out(vty, "BGP nexthop group %sdebugging is on\n", + detail ? "detailed " : ""); + } + } + return CMD_SUCCESS; +} + DEFUN (debug_bgp_labelpool, debug_bgp_labelpool_cmd, "debug bgp labelpool", @@ -2177,6 +2213,8 @@ DEFUN (no_debug_bgp, TERM_DEBUG_OFF(evpn_mh, EVPN_MH_RT); TERM_DEBUG_OFF(bfd, BFD_LIB); TERM_DEBUG_OFF(cond_adv, COND_ADV); + TERM_DEBUG_OFF(nexthop_group, NEXTHOP_GROUP); + TERM_DEBUG_OFF(nexthop_group, NEXTHOP_GROUP_DETAIL); vty_out(vty, "All possible debugging has been turned off\n"); @@ -2270,6 +2308,11 @@ DEFUN_NOSH (show_debugging_bgp, vty_out(vty, " BGP conditional advertisement debugging is on\n"); + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + vty_out(vty, " BGP nexthop group detailed debugging is on\n"); + else if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) + vty_out(vty, " BGP nexthop group debugging is on\n"); + cmd_show_lib_debugs(vty); hook_call(bgp_hook_config_write_debug, vty, false); @@ -2408,6 +2451,14 @@ static int bgp_config_write_debug(struct vty *vty) write++; } + if (CONF_BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) { + vty_out(vty, "debug bgp nexthop-group detail\n"); + write++; + } else if (CONF_BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) { + vty_out(vty, "debug bgp nexthop-group\n"); + write++; + } + if (hook_call(bgp_hook_config_write_debug, vty, true)) write++; @@ -2545,6 +2596,10 @@ void bgp_debug_init(void) /* debug bgp conditional advertisement */ install_element(ENABLE_NODE, &debug_bgp_cond_adv_cmd); install_element(CONFIG_NODE, &debug_bgp_cond_adv_cmd); + + /* debug bgp nexthop group [detail] */ + install_element(ENABLE_NODE, &debug_bgp_nexthop_group_cmd); + install_element(CONFIG_NODE, &debug_bgp_nexthop_group_cmd); } /* Return true if this prefix is on the per_prefix_list of prefixes to debug diff --git a/bgpd/bgp_debug.h b/bgpd/bgp_debug.h index 061d966dc392..b83f41591894 100644 --- a/bgpd/bgp_debug.h +++ b/bgpd/bgp_debug.h @@ -71,6 +71,7 @@ extern unsigned long conf_bgp_debug_graceful_restart; extern unsigned long conf_bgp_debug_evpn_mh; extern unsigned long conf_bgp_debug_bfd; extern unsigned long conf_bgp_debug_cond_adv; +extern unsigned long conf_bgp_debug_nexthop_group; extern unsigned long term_bgp_debug_as4; extern unsigned long term_bgp_debug_neighbor_events; @@ -89,6 +90,7 @@ extern unsigned long term_bgp_debug_graceful_restart; extern unsigned long term_bgp_debug_evpn_mh; extern unsigned long term_bgp_debug_bfd; extern unsigned long term_bgp_debug_cond_adv; +extern unsigned long term_bgp_debug_nexthop_group; extern struct list *bgp_debug_neighbor_events_peers; extern struct list *bgp_debug_keepalive_peers; @@ -130,6 +132,8 @@ struct bgp_debug_filter { #define BGP_DEBUG_PBR_ERROR 0x02 #define BGP_DEBUG_EVPN_MH_ES 0x01 #define BGP_DEBUG_EVPN_MH_RT 0x02 +#define BGP_DEBUG_NEXTHOP_GROUP 0x01 +#define BGP_DEBUG_NEXTHOP_GROUP_DETAIL 0x02 #define BGP_DEBUG_GRACEFUL_RESTART 0x01 diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c index 567af5bb7577..7d8e2263c7ab 100644 --- a/bgpd/bgp_fsm.c +++ b/bgpd/bgp_fsm.c @@ -42,6 +42,7 @@ #include "bgpd/bgp_io.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_vty.h" +#include "bgpd/bgp_nhg.h" DEFINE_HOOK(peer_backward_transition, (struct peer * peer), (peer)); DEFINE_HOOK(peer_status_changed, (struct peer * peer), (peer)); @@ -1242,6 +1243,12 @@ void bgp_fsm_change_status(struct peer_connection *connection, if (status >= Clearing && (peer->established || peer == bgp->peer_self)) { bgp_clear_route_all(peer); + /* do not wait the clear-node thread to be called. otherwise, + * the updated paths may be processed before. + */ + if (bgp_option_check(BGP_OPT_NHG)) + bgp_nhg_clear_nhg_nexthop(); + /* If no route was queued for the clear-node processing, * generate the * completion event here. This is needed because if there are no diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c index 535d2fc5f434..59108fca4a2c 100644 --- a/bgpd/bgp_main.c +++ b/bgpd/bgp_main.c @@ -522,6 +522,7 @@ int main(int argc, char **argv) if (bgpd_di.graceful_restart) SET_FLAG(bm->flags, BM_FLAG_GRACEFUL_RESTART); + bgp_nhg_configure_default(); bgp_error_init(); /* Initializations. */ libagentx_init(); diff --git a/bgpd/bgp_memory.c b/bgpd/bgp_memory.c index c2599ade58f1..fd1ff7144c06 100644 --- a/bgpd/bgp_memory.c +++ b/bgpd/bgp_memory.c @@ -86,6 +86,7 @@ DEFINE_MTYPE(BGPD, BGP_DEBUG_STR, "BGP debug filter string"); DEFINE_MTYPE(BGPD, BGP_DISTANCE, "BGP distance"); DEFINE_MTYPE(BGPD, BGP_NEXTHOP_CACHE, "BGP nexthop"); +DEFINE_MTYPE(BGPD, BGP_NHG_CACHE, "BGP NHG"); DEFINE_MTYPE(BGPD, BGP_CONFED_LIST, "BGP confed list"); DEFINE_MTYPE(BGPD, PEER_UPDATE_SOURCE, "BGP peer update interface"); DEFINE_MTYPE(BGPD, PEER_CONF_IF, "BGP peer config interface"); diff --git a/bgpd/bgp_memory.h b/bgpd/bgp_memory.h index 1f76945da3e0..64004a887da8 100644 --- a/bgpd/bgp_memory.h +++ b/bgpd/bgp_memory.h @@ -82,6 +82,7 @@ DECLARE_MTYPE(BGP_DEBUG_STR); DECLARE_MTYPE(BGP_DISTANCE); DECLARE_MTYPE(BGP_NEXTHOP_CACHE); +DECLARE_MTYPE(BGP_NHG_CACHE); DECLARE_MTYPE(BGP_CONFED_LIST); DECLARE_MTYPE(PEER_UPDATE_SOURCE); DECLARE_MTYPE(PEER_CONF_IF); diff --git a/bgpd/bgp_nexthop.c b/bgpd/bgp_nexthop.c index 401549c4e859..285c4243ae90 100644 --- a/bgpd/bgp_nexthop.c +++ b/bgpd/bgp_nexthop.c @@ -729,61 +729,11 @@ bool bgp_subgrp_multiaccess_check_v4(struct in_addr nexthop, return false; } -static void bgp_show_bgp_path_info_flags(uint32_t flags, json_object *json) -{ - json_object *json_flags = NULL; - - if (!json) - return; - - json_flags = json_object_new_object(); - json_object_boolean_add(json_flags, "igpChanged", - CHECK_FLAG(flags, BGP_PATH_IGP_CHANGED)); - json_object_boolean_add(json_flags, "damped", - CHECK_FLAG(flags, BGP_PATH_DAMPED)); - json_object_boolean_add(json_flags, "history", - CHECK_FLAG(flags, BGP_PATH_HISTORY)); - json_object_boolean_add(json_flags, "bestpath", - CHECK_FLAG(flags, BGP_PATH_SELECTED)); - json_object_boolean_add(json_flags, "valid", - CHECK_FLAG(flags, BGP_PATH_VALID)); - json_object_boolean_add(json_flags, "attrChanged", - CHECK_FLAG(flags, BGP_PATH_ATTR_CHANGED)); - json_object_boolean_add(json_flags, "deterministicMedCheck", - CHECK_FLAG(flags, BGP_PATH_DMED_CHECK)); - json_object_boolean_add(json_flags, "deterministicMedSelected", - CHECK_FLAG(flags, BGP_PATH_DMED_SELECTED)); - json_object_boolean_add(json_flags, "stale", - CHECK_FLAG(flags, BGP_PATH_STALE)); - json_object_boolean_add(json_flags, "removed", - CHECK_FLAG(flags, BGP_PATH_REMOVED)); - json_object_boolean_add(json_flags, "counted", - CHECK_FLAG(flags, BGP_PATH_COUNTED)); - json_object_boolean_add(json_flags, "multipath", - CHECK_FLAG(flags, BGP_PATH_MULTIPATH)); - json_object_boolean_add(json_flags, "multipathChanged", - CHECK_FLAG(flags, BGP_PATH_MULTIPATH_CHG)); - json_object_boolean_add(json_flags, "ribAttributeChanged", - CHECK_FLAG(flags, BGP_PATH_RIB_ATTR_CHG)); - json_object_boolean_add(json_flags, "nexthopSelf", - CHECK_FLAG(flags, BGP_PATH_ANNC_NH_SELF)); - json_object_boolean_add(json_flags, "linkBandwidthChanged", - CHECK_FLAG(flags, BGP_PATH_LINK_BW_CHG)); - json_object_boolean_add(json_flags, "acceptOwn", - CHECK_FLAG(flags, BGP_PATH_ACCEPT_OWN)); - json_object_object_add(json, "flags", json_flags); -} - static void bgp_show_nexthop_paths(struct vty *vty, struct bgp *bgp, struct bgp_nexthop_cache *bnc, json_object *json) { - struct bgp_dest *dest; struct bgp_path_info *path; - afi_t afi; - safi_t safi; - struct bgp_table *table; - struct bgp *bgp_path; json_object *paths = NULL; json_object *json_path = NULL; @@ -792,44 +742,11 @@ static void bgp_show_nexthop_paths(struct vty *vty, struct bgp *bgp, else vty_out(vty, " Paths:\n"); LIST_FOREACH (path, &(bnc->paths), nh_thread) { - dest = path->net; - assert(dest && bgp_dest_table(dest)); - afi = family2afi(bgp_dest_get_prefix(dest)->family); - table = bgp_dest_table(dest); - safi = table->safi; - bgp_path = table->bgp; - - - if (json) { + if (json) json_path = json_object_new_object(); - json_object_string_add(json_path, "afi", afi2str(afi)); - json_object_string_add(json_path, "safi", - safi2str(safi)); - json_object_string_addf(json_path, "prefix", "%pBD", - dest); - if (dest->pdest) - json_object_string_addf( - json_path, "rd", - BGP_RD_AS_FORMAT(bgp->asnotation), - (struct prefix_rd *)bgp_dest_get_prefix( - dest->pdest)); - json_object_string_add( - json_path, "vrf", - vrf_id_to_name(bgp_path->vrf_id)); - bgp_show_bgp_path_info_flags(path->flags, json_path); + bgp_path_info_display(path, vty, json_path); + if (json) json_object_array_add(paths, json_path); - continue; - } - if (dest->pdest) { - vty_out(vty, " %d/%d %pBD RD ", afi, safi, dest); - vty_out(vty, BGP_RD_AS_FORMAT(bgp->asnotation), - (struct prefix_rd *)bgp_dest_get_prefix( - dest->pdest)); - vty_out(vty, " %s flags 0x%x\n", bgp_path->name_pretty, - path->flags); - } else - vty_out(vty, " %d/%d %pBD %s flags 0x%x\n", - afi, safi, dest, bgp_path->name_pretty, path->flags); } if (json) json_object_object_add(json, "paths", paths); diff --git a/bgpd/bgp_nhg.c b/bgpd/bgp_nhg.c index bf0ba77444e3..e6abd2ee4f24 100644 --- a/bgpd/bgp_nhg.c +++ b/bgpd/bgp_nhg.c @@ -5,11 +5,28 @@ */ #include +#include "memory.h" +#include "jhash.h" #include #include +#include #include +#include +#include +#include +#include "bgpd/bgp_nhg_clippy.c" + +extern struct zclient *zclient; + +DEFINE_MTYPE_STATIC(BGPD, BGP_NHG_CONNECTED, "BGP NHG Connected"); +DEFINE_MTYPE_STATIC(BGPD, BGP_NHG_PEER, "BGP NHG Peer"); + +/* BGP NHG hash table. */ +struct bgp_nhg_cache_head nhg_cache_table; +/* BGP NHG parent hash table ordered by ids. */ +struct bgp_nhg_parent_cache_head nhg_parent_cache_table; /**************************************************************************** * L3 NHGs are used for fast failover of nexthops in the dplane. These are @@ -58,6 +75,212 @@ static void bgp_nhg_zebra_init(void) bgp_nhg_del_cb); } +/* BGP NHGs are either child or parent nhgs. To reflect dependency between a parent + * and its childs (and reversely reflect that a child is used by a list of parents), + * a tree of 'bgp_nhg_connected' structures are used. The below API is used to + * handle those structures. + */ +static void bgp_nhg_connected_free(struct bgp_nhg_connected *dep) +{ + XFREE(MTYPE_BGP_NHG_CONNECTED, dep); +} + +static struct bgp_nhg_connected *bgp_nhg_connected_new(struct bgp_nhg_cache *nhg) +{ + struct bgp_nhg_connected *new = NULL; + + new = XCALLOC(MTYPE_BGP_NHG_CONNECTED, sizeof(struct bgp_nhg_connected)); + new->nhg = nhg; + + return new; +} + +static __attribute__((__unused__)) bool +bgp_nhg_connected_tree_is_empty(const struct bgp_nhg_connected_tree_head *head) +{ + return bgp_nhg_connected_tree_count(head) ? false : true; +} + +struct bgp_nhg_cache *bgp_nhg_connected_tree_del_nhg(struct bgp_nhg_connected_tree_head *head, + struct bgp_nhg_cache *depend) +{ + struct bgp_nhg_connected lookup = {}; + struct bgp_nhg_connected *remove = NULL; + struct bgp_nhg_cache *removed_nhg; + + lookup.nhg = depend; + + /* Lookup to find the element, then remove it */ + remove = bgp_nhg_connected_tree_find(head, &lookup); + if (remove) + /* Re-returning here just in case this API changes.. + * the _del list api's are a bit undefined at the moment. + * + * So hopefully returning here will make it fail if the api + * changes to something different than currently expected. + */ + remove = bgp_nhg_connected_tree_del(head, remove); + + /* If the entry was sucessfully removed, free the 'connected` struct */ + if (remove) { + removed_nhg = remove->nhg; + bgp_nhg_connected_free(remove); + return removed_nhg; + } + + return NULL; +} + +/* Assuming UNIQUE RB tree. If this changes, assumptions here about + * insertion need to change. + */ +struct bgp_nhg_cache *bgp_nhg_connected_tree_add_nhg(struct bgp_nhg_connected_tree_head *head, + struct bgp_nhg_cache *depend) +{ + struct bgp_nhg_connected *new = NULL; + + new = bgp_nhg_connected_new(depend); + + /* On success, NULL will be returned from the + * RB code. + */ + if (new && (bgp_nhg_connected_tree_add(head, new) == NULL)) + return NULL; + + /* If it wasn't successful, it must be a duplicate. We enforce the + * unique property for the `nhg_connected` tree. + */ + bgp_nhg_connected_free(new); + + return depend; +} + +static __attribute__((__unused__)) unsigned int bgp_nhg_childs_count(const struct bgp_nhg_cache *nhg) +{ + return bgp_nhg_connected_tree_count(&nhg->nhg_childs); +} + +static __attribute__((__unused__)) bool bgp_nhg_childs_is_empty(const struct bgp_nhg_cache *nhg) +{ + return bgp_nhg_connected_tree_is_empty(&nhg->nhg_childs); +} + +static __attribute__((__unused__)) void bgp_nhg_childs_del(struct bgp_nhg_cache *from, + struct bgp_nhg_cache *depend) +{ + bgp_nhg_connected_tree_del_nhg(&from->nhg_childs, depend); +} + +static void bgp_nhg_childs_init(struct bgp_nhg_cache *nhg) +{ + bgp_nhg_connected_tree_init(&nhg->nhg_childs); +} + +static __attribute__((__unused__)) unsigned int bgp_nhg_parents_count(const struct bgp_nhg_cache *nhg) +{ + return bgp_nhg_connected_tree_count(&nhg->nhg_parents); +} + +static __attribute__((__unused__)) void bgp_nhg_parents_del(struct bgp_nhg_cache *from, + struct bgp_nhg_cache *dependent) +{ + bgp_nhg_connected_tree_del_nhg(&from->nhg_parents, dependent); +} + +static void bgp_nhg_parents_init(struct bgp_nhg_cache *nhg) +{ + bgp_nhg_connected_tree_init(&nhg->nhg_parents); +} + +/* BGP NHGs may be used by multiple peers. To reflect that a nexthop belongs to + * multiple peers, a RBTree list indexed by peer is created in each child nexthop group. + * The inner structure will store the number of paths from that peer using that nexthop. + */ +static void bgp_nhg_peer_cache_free(struct bgp_nhg_peer_cache *p) +{ + XFREE(MTYPE_BGP_NHG_PEER, p); +} + +static struct bgp_nhg_peer_cache *bgp_nhg_peer_cache_new(struct peer *peer) +{ + struct bgp_nhg_peer_cache *new = NULL; + + new = XCALLOC(MTYPE_BGP_NHG_PEER, sizeof(struct bgp_nhg_peer_cache)); + new->peer = peer; + + return new; +} + +static unsigned int bgp_nhg_peers_count(const struct bgp_nhg_cache *nhg) +{ + return bgp_nhg_peer_cache_tree_count(&nhg->peers); +} + +static struct bgp_nhg_peer_cache * +bgp_nhg_peer_cache_tree_add_peer(struct bgp_nhg_peer_cache_tree_head *head, struct peer *peer) +{ + struct bgp_nhg_peer_cache *new = NULL; + + new = bgp_nhg_peer_cache_new(peer); + + /* On success, NULL will be returned from the + * RB code. + */ + if (new && (bgp_nhg_peer_cache_tree_add(head, new) == NULL)) + return new; + + /* If it wasn't successful, it must be a duplicate. We enforce the + * unique property for the `nhg_connected` tree. + */ + bgp_nhg_peer_cache_free(new); + + return NULL; +} + +static void bgp_nhg_peer_cache_init(struct bgp_nhg_cache *nhg) +{ + bgp_nhg_peer_cache_tree_init(&nhg->peers); +} + +/* return value returns -1 in case operation failed + * upon success, two values may be returned + * - return 1 if a struct bgp_nhg_peer_cache has been allocated or removed + * - return 0 otherwise + */ +int bgp_nhg_peer_cache_tree_update_path_count(struct bgp_nhg_cache *nhg, struct peer *peer, bool add) +{ + struct bgp_nhg_peer_cache *p = NULL; + struct bgp_nhg_peer_cache lookup; + struct bgp_nhg_peer_cache_tree_head *head = &nhg->peers; + + lookup.peer = peer; + + /* Lookup to find the element, then remove it */ + p = bgp_nhg_peer_cache_tree_find(head, &lookup); + if (p) { + if (add == false) { + p->path_count--; + if (p->path_count == 0) { + bgp_nhg_peer_cache_tree_del(head, p); + bgp_nhg_peer_cache_free(p); + return 1; + } + } else + p->path_count++; + return 0; + } + if (add == false) + return -1; + + p = bgp_nhg_peer_cache_tree_add_peer(head, peer); + if (p) { + p->path_count++; + return 1; + } + + return -1; +} + void bgp_nhg_init(void) { uint32_t id_max; @@ -69,6 +292,11 @@ void bgp_nhg_init(void) if (BGP_DEBUG(nht, NHT) || BGP_DEBUG(evpn_mh, EVPN_MH_ES)) zlog_debug("bgp nhg range %u - %u", bgp_nhg_start + 1, bgp_nhg_start + id_max); + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) + zlog_debug("bgp nexthop group init"); + + bgp_nhg_cache_init(&nhg_cache_table); + bgp_nhg_parent_cache_init(&nhg_parent_cache_table); } void bgp_nhg_finish(void) @@ -97,3 +325,1034 @@ void bgp_nhg_id_free(uint32_t nhg_id) bf_release_index(bgp_nh_id_bitmap, nhg_id); } + +void bgp_nhg_debug_parent(uint32_t child_ids[], int count, char *group_buf, size_t len) +{ + int i; + char *ptr = group_buf; + size_t written_len = 0; + + group_buf[0] = '\0'; + for (i = 0; i < count; i++) { + written_len += snprintf(ptr, len - written_len, "%u", child_ids[i]); + ptr = group_buf + written_len; + if (i + 1 < count) { + written_len += snprintf(ptr, len - written_len, ", "); + ptr = group_buf + written_len; + } + } +} + +/* display in a debug trace BGP NHG information, with a custom 'prefix' string */ +static void bgp_nhg_debug(struct bgp_nhg_cache *nhg, const char *prefix) +{ + char nexthop_buf[BGP_NEXTHOP_BUFFER_SIZE]; + + if (CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_TYPE_PARENT)) { + bgp_nhg_debug_parent(nhg->childs.childs, nhg->childs.child_num, nexthop_buf, + sizeof(nexthop_buf)); + zlog_debug("NHG %u: child %s (%s)", nhg->id, prefix, nexthop_buf); + return; + } + if (!nhg->nexthops.nexthop_num) + return; + + if (nhg->nexthops.nexthop_num > 1) { + zlog_debug("NHG %u: %s", nhg->id, prefix); + bgp_debug_zebra_nh(nhg->nexthops.nexthops, nhg->nexthops.nexthop_num); + return; + } + bgp_debug_zebra_nh_buffer(&nhg->nexthops.nexthops[0], nexthop_buf, sizeof(nexthop_buf)); + zlog_debug("NHG %u: %s (%s)", nhg->id, prefix, nexthop_buf); +} + +static struct bgp_nhg_cache *bgp_nhg_find_per_id(uint32_t id) +{ + struct bgp_nhg_cache *nhg; + struct bgp_nhg_cache nhg_tmp = { 0 }; + + /* parse list of childs */ + frr_each_safe (bgp_nhg_cache, &nhg_cache_table, nhg) { + if (nhg->id == id) + return nhg; + } + + /* parse list of parents */ + nhg_tmp.id = id; + return bgp_nhg_parent_cache_find(&nhg_parent_cache_table, &nhg_tmp); +} + +/* compare parent child list and return true if same */ +static bool bgp_nhg_parent_cache_compare_childs(const struct bgp_nhg_cache *a, + const struct bgp_nhg_cache *b) +{ + int i; + + if (a->childs.child_num != b->childs.child_num) + return false; + + for (i = 0; i < a->childs.child_num; i++) { + if (a->childs.childs[i] != b->childs.childs[i]) + return false; + } + return true; +} + +struct bgp_nhg_cache *bgp_nhg_parent_find_per_child(struct bgp_path_info *p_mpinfo[], + uint32_t *valid_nh_count, + struct bgp_nhg_cache *lookup) +{ + struct bgp_nhg_cache *nhg; + unsigned int i; + + /* look in current pi->bgp_nhg in priority */ + for (i = 0; i < *valid_nh_count; i++) { + if (p_mpinfo[i] && p_mpinfo[i]->bgp_nhg && + bgp_nhg_parent_cache_compare_childs(lookup, p_mpinfo[i]->bgp_nhg)) + return p_mpinfo[i]->bgp_nhg; + } + /* get first matching nhg */ + frr_each_safe (bgp_nhg_parent_cache, &nhg_parent_cache_table, nhg) { + if (bgp_nhg_parent_cache_compare_childs(lookup, nhg)) + return nhg; + } + return NULL; +} + +int bgp_nhg_parent_cache_compare(const struct bgp_nhg_cache *a, const struct bgp_nhg_cache *b) +{ + if (a->id != b->id) + return a->id - b->id; + return 0; +} + +uint32_t bgp_nhg_cache_hash(const struct bgp_nhg_cache *nhg) +{ + if (CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_TYPE_PARENT)) + return jhash_1word((uint32_t)nhg->childs.child_num, 0x55aa5a5a); + return jhash_1word((uint32_t)nhg->nexthops.nexthop_num, 0x55aa5a5a); +} + +uint32_t bgp_nhg_cache_compare(const struct bgp_nhg_cache *a, const struct bgp_nhg_cache *b) +{ + int i, ret; + + if (a->flags != b->flags) + return a->flags - b->flags; + + if (CHECK_FLAG(a->flags, BGP_NHG_FLAG_TYPE_PARENT)) { + for (i = 0; i < a->childs.child_num; i++) { + if (a->childs.childs[i] != b->childs.childs[i]) + return a->childs.childs[i] - b->childs.childs[i]; + } + return 0; + } + + for (i = 0; i < a->nexthops.nexthop_num; i++) { + ret = zapi_nexthop_cmp(&a->nexthops.nexthops[i], &b->nexthops.nexthops[i]); + if (ret) + return ret; + } + return 0; +} + +/* return the first nexthop-vrf available, VRF_DEFAULT otherwise */ +static vrf_id_t bgp_nhg_get_vrfid(struct bgp_nhg_cache *nhg) +{ + vrf_id_t vrf_id = VRF_DEFAULT; + int i = 0; + struct bgp_nhg_cache *child_nhg; + + if (CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_TYPE_PARENT)) { + for (i = 0; i < nhg->childs.child_num; i++) { + child_nhg = bgp_nhg_find_per_id(nhg->childs.childs[i]); + if (child_nhg) + return bgp_nhg_get_vrfid(child_nhg); + } + } + + for (i = 0; i < nhg->nexthops.nexthop_num; i++) + return nhg->nexthops.nexthops[i].vrf_id; + + return vrf_id; +} + +static bool bgp_nhg_add_or_update_nhg_group(struct bgp_nhg_cache *bgp_nhg, + struct zapi_nhg_group *api_nhg_group) +{ + bool ret = true; + int i; + struct bgp_nhg_cache *child_nhg; + + api_nhg_group->id = bgp_nhg->id; + api_nhg_group->child_group_num = 0; + for (i = 0; i < bgp_nhg->childs.child_num; i++) { + if (api_nhg_group->child_group_num >= MULTIPATH_NUM) { + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_warn("%s: number of nexthops greater than max multipath size, truncating", + __func__); + break; + } + child_nhg = bgp_nhg_find_per_id(bgp_nhg->childs.childs[i]); + if (!child_nhg || !CHECK_FLAG(child_nhg->state, BGP_NHG_STATE_INSTALLED)) { + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_warn("%s: nhg %u not sent, child NHG ID %u not present or not installed.", + __func__, bgp_nhg->id, bgp_nhg->childs.childs[i]); + continue; + } + api_nhg_group->child_group_id[i] = bgp_nhg->childs.childs[i]; + api_nhg_group->child_group_num++; + } + if (api_nhg_group->child_group_num == 0) { + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug("%s: nhg %u not sent: no valid groups", __func__, + api_nhg_group->id); + ret = false; + } + return ret; +} + +static bool bgp_nhg_add_or_update_nhg_nexthop(struct bgp_nhg_cache *bgp_nhg, + struct zapi_nhg *api_nhg) +{ + int i; + + if (bgp_nhg->nexthops.nexthop_num == 0) { + /* assumption that dependent nhg are removed before when id is installed */ + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug("%s: nhg %u not sent: no valid nexthops", __func__, bgp_nhg->id); + return false; + } + + api_nhg->id = bgp_nhg->id; + + for (i = 0; i < bgp_nhg->nexthops.nexthop_num; i++) { + if (api_nhg->nexthop_num >= MULTIPATH_NUM) { + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_warn("%s: number of nexthops greater than maximum number of multipathes, discard some nexthops.", + __func__); + break; + } + memcpy(&api_nhg->nexthops[api_nhg->nexthop_num], &bgp_nhg->nexthops.nexthops[i], + sizeof(struct zapi_nexthop)); + api_nhg->nexthop_num++; + } + return true; +} + +static void bgp_nhg_add_or_update_nhg(struct bgp_nhg_cache *bgp_nhg) +{ + struct zapi_nhg api_nhg = {}; + struct zapi_nhg_group api_nhg_group = {}; + uint32_t message = 0; + uint8_t flags = 0; + + if (CHECK_FLAG(bgp_nhg->flags, BGP_NHG_FLAG_ALLOW_RECURSION)) + SET_FLAG(flags, NEXTHOP_GROUP_ALLOW_RECURSION); + + if (CHECK_FLAG(bgp_nhg->flags, BGP_NHG_FLAG_SRTE_PRESENCE)) + SET_FLAG(message, ZAPI_MESSAGE_SRTE); + + if (CHECK_FLAG(bgp_nhg->flags, BGP_NHG_FLAG_IBGP)) + SET_FLAG(flags, NEXTHOP_GROUP_IBGP); + + if (CHECK_FLAG(bgp_nhg->flags, BGP_NHG_FLAG_TYPE_PARENT)) { + if (bgp_nhg_add_or_update_nhg_group(bgp_nhg, &api_nhg_group)) { + api_nhg_group.flags = flags; + api_nhg_group.message = message; + zclient_nhg_child_send(zclient, ZEBRA_NHG_CHILD_ADD, &api_nhg_group); + } + return; + } + + api_nhg.flags = flags; + api_nhg.message = message; + + if (bgp_nhg_add_or_update_nhg_nexthop(bgp_nhg, &api_nhg)) + zclient_nhg_send(zclient, ZEBRA_NHG_ADD, &api_nhg); +} + +struct bgp_nhg_cache *bgp_nhg_new(uint32_t flags, uint16_t num, struct zapi_nexthop api_nh[], + uint32_t api_group[]) +{ + struct bgp_nhg_cache *nhg; + int i; + + nhg = XCALLOC(MTYPE_BGP_NHG_CACHE, sizeof(struct bgp_nhg_cache)); + if (CHECK_FLAG(flags, BGP_NHG_FLAG_TYPE_PARENT)) { + for (i = 0; i < num; i++) + nhg->childs.childs[i] = api_group[i]; + nhg->childs.child_num = num; + } else { + for (i = 0; i < num; i++) + memcpy(&nhg->nexthops.nexthops[i], &api_nh[i], sizeof(struct zapi_nexthop)); + nhg->nexthops.nexthop_num = num; + } + nhg->flags = flags; + + nhg->id = bgp_nhg_id_alloc(); + + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) + bgp_nhg_debug(nhg, "creation"); + + LIST_INIT(&(nhg->paths)); + bgp_nhg_parents_init(nhg); + bgp_nhg_childs_init(nhg); + bgp_nhg_peer_cache_init(nhg); + if (CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_TYPE_PARENT)) + bgp_nhg_parent_cache_add(&nhg_parent_cache_table, nhg); + else + bgp_nhg_cache_add(&nhg_cache_table, nhg); + + /* prepare the nexthop */ + bgp_nhg_add_or_update_nhg(nhg); + + return nhg; +} + +/* Called when a child nexthop-group must be detached from parent: + * The child is detached from the parent. The parent is marked as updated + * if the number of childs is not 0. + */ +static void bgp_nhg_detach_child_from_parent(struct bgp_nhg_cache *nhg, + struct bgp_nhg_cache *nhg_child) +{ + int i, j; + struct bgp_path_info *path, *safe; + char nexthop_buf[BGP_NEXTHOP_BUFFER_SIZE]; + bool same_nexthop = true; + + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) { + bgp_debug_zebra_nh_buffer(&nhg_child->nexthops.nexthops[0], nexthop_buf, + sizeof(nexthop_buf)); + zlog_debug("NHG %u: detaching ID %u nexthop (%s)", nhg->id, nhg_child->id, + nexthop_buf); + } + + j = 0; + for (i = 0; i < nhg->childs.child_num; i++) { + if (i != j && nhg->childs.childs[j] != nhg->childs.childs[i]) { + same_nexthop = false; + break; + } + } + + for (i = 0; i < nhg->childs.child_num; i++) { + if (nhg->childs.childs[i] == nhg_child->id) + break; + } + assert(i != nhg->childs.child_num); + + if (i < nhg->childs.child_num - 1) { + for (j = i + 1; j < nhg->childs.child_num; j++) + nhg->childs.childs[j - 1] = nhg->childs.childs[j]; + } + nhg->childs.childs[nhg->childs.child_num - 1] = 0; + nhg->childs.child_num--; + + LIST_FOREACH_SAFE (path, &(nhg->paths), nhg_cache_thread, safe) { + if (!path->bgp_nhg_nexthop) { + LIST_REMOVE(path, nhg_cache_thread); + path->bgp_nhg = NULL; + nhg->path_count--; + } + } + + /* remove it from original nhg */ + if (same_nexthop == false || nhg->childs.child_num == 0) { + bgp_nhg_parents_del(nhg_child, nhg); + bgp_nhg_childs_del(nhg, nhg_child); + } + /* sort to always send ordered information to zebra */ + bgp_nhg_parent_sort(nhg->childs.childs, nhg->childs.child_num); + + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) { + zlog_debug("NHG %u: detached ID %u nexthop (%s) (%u -> %u)", nhg->id, nhg_child->id, + nexthop_buf, nhg->childs.child_num + 1, nhg->childs.child_num); + } + + if (nhg->childs.child_num) { + SET_FLAG(nhg->state, BGP_NHG_STATE_UPDATED); + bgp_nhg_add_or_update_nhg(nhg); + } +} + +static void bgp_nhg_connected_del(struct bgp_nhg_cache *nhg) +{ + struct bgp_nhg_connected *rb_node_dep = NULL; + + frr_each_safe (bgp_nhg_connected_tree, &(nhg->nhg_parents), rb_node_dep) { + bgp_nhg_childs_del(rb_node_dep->nhg, nhg); + bgp_nhg_parents_del(nhg, rb_node_dep->nhg); + } + + frr_each_safe (bgp_nhg_connected_tree, &(nhg->nhg_childs), rb_node_dep) { + bgp_nhg_parents_del(rb_node_dep->nhg, nhg); + bgp_nhg_childs_del(nhg, rb_node_dep->nhg); + } +} + +void bgp_nhg_free(struct bgp_nhg_cache *nhg) +{ + struct zapi_nhg api_nhg = {}; + + bgp_nhg_connected_del(nhg); + + api_nhg.id = nhg->id; + + if (api_nhg.id) + zclient_nhg_send(zclient, ZEBRA_NHG_DEL, &api_nhg); + + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) + bgp_nhg_debug(nhg, "removal"); + + if (CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_TYPE_PARENT)) + bgp_nhg_parent_cache_del(&nhg_parent_cache_table, nhg); + else + bgp_nhg_cache_del(&nhg_cache_table, nhg); + XFREE(MTYPE_BGP_NHG_CACHE, nhg); +} + +/* Used to sync the BGP NHG Parent hash table with the BGP NHG hash table. After a + * child group is removed, some BGP NHG group may be empty. This function detects it + * and frees it + */ +static void bgp_nhg_parent_unused_clean(void) +{ + struct bgp_nhg_cache *nhg; + + frr_each_safe (bgp_nhg_parent_cache, &nhg_parent_cache_table, nhg) { + if (nhg->childs.child_num) + continue; + if (LIST_EMPTY(&(nhg->paths))) + bgp_nhg_free(nhg); + } +} + +void bgp_nhg_path_nexthop_unlink(struct bgp_path_info *pi, bool force) +{ + struct bgp_nhg_cache *nhg_nexthop; + + nhg_nexthop = pi->bgp_nhg_nexthop; + if (nhg_nexthop) { + /* detach nexthop */ + LIST_REMOVE(pi, nhg_nexthop_cache_thread); + pi->bgp_nhg_nexthop->path_count--; + bgp_nhg_peer_cache_tree_update_path_count(pi->bgp_nhg_nexthop, pi->peer, false); + if (force && LIST_EMPTY(&(nhg_nexthop->paths))) + bgp_nhg_free(nhg_nexthop); + pi->bgp_nhg_nexthop = NULL; + } +} + +static void bgp_nhg_path_unlink_internal(struct bgp_path_info *pi, bool free_nhg) +{ + struct bgp_nhg_cache *nhg; + + if (!pi) + return; + + nhg = pi->bgp_nhg; + + bgp_nhg_path_nexthop_unlink(pi, true); + + if (!nhg) + return; + + LIST_REMOVE(pi, nhg_cache_thread); + nhg->path_count--; + pi->bgp_nhg = NULL; + if (LIST_EMPTY(&(nhg->paths)) && free_nhg) + bgp_nhg_free(nhg); +} + +void bgp_nhg_path_unlink(struct bgp_path_info *pi) +{ + return bgp_nhg_path_unlink_internal(pi, true); +} + +void bgp_nhg_parent_link(struct bgp_nhg_cache *nhg_childs[], int nexthop_num, + struct bgp_nhg_cache *nhg_parent) +{ + int i; + + /* updates NHG dependencies */ + for (i = 0; i < nexthop_num; i++) { + bgp_nhg_connected_tree_add_nhg(&nhg_parent->nhg_childs, nhg_childs[i]); + bgp_nhg_connected_tree_add_nhg(&nhg_childs[i]->nhg_parents, nhg_parent); + } +} + +/* called when ZEBRA notified the BGP NHG id is installed */ +void bgp_nhg_id_set_installed(uint32_t id) +{ + static struct bgp_nhg_cache *nhg; + struct bgp_path_info *path; + struct bgp_table *table; + struct bgp_nhg_connected *rb_node_dep = NULL; + + nhg = bgp_nhg_find_per_id(id); + if (nhg == NULL) + return; + SET_FLAG(nhg->state, BGP_NHG_STATE_INSTALLED); + + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) + zlog_debug("NHG %u: ID is installed, update dependent NHGs", nhg->id); + + frr_each_safe (bgp_nhg_connected_tree, &nhg->nhg_parents, rb_node_dep) { + bgp_nhg_add_or_update_nhg(rb_node_dep->nhg); + } + + if (!CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_TYPE_PARENT)) + return; + + /* only update routes if it is a parent nhg */ + if (CHECK_FLAG(nhg->state, BGP_NHG_STATE_UPDATED)) { + UNSET_FLAG(nhg->state, BGP_NHG_STATE_UPDATED); + return; + } + + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) + zlog_debug("NHG %u: ID is installed, update dependent routes", nhg->id); + LIST_FOREACH (path, &(nhg->paths), nhg_cache_thread) { + if (!CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) + continue; + table = bgp_dest_table(path->net); + if (table) + bgp_zebra_route_install(path->net, path, table->bgp, true, NULL, false); + } +} + +/* called when ZEBRA notified the BGP NHG id is removed */ +void bgp_nhg_id_set_removed(uint32_t id) +{ + static struct bgp_nhg_cache *nhg; + struct bgp_nhg_connected *rb_node_dep = NULL; + + nhg = bgp_nhg_find_per_id(id); + if (nhg == NULL) + return; + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) + zlog_debug("NHG %u: ID is uninstalled, update dependent NHGs", nhg->id); + UNSET_FLAG(nhg->state, BGP_NHG_STATE_INSTALLED); + SET_FLAG(nhg->state, BGP_NHG_STATE_REMOVED); + frr_each_safe (bgp_nhg_connected_tree, &nhg->nhg_parents, rb_node_dep) + bgp_nhg_add_or_update_nhg(rb_node_dep->nhg); +} + +static void bgp_nhg_remove_nexthops(struct bgp_nhg_cache *nhg) +{ + struct bgp_nhg_connected *rb_node_dep = NULL; + struct bgp_nhg_cache *parent_nhg; + struct bgp_path_info *path, *safe; + + frr_each_safe (bgp_nhg_connected_tree, &nhg->nhg_parents, rb_node_dep) { + parent_nhg = rb_node_dep->nhg; + LIST_FOREACH_SAFE (path, &(parent_nhg->paths), nhg_cache_thread, safe) { + if (path->bgp_nhg_nexthop == nhg) { + LIST_REMOVE(path, nhg_cache_thread); + path->bgp_nhg = NULL; + parent_nhg->path_count--; + LIST_REMOVE(path, nhg_nexthop_cache_thread); + path->bgp_nhg_nexthop = NULL; + nhg->path_count--; + bgp_nhg_peer_cache_tree_update_path_count(nhg, path->peer, false); + } + } + bgp_nhg_detach_child_from_parent(parent_nhg, nhg); + } + if (LIST_EMPTY(&(nhg->paths))) + bgp_nhg_free(nhg); +} + +/* This function unlinks the BGP nexthop group cache of BGP paths in some cases: + * - when a BGP NHG is resolved over a default route + * - if the passed resolved_prefix is the prefix of the path (case recursive loop) + * + * Without BGP NHG, those checks are done in ZEBRA, function nexthop_active(), + * leading to not installing the route: + * - if resolve-via-default is unconfigured + * - if a recursive loop happens for non host route + * + * With BGP NHG, those checks are done in BGP in this function, + * the routes will not use the BGP nexthop-groups, and will use the old ZEBRA code check, + * if the prefix paths meet the unlink conditions explained previously. + * + * in: nhg, the bgp nexthop group cache entry + * in: resolved_prefix, the resolved prefix of the nexthop: NULL if default route. + * in: child, the child nexthop-group of the path + * out: return true if the nexthop group has no more paths and is freed, false otherwise + */ +static void bgp_nhg_detach_paths_resolved_over_prefix_internal(struct bgp_nhg_cache *nhg, + struct prefix *resolved_prefix, + struct bgp_nhg_cache *child) +{ + struct bgp_path_info *path, *safe; + const struct prefix *p; + bool is_default_path; + struct bgp_table *table; + + if (!resolved_prefix) + return; + + is_default_path = is_default_prefix(resolved_prefix); + + LIST_FOREACH_SAFE (path, &(nhg->paths), nhg_cache_thread, safe) { + if (path->bgp_nhg_nexthop != child) + continue; + p = bgp_dest_get_prefix(path->net); + if (is_default_path) { + /* disallow routes which resolve over default route + */ + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug(" :%s: %pFX Resolved against default route", + __func__, p); + } else if (prefix_same(resolved_prefix, p) && !is_host_route(p)) { + /* disallow non host routes with resolve over themselves + */ + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug(" %s: %pFX, Matched against ourself and prefix length is not max bit length", + __func__, p); + } else + continue; + /* nhg = pi->nhg is detached, + * nhg will not be suppressed when bgp_nhg_path_unlink() is called + */ + bgp_nhg_path_nexthop_unlink(path, false); + bgp_nhg_path_unlink_internal(path, false); + /* path should still be active */ + table = bgp_dest_get_bgp_table_info(path->net); + if (table->bgp) + bgp_zebra_route_install(path->net, path, table->bgp, true, NULL, false); + } +} + +static bool bgp_nhg_detach_paths_resolved_over_prefix(struct bgp_nhg_cache *nhg, + struct prefix *resolved_prefix) +{ + struct bgp_nhg_connected *rb_node_dep = NULL; + struct bgp_nhg_cache *parent_nhg; + + frr_each_safe (bgp_nhg_connected_tree, &nhg->nhg_parents, rb_node_dep) { + parent_nhg = rb_node_dep->nhg; + bgp_nhg_detach_paths_resolved_over_prefix_internal(parent_nhg, resolved_prefix, nhg); + } + if (LIST_EMPTY(&(nhg->paths))) { + bgp_nhg_free(nhg); + return true; + } + return false; +} + +void bgp_nhg_refresh_by_nexthop(struct bgp_nexthop_cache *bnc) +{ + struct bgp_nhg_cache *nhg; + int i; + struct zapi_nexthop *zapi_nh; + uint32_t srte_color = bnc->srte_color; + struct prefix *p = &bnc->prefix; + vrf_id_t vrf_id = bnc->bgp->vrf_id; + bool found; + + frr_each_safe (bgp_nhg_cache, &nhg_cache_table, nhg) { + found = false; + if (CHECK_FLAG(nhg->state, BGP_NHG_STATE_REMOVED)) + continue; + if (!CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_ALLOW_RECURSION)) + continue; + if ((srte_color && !CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_SRTE_PRESENCE)) || + (!srte_color && CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_SRTE_PRESENCE))) + continue; + for (i = 0; i < nhg->nexthops.nexthop_num; i++) { + zapi_nh = &nhg->nexthops.nexthops[i]; + if (zapi_nh->type == NEXTHOP_TYPE_IFINDEX || + zapi_nh->type == NEXTHOP_TYPE_BLACKHOLE) + continue; + if (srte_color && zapi_nh->srte_color != srte_color) + continue; + if (p->family == AF_INET && + (zapi_nh->type == NEXTHOP_TYPE_IPV4 || + zapi_nh->type == NEXTHOP_TYPE_IPV4_IFINDEX) && + IPV4_ADDR_SAME(&zapi_nh->gate.ipv4, &p->u.prefix4)) { + found = true; + break; + } + if (p->family == AF_INET6 && + (zapi_nh->type == NEXTHOP_TYPE_IPV6 || + zapi_nh->type == NEXTHOP_TYPE_IPV6_IFINDEX) && + IPV6_ADDR_SAME(&zapi_nh->gate.ipv6, &p->u.prefix6)) { + found = true; + break; + } + } + if (found) { + if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_VALID)) { + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) + zlog_debug("NHG %u, VRF %u : nexthop %pFX SRTE %u is invalid.", + nhg->id, vrf_id, p, srte_color); + bgp_nhg_remove_nexthops(nhg); + continue; + } + + if (bgp_nhg_detach_paths_resolved_over_prefix(nhg, &bnc->resolved_prefix)) + continue; + + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) + zlog_debug("NHG %u, VRF %u : nexthop %pFX SRTE %u has changed.", + nhg->id, vrf_id, p, srte_color); + bgp_nhg_add_or_update_nhg(nhg); + } + } + bgp_nhg_parent_unused_clean(); +} + +static void show_bgp_nhg_path_helper(struct vty *vty, json_object *paths, struct bgp_path_info *path) +{ + json_object *json_path = NULL; + + if (paths) + json_path = json_object_new_object(); + bgp_path_info_display(path, vty, json_path); + if (paths) + json_object_array_add(paths, json_path); +} + +static void show_bgp_nhg_id_helper_detail(struct vty *vty, struct bgp_nhg_cache *nhg, + json_object *json) +{ + struct bgp_path_info *path; + json_object *paths = NULL; + + if (json) + paths = json_object_new_array(); + else + vty_out(vty, " Paths:\n"); + + if (CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_TYPE_PARENT)) { + LIST_FOREACH (path, &(nhg->paths), nhg_cache_thread) + show_bgp_nhg_path_helper(vty, paths, path); + } else { + LIST_FOREACH (path, &(nhg->paths), nhg_nexthop_cache_thread) + show_bgp_nhg_path_helper(vty, paths, path); + } + + if (json) + json_object_object_add(json, "paths", paths); +} + +static void show_bgp_nhg_id_helper(struct vty *vty, struct bgp_nhg_cache *nhg, json_object *json, + bool detail) +{ + struct nexthop *nexthop; + json_object *json_entry; + json_object *json_array = NULL; + int i; + bool first; + struct bgp_nhg_connected *rb_node_dep = NULL; + struct bgp_nhg_peer_cache *rb_node_peer = NULL; + + if (json) { + json_object_int_add(json, "nhgId", nhg->id); + json_object_int_add(json, "pathCount", nhg->path_count); + json_object_int_add(json, "flagAllowRecursion", + CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_ALLOW_RECURSION)); + json_object_boolean_add(json, "flagAllowRecursion", + CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_ALLOW_RECURSION)); + json_object_boolean_add(json, "flagInternalBgp", + CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_IBGP)); + json_object_boolean_add(json, "flagSrtePresence", + CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_SRTE_PRESENCE)); + json_object_boolean_add(json, "flagTypeParent", + CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_TYPE_PARENT)); + json_object_boolean_add(json, "stateInstalled", + CHECK_FLAG(nhg->state, BGP_NHG_STATE_INSTALLED)); + json_object_boolean_add(json, "stateRemoved", + CHECK_FLAG(nhg->state, BGP_NHG_STATE_REMOVED)); + } else { + vty_out(vty, "ID: %u", nhg->id); + if (nhg->path_count) + vty_out(vty, ", #paths %u", nhg->path_count); + vty_out(vty, "\n"); + vty_out(vty, " Flags: 0x%04x", nhg->flags); + first = true; + if (CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_ALLOW_RECURSION)) { + vty_out(vty, " (allowRecursion"); + first = false; + } + if (CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_IBGP)) { + vty_out(vty, "%sinternalBgp", first ? " (" : ", "); + first = false; + } + if (CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_SRTE_PRESENCE)) { + vty_out(vty, "%sSrtePresence", first ? " (" : ", "); + first = false; + } + if (CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_TYPE_PARENT)) + vty_out(vty, "%sTypeParent", first ? " (" : ", "); + if (nhg->flags) + vty_out(vty, ")"); + vty_out(vty, "\n"); + + vty_out(vty, " State: 0x%04x", nhg->state); + first = true; + if (CHECK_FLAG(nhg->state, BGP_NHG_STATE_INSTALLED)) { + vty_out(vty, " (Installed"); + first = false; + } + if (CHECK_FLAG(nhg->state, BGP_NHG_STATE_REMOVED)) { + vty_out(vty, "%sRemoved", first ? " (" : ", "); + first = false; + } + if (CHECK_FLAG(nhg->state, BGP_NHG_STATE_UPDATED)) { + vty_out(vty, "%sUpdated", first ? " (" : ", "); + first = false; + } + if (nhg->state) + vty_out(vty, ")"); + vty_out(vty, "\n"); + } + + if (CHECK_FLAG(nhg->flags, BGP_NHG_FLAG_TYPE_PARENT)) { + if (bgp_nhg_childs_count(nhg)) { + if (json) { + json_object_int_add(json, "childListPeerCount", + nhg->childs.child_num); + json_object_int_add(json, "childListCount", + bgp_nhg_childs_count(nhg)); + json_array = json_object_new_array(); + } else { + vty_out(vty, " child list count %u", + bgp_nhg_childs_count(nhg)); + if (nhg->childs.child_num != bgp_nhg_childs_count(nhg)) + vty_out(vty, ", peer count %u", nhg->childs.child_num); + vty_out(vty, "\n"); + vty_out(vty, " child(s)"); + } + frr_each_safe (bgp_nhg_connected_tree, &nhg->nhg_childs, rb_node_dep) { + if (json) { + json_entry = json_object_new_object(); + json_object_int_add(json_entry, "Id", rb_node_dep->nhg->id); + json_object_array_add(json_array, json_entry); + } else { + vty_out(vty, " %u", rb_node_dep->nhg->id); + } + } + if (json_array) + json_object_object_add(json, "childList", json_array); + else + vty_out(vty, "\n"); + } + if (detail) + show_bgp_nhg_id_helper_detail(vty, nhg, json); + return; + } + + if (nhg->nexthops.nexthop_num && json) + json_array = json_object_new_array(); + + for (i = 0; i < nhg->nexthops.nexthop_num; i++) { + nexthop = nexthop_from_zapi_nexthop(&nhg->nexthops.nexthops[i]); + if (json) { + json_entry = json_object_new_object(); + nexthop_json_helper(json_entry, nexthop, true, AF_UNSPEC); + json_object_string_add(json_entry, "vrf", vrf_id_to_name(nexthop->vrf_id)); + json_object_array_add(json_array, json_entry); + } else { + if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) + vty_out(vty, " "); + else + /* Make recursive nexthops a bit more clear */ + vty_out(vty, " "); + nexthop_vty_helper(vty, nexthop, true, AF_UNSPEC); + vty_out(vty, "\n"); + } + nexthops_free(nexthop); + } + if (json_array) + json_object_object_add(json, "nexthops", json_array); + + if (bgp_nhg_parents_count(nhg)) { + if (json) { + json_object_int_add(json, "parentListCount", bgp_nhg_parents_count(nhg)); + json_array = json_object_new_array(); + } else { + vty_out(vty, " parent list count %u\n", bgp_nhg_parents_count(nhg)); + vty_out(vty, " parent(s)"); + } + frr_each_safe (bgp_nhg_connected_tree, &nhg->nhg_parents, rb_node_dep) { + if (json) { + json_entry = json_object_new_object(); + json_object_int_add(json_entry, "Id", rb_node_dep->nhg->id); + json_object_array_add(json_array, json_entry); + } else { + vty_out(vty, " %u", rb_node_dep->nhg->id); + } + } + if (json_array) + json_object_object_add(json, "parentList", json_array); + else + vty_out(vty, "\n"); + } + if (bgp_nhg_peers_count(nhg)) { + if (json) { + json_object_int_add(json, "peersListCount", bgp_nhg_peers_count(nhg)); + json_array = json_object_new_array(); + } else { + vty_out(vty, " peer(s) count %u\n", bgp_nhg_peers_count(nhg)); + vty_out(vty, " peers(s)"); + } + frr_each_safe (bgp_nhg_peer_cache_tree, &nhg->peers, rb_node_peer) { + if (json) { + json_entry = json_object_new_object(); + json_object_string_add(json_entry, "peerAddress", + rb_node_peer->peer->host); + json_object_int_add(json_entry, "peerCount", + rb_node_peer->path_count); + json_object_array_add(json_array, json_entry); + } else + vty_out(vty, " %s[%u]", rb_node_peer->peer->host, + rb_node_peer->path_count); + } + if (json_array) + json_object_object_add(json, "peerList", json_array); + else + vty_out(vty, "\n"); + } + + if (detail) + show_bgp_nhg_id_helper_detail(vty, nhg, json); +} + +static void show_bgp_nhg_id_list_helper(struct vty *vty, struct bgp_nhg_cache *nhg, + json_object *json_list, bool detail) +{ + json_object *json = NULL; + + if (json_list) + json = json_object_new_object(); + show_bgp_nhg_id_helper(vty, nhg, json, detail); + if (json_list) + json_object_array_add(json_list, json); +} + +DEFPY(show_ip_bgp_nhg, show_ip_bgp_nhg_cmd, + "show [ip] bgp [vrf ] nexthop-group [<(0-4294967295)>$id] [detail$detail] [json$uj]", + SHOW_STR IP_STR BGP_STR VRF_FULL_CMD_HELP_STR + "BGP nexthop-group table\n" + "Nexthop Group ID\n" + "Show detailed information\n" JSON_STR) +{ + json_object *json = NULL; + json_object *json_list = NULL; + struct vrf *vrf = NULL; + static struct bgp_nhg_cache *nhg; + + if (id) { + nhg = bgp_nhg_find_per_id(id); + if (!nhg) + return CMD_SUCCESS; + if (uj) + json = json_object_new_object(); + show_bgp_nhg_id_helper(vty, nhg, json, !!detail); + if (json) + vty_json(vty, json); + return CMD_SUCCESS; + } + + if (vrf_is_backend_netns() && (vrf_name || vrf_all)) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "VRF subcommand does not make any sense in netns based vrf's\n"); + return CMD_WARNING; + } + if (vrf_name) + vrf = vrf_lookup_by_name(vrf_name); + + if (uj) + json_list = json_object_new_array(); + + + frr_each_safe (bgp_nhg_cache, &nhg_cache_table, nhg) { + if (vrf && vrf->vrf_id != bgp_nhg_get_vrfid(nhg)) + continue; + show_bgp_nhg_id_list_helper(vty, nhg, json_list, !!detail); + } + frr_each_safe (bgp_nhg_parent_cache, &nhg_parent_cache_table, nhg) { + if (vrf && vrf->vrf_id != bgp_nhg_get_vrfid(nhg)) + continue; + show_bgp_nhg_id_list_helper(vty, nhg, json_list, !!detail); + } + if (json_list) + vty_json(vty, json_list); + return CMD_SUCCESS; +} + +/* remove nexthop nhg that are no more used */ +void bgp_nhg_clear_nhg_nexthop(void) +{ + struct bgp_nhg_connected *rb_node_dep = NULL; + struct bgp_nhg_cache *nhg, *child_nhg; + struct bgp_path_info *path, *safe; + char nexthop_buf[BGP_NEXTHOP_BUFFER_SIZE]; + + frr_each_safe (bgp_nhg_parent_cache, &nhg_parent_cache_table, nhg) { + frr_each_safe (bgp_nhg_connected_tree, &nhg->nhg_childs, rb_node_dep) { + child_nhg = rb_node_dep->nhg; + if (child_nhg && LIST_EMPTY(&(child_nhg->paths))) { + /* sync bgp_nhg paths */ + LIST_FOREACH_SAFE (path, &(nhg->paths), nhg_cache_thread, safe) { + if (!path->bgp_nhg_nexthop) { + LIST_REMOVE(path, nhg_cache_thread); + path->bgp_nhg = NULL; + nhg->path_count--; + } + } + + bgp_nhg_detach_child_from_parent(nhg, child_nhg); + } + } + } + bgp_nhg_parent_unused_clean(); + frr_each_safe (bgp_nhg_parent_cache, &nhg_parent_cache_table, nhg) { + if (nhg->childs.child_num != bgp_nhg_childs_count(nhg)) { + if (bgp_nhg_childs_count(nhg) != 1) + continue; + frr_each_safe (bgp_nhg_connected_tree, &nhg->nhg_childs, rb_node_dep) { + child_nhg = rb_node_dep->nhg; + if (bgp_nhg_peers_count(child_nhg) < nhg->childs.child_num) { + /* we can decrease the number of nexthops */ + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP)) { + bgp_debug_zebra_nh_buffer(&child_nhg->nexthops + .nexthops[0], + nexthop_buf, + sizeof(nexthop_buf)); + zlog_debug("NHG %u: peer count changed (%u -> %u) for nexthop (%s)", + child_nhg->id, nhg->childs.child_num, + bgp_nhg_peers_count(child_nhg), + nexthop_buf); + } + bgp_nhg_detach_child_from_parent(nhg, child_nhg); + } + } + } + } + bgp_nhg_parent_unused_clean(); +} + +void bgp_nhg_vty_init(void) +{ + install_element(VIEW_NODE, &show_ip_bgp_nhg_cmd); +} + +static int bgp_nhg_parent_compare(const void *a, const void *b) +{ + uint32_t *num1 = (uint32_t *)a, *num2 = (uint32_t *)b; + + return *num1 - *num2; +} +void bgp_nhg_parent_sort(uint32_t grp[], uint16_t nhg_num) +{ + qsort(grp, nhg_num, sizeof(uint32_t), &bgp_nhg_parent_compare); +} diff --git a/bgpd/bgp_nhg.h b/bgpd/bgp_nhg.h index 370e8ab09164..dcecef49b512 100644 --- a/bgpd/bgp_nhg.h +++ b/bgpd/bgp_nhg.h @@ -8,11 +8,114 @@ #define _BGP_NHG_H #include "nexthop_group.h" +#include "bgpd/bgp_table.h" + +PREDECL_HASH(bgp_nhg_cache); +PREDECL_RBTREE_UNIQ(bgp_nhg_parent_cache); +PREDECL_RBTREE_UNIQ(bgp_nhg_connected_tree); +PREDECL_RBTREE_UNIQ(bgp_nhg_peer_cache_tree); + +extern struct bgp_nhg_cache_head nhg_cache_table; +extern struct bgp_nhg_parent_cache_head nhg_parent_cache_table; + +struct bgp_nhg_nexthop_cache { + uint16_t nexthop_num; + struct zapi_nexthop nexthops[MULTIPATH_NUM]; +}; + +struct bgp_nhg_child_cache { + uint16_t child_num; + uint32_t childs[MULTIPATH_NUM]; +}; + +struct bgp_nhg_cache { + struct bgp_nhg_cache_item entry; + struct bgp_nhg_parent_cache_item parent_entry; + + uint32_t id; + + /* obtained from lib/zclient.h, zapi_route->flags + * some flags of interest for nexthop handling : + * ALLOW_RECURSION, IBGP + */ +#define BGP_NHG_FLAG_ALLOW_RECURSION (1 << 0) +#define BGP_NHG_FLAG_IBGP (1 << 1) +#define BGP_NHG_FLAG_SRTE_PRESENCE (1 << 2) +#define BGP_NHG_FLAG_TYPE_PARENT (1 << 3) + uint16_t flags; +#define BGP_NHG_STATE_INSTALLED (1 << 0) +#define BGP_NHG_STATE_REMOVED (1 << 1) +#define BGP_NHG_STATE_UPDATED (1 << 2) + uint16_t state; + + /* other parameters are route attributes and are not + * relevant for qualifying next-hop: + * tag, metric, distance + */ + union { + struct bgp_nhg_nexthop_cache nexthops; + struct bgp_nhg_child_cache childs; + }; + + LIST_HEAD(nhg_path_list, bgp_path_info) paths; + + unsigned int path_count; + time_t last_update; + + struct bgp_nhg_peer_cache_tree_head peers; + /* Dependency tree between parent nhg and child nhg: + * For instance, to represent 2 ECMP nexthops, + * 1 parent nhg entry (ID 3) and 2 child nhg (ID 1 and ID 2) + * entries are necessary. + * + * bgp_nhg(ID 3)->nhg_childs has ID 1 and ID 2 in the tree + * bgp_nhg(ID 3)->nhg_parents is empty + * + * bgp_nhg(ID 1)->nhg_childs is empty + * bgp_nhg(ID 1)->nhg_parents is ID 3 in the tree + * + * bgp_nhg(ID 2)->nhg_childs is empty + * bgp_nhg(ID 2)->nhg_parents is ID 3 in the tree + */ + struct bgp_nhg_connected_tree_head nhg_childs, nhg_parents; +}; + +extern uint32_t bgp_nhg_cache_hash(const struct bgp_nhg_cache *nhg); +extern uint32_t bgp_nhg_cache_compare(const struct bgp_nhg_cache *a, const struct bgp_nhg_cache *b); +DECLARE_HASH(bgp_nhg_cache, struct bgp_nhg_cache, entry, bgp_nhg_cache_compare, bgp_nhg_cache_hash); +extern int bgp_nhg_parent_cache_compare(const struct bgp_nhg_cache *a, + const struct bgp_nhg_cache *b); +DECLARE_RBTREE_UNIQ(bgp_nhg_parent_cache, struct bgp_nhg_cache, parent_entry, + bgp_nhg_parent_cache_compare); + +extern struct bgp_nhg_cache *bgp_nhg_parent_find_per_child(struct bgp_path_info *p_mpinfo[], + uint32_t *valid_nh_count, + struct bgp_nhg_cache *lookup); /* APIs for setting up and allocating L3 nexthop group ids */ extern uint32_t bgp_nhg_id_alloc(void); extern void bgp_nhg_id_free(uint32_t nhg_id); extern void bgp_nhg_init(void); void bgp_nhg_finish(void); +extern struct bgp_nhg_cache *bgp_nhg_find(struct bgp *bgp, struct bgp_dest *dest, + struct bgp_path_info *pi, afi_t afi, safi_t safi); +extern void bgp_nhg_path_unlink(struct bgp_path_info *pi); +extern void bgp_nhg_path_nexthop_unlink(struct bgp_path_info *pi, bool force); +extern void bgp_nhg_parent_link(struct bgp_nhg_cache *nhg_childs[], int nexthop_num, + struct bgp_nhg_cache *nhg_parent); + +extern struct bgp_nhg_cache *bgp_nhg_new(uint32_t flags, uint16_t num, struct zapi_nexthop api_nh[], + uint32_t api_group[]); +extern void bgp_nhg_free(struct bgp_nhg_cache *nhg); +extern void bgp_nhg_id_set_installed(uint32_t id); +extern void bgp_nhg_id_set_removed(uint32_t id); +extern void bgp_nhg_refresh_by_nexthop(struct bgp_nexthop_cache *bnc); +void bgp_nhg_vty_init(void); +void bgp_nhg_debug_parent(uint32_t child_ids[], int count, char *group_buf, size_t len); +void bgp_nhg_parent_sort(uint32_t child_ids[], uint16_t nhg_num); +void bgp_nhg_clear_nhg_nexthop(void); + +extern int bgp_nhg_peer_cache_tree_update_path_count(struct bgp_nhg_cache *nhg, struct peer *peer, + bool add); #endif /* _BGP_NHG_H */ diff --git a/bgpd/bgp_nhg_private.h b/bgpd/bgp_nhg_private.h new file mode 100644 index 000000000000..b95729430a4b --- /dev/null +++ b/bgpd/bgp_nhg_private.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Nexthop Group Private Functions. + * Copyright (C) 2024 6WIND + */ + +/** + * These functions should only be used internally for BGP NHG handling + * manipulation and in certain special cases. + * + * Please use `bgpd/bgp_nhg.h` for any general BGP NHG api need + */ + +#ifndef __BGP_NHG_PRIVATE_H__ +#define __BGP_NHG_PRIVATE_H__ + +#include "bgpd/bgp_nhg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Abstraction for BGP connected trees */ +struct bgp_nhg_connected { + struct bgp_nhg_connected_tree_item tree_item; + struct bgp_nhg_cache *nhg; +}; + +static int bgp_nhg_connected_cmp(const struct bgp_nhg_connected *con1, + const struct bgp_nhg_connected *con2) +{ + return (con1->nhg->id - con2->nhg->id); +} + +DECLARE_RBTREE_UNIQ(bgp_nhg_connected_tree, struct bgp_nhg_connected, tree_item, + bgp_nhg_connected_cmp); + +/* bgp nhg connected tree direct access functions */ +extern void bgp_nhg_connected_tree_init(struct bgp_nhg_connected_tree_head *head); +extern void bgp_nhg_connected_tree_free(struct bgp_nhg_connected_tree_head *head); + +/* I realize _add/_del returns are backwords. + * + * Currently the list APIs are not standardized for what happens in + * the _del() function when the item isn't present. + * + * We are choosing to return NULL if not found in the _del case for now. + */ + +/* Delete NHE from the tree. On success, return the NHE, otherwise NULL. */ +extern struct bgp_nhg_cache *bgp_nhg_connected_tree_del_nhg(struct bgp_nhg_connected_tree_head *head, + struct bgp_nhg_cache *nhg); +/* ADD NHE to the tree. On success, return NULL, otherwise return the NHE. */ +extern struct bgp_nhg_cache *bgp_nhg_connected_tree_add_nhg(struct bgp_nhg_connected_tree_head *head, + struct bgp_nhg_cache *nhe); + + +/* Abstraction for BGP peer trees */ +struct bgp_nhg_peer_cache { + struct bgp_nhg_peer_cache_tree_item entry; + struct peer *peer; + unsigned int path_count; +}; + +static int bgp_nhg_peer_cache_cmp(const struct bgp_nhg_peer_cache *p1, + const struct bgp_nhg_peer_cache *p2) +{ + return !(p1->peer == p2->peer); +} + +DECLARE_RBTREE_UNIQ(bgp_nhg_peer_cache_tree, struct bgp_nhg_peer_cache, entry, + bgp_nhg_peer_cache_cmp); + +/* bgp nhg peer cache tree direct access functions */ +extern void bgp_nhg_peer_cache_tree_init(struct bgp_nhg_peer_cache_tree_head *head); +extern void bgp_nhg_peer_cache_tree_free(struct bgp_nhg_peer_cache_tree_head *head); + + +#ifdef __cplusplus +} +#endif + +#endif /* __BGP_NHG_PRIVATE_H__ */ diff --git a/bgpd/bgp_nht.c b/bgpd/bgp_nht.c index 59566ee6d67e..bba07c479a78 100644 --- a/bgpd/bgp_nht.c +++ b/bgpd/bgp_nht.c @@ -26,6 +26,7 @@ #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_nht.h" +#include "bgpd/bgp_nhg.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_flowspec_util.h" @@ -48,6 +49,28 @@ static int bgp_isvalid_nexthop(struct bgp_nexthop_cache *bnc) && bnc->nexthop_num > 0)); } +/** + * evaluate_nexthops - if a nexthop change occurs at ZEBRA level, + * the NHG must be refreshed. + * ARGUMENTS: + * struct bgp_nexthop_cache *bnc -- the nexthop structure. + * RETURNS: + * void. + */ +static void evaluate_nexthops(struct bgp_nexthop_cache *bnc) +{ + if (!CHECK_FLAG(bnc->change_flags, BGP_NEXTHOP_CHANGED)) + return; + + if (BGP_DEBUG(nht, NHT)) + zlog_debug("%s: nexthop change detected for bnc %pFX(%d)(%u)(%s), refreshing NHGs", + __func__, &bnc->prefix, bnc->ifindex_ipv6_ll, bnc->srte_color, + bnc->bgp->name_pretty); + + if (bgp_option_check(BGP_OPT_NHG)) + bgp_nhg_refresh_by_nexthop(bnc); +} + static int bgp_isvalid_nexthop_for_ebgp(struct bgp_nexthop_cache *bnc, struct bgp_path_info *path) { @@ -168,6 +191,7 @@ void bgp_unlink_nexthop(struct bgp_path_info *path) bgp_mplsvpn_path_nh_label_unlink(path); bgp_mplsvpn_path_nh_label_bind_unlink(path); + bgp_nhg_path_unlink(path); if (!bnc) return; @@ -751,6 +775,7 @@ static void bgp_process_nexthop_update(struct bgp_nexthop_cache *bnc, bnc->nexthop = NULL; } + evaluate_nexthops(bnc); evaluate_paths(bnc); } diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index 6b116db1075d..45dd1e6df551 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -3918,7 +3918,7 @@ static int bgp_capability_msg_parse(struct peer *peer, uint8_t *pnt, peer->afc_nego[afi][safi] = 0; if (peer_active_nego(peer)) - bgp_clear_route(peer, afi, safi); + bgp_clear_route(peer, afi, safi, false); else goto done; } diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 551547b7c098..b8eadbc9bcbe 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -75,6 +75,7 @@ #include "bgpd/bgp_flowspec.h" #include "bgpd/bgp_flowspec_util.h" #include "bgpd/bgp_pbr.h" +#include "bgpd/bgp_nhg.h" #include "bgpd/bgp_route_clippy.c" @@ -3619,6 +3620,7 @@ static void bgp_process_main_one(struct bgp *bgp, struct bgp_dest *dest, struct bgp_path_info *old_select; struct bgp_path_info_pair old_and_new; int debug = 0; + struct bgp_path_info *pi, *nextpi; /* * For default bgp instance, which is deleted i.e. marked hidden @@ -3790,6 +3792,13 @@ static void bgp_process_main_one(struct bgp *bgp, struct bgp_dest *dest, UNSET_FLAG(new_select->flags, BGP_PATH_LINK_BW_CHG); } + for (pi = bgp_dest_get_bgp_path_info(dest); (pi != NULL) && (nextpi = pi->next, 1); + pi = nextpi) { + if (!CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) && + !CHECK_FLAG(pi->flags, BGP_PATH_MULTIPATH)) + bgp_nhg_path_unlink(pi); + } + /* call bmp hook for loc-rib route update / withdraw after flags were * set */ @@ -4316,6 +4325,7 @@ void bgp_rib_remove(struct bgp_dest *dest, struct bgp_path_info *pi, } } + bgp_nhg_path_nexthop_unlink(pi, false); hook_call(bgp_process, peer->bgp, afi, safi, dest, peer, true); bgp_process(peer->bgp, dest, pi, afi, safi); } @@ -6013,7 +6023,6 @@ static void bgp_clear_node_complete(struct work_queue *wq) /* Tickle FSM to start moving again */ BGP_EVENT_ADD(peer->connection, Clearing_Completed); - peer_unlock(peer); /* bgp_clear_route */ } @@ -6037,7 +6046,7 @@ static void bgp_clear_node_queue_init(struct peer *peer) } static void bgp_clear_route_table(struct peer *peer, afi_t afi, safi_t safi, - struct bgp_table *table) + struct bgp_table *table, bool nexthop_unlink) { struct bgp_dest *dest; int force = peer->bgp->process_queue ? 0 : 1; @@ -6106,6 +6115,9 @@ static void bgp_clear_route_table(struct peer *peer, afi_t afi, safi_t safi, if (pi->peer != peer) continue; + if (nexthop_unlink && !force) + bgp_nhg_path_nexthop_unlink(pi, false); + if (force) { dest = bgp_path_info_reap(dest, pi); assert(dest); @@ -6127,7 +6139,7 @@ static void bgp_clear_route_table(struct peer *peer, afi_t afi, safi_t safi, return; } -void bgp_clear_route(struct peer *peer, afi_t afi, safi_t safi) +void bgp_clear_route(struct peer *peer, afi_t afi, safi_t safi, bool nexthop_unlink) { struct bgp_dest *dest; struct bgp_table *table; @@ -6156,7 +6168,7 @@ void bgp_clear_route(struct peer *peer, afi_t afi, safi_t safi) peer_lock(peer); if (safi != SAFI_MPLS_VPN && safi != SAFI_ENCAP && safi != SAFI_EVPN) - bgp_clear_route_table(peer, afi, safi, NULL); + bgp_clear_route_table(peer, afi, safi, NULL, nexthop_unlink); else for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; dest = bgp_route_next(dest)) { @@ -6164,7 +6176,7 @@ void bgp_clear_route(struct peer *peer, afi_t afi, safi_t safi) if (!table) continue; - bgp_clear_route_table(peer, afi, safi, table); + bgp_clear_route_table(peer, afi, safi, table, nexthop_unlink); } /* unlock if no nodes got added to the clear-node-queue. */ @@ -6178,7 +6190,7 @@ void bgp_clear_route_all(struct peer *peer) safi_t safi; FOREACH_AFI_SAFI (afi, safi) - bgp_clear_route(peer, afi, safi); + bgp_clear_route(peer, afi, safi, true); #ifdef ENABLE_BGP_VNC rfapiProcessPeerDown(peer); @@ -6297,6 +6309,9 @@ void bgp_clear_stale_route(struct peer *peer, afi_t afi, safi_t safi) break; } } + /* all routes marked as remove - let us clean nhg context */ + if (bgp_option_check(BGP_OPT_NHG)) + bgp_nhg_clear_nhg_nexthop(); } void bgp_set_stale_route(struct peer *peer, afi_t afi, safi_t safi) diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h index 1df0ffd300e1..5766de32a1a9 100644 --- a/bgpd/bgp_route.h +++ b/bgpd/bgp_route.h @@ -17,6 +17,7 @@ struct bgp_nexthop_cache; struct bgp_route_evpn; +struct bgp_nhg_cache; enum bgp_show_type { bgp_show_type_normal, @@ -367,6 +368,18 @@ struct bgp_path_info { struct bgp_mplsvpn_label_nh blnc; struct bgp_mplsvpn_nh_label_bind bmnc; } mplsvpn; + + /* Back pointer to the BGP nhg structure */ + struct bgp_nhg_cache *bgp_nhg; + + /* For nexthop group cache linked list */ + LIST_ENTRY(bgp_path_info) nhg_cache_thread; + + /* Back pointer to the BGP nhg nexthop structure */ + struct bgp_nhg_cache *bgp_nhg_nexthop; + + /* For nexthop group cache linked list */ + LIST_ENTRY(bgp_path_info) nhg_nexthop_cache_thread; }; /* Structure used in BGP path selection */ @@ -744,7 +757,7 @@ extern void bgp_soft_reconfig_table_task_cancel(const struct bgp *bgp, * and return true. If it is not return false; and do nothing */ extern bool bgp_soft_reconfig_in(struct peer *peer, afi_t afi, safi_t safi); -extern void bgp_clear_route(struct peer *, afi_t, safi_t); +extern void bgp_clear_route(struct peer *peer, afi_t afi, safi_t safi, bool nexthop_unlink); extern void bgp_clear_route_all(struct peer *); extern void bgp_clear_adj_in(struct peer *, afi_t, safi_t); extern void bgp_clear_stale_route(struct peer *, afi_t, safi_t); diff --git a/bgpd/bgp_table.h b/bgpd/bgp_table.h index 130f5ca749e5..9aa83be0ba1c 100644 --- a/bgpd/bgp_table.h +++ b/bgpd/bgp_table.h @@ -98,6 +98,9 @@ struct bgp_dest { #define BGP_NODE_PROCESS_CLEAR (1 << 9) #define BGP_NODE_SCHEDULE_FOR_INSTALL (1 << 10) #define BGP_NODE_SCHEDULE_FOR_DELETE (1 << 11) +#define BGP_NODE_FIB_UPDATE_UNNEEDED (1 << 12) +/* this flag overrides previous one, when present */ +#define BGP_NODE_FIB_UPDATE_OVERRIDE (1 << 13) struct bgp_addpath_node_data tx_addpath; diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index 62b79541bf21..cbcea9836399 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -68,6 +68,7 @@ #ifdef ENABLE_BGP_VNC #include "bgpd/rfapi/bgp_rfapi_cfg.h" #endif +#include "bgpd/bgp_nhg.h" FRR_CFG_DEFAULT_BOOL(BGP_IMPORT_CHECK, { @@ -134,6 +135,7 @@ FRR_CFG_DEFAULT_BOOL(BGP_ENFORCE_FIRST_AS, { .val_bool = false, .match_version = "< 9.1", }, { .val_bool = true }, ); +FRR_CFG_DEFAULT_BOOL(BGP_NEXTHOP_GROUP, { .val_bool = false }, ); DEFINE_HOOK(bgp_inst_config_write, (struct bgp *bgp, struct vty *vty), @@ -2027,6 +2029,38 @@ DEFPY (no_bgp_send_extra_data, return CMD_SUCCESS; } +void bgp_nhg_configure_default(void) +{ + if (DFLT_BGP_NEXTHOP_GROUP) + bgp_option_set(BGP_OPT_NHG); +} + +DEFPY (bgp_nhg, + bgp_nhg_cmd, + "[no$no] bgp nexthop-group", + NO_STR + BGP_STR + "Enable nexthop-group support in BGP\n") +{ + if (no && !bgp_option_check(BGP_OPT_NHG)) { + vty_out(vty, "%% nexthop-group option is already unset, nothing to do here.\n"); + return CMD_SUCCESS; + } + if (!no && bgp_option_check(BGP_OPT_NHG)) { + vty_out(vty, "%% nexthop-group option is already set, nothing to do here.\n"); + return CMD_SUCCESS; + } + + if (no) + bgp_option_unset(BGP_OPT_NHG); + else + bgp_option_set(BGP_OPT_NHG); + + bgp_option_nhg_update(); + + return CMD_SUCCESS; +} + DEFUN (bgp_confederation_identifier, bgp_confederation_identifier_cmd, "bgp confederation identifier ASNUM", @@ -19415,6 +19449,11 @@ int bgp_config_write(struct vty *vty) if (bgp_option_check(BGP_OPT_NO_FIB)) vty_out(vty, "bgp no-rib\n"); + if (bgp_option_check(BGP_OPT_NHG) && !DFLT_BGP_NEXTHOP_GROUP) + vty_out(vty, "bgp nexthop-group\n"); + else if (!bgp_option_check(BGP_OPT_NHG) && DFLT_BGP_NEXTHOP_GROUP) + vty_out(vty, "no bgp nexthop-group\n"); + if (CHECK_FLAG(bm->flags, BM_FLAG_SEND_EXTRA_DATA_TO_ZEBRA)) vty_out(vty, "bgp send-extra-data zebra\n"); @@ -20436,6 +20475,9 @@ void bgp_vty_init(void) install_element(CONFIG_NODE, &bgp_norib_cmd); install_element(CONFIG_NODE, &no_bgp_norib_cmd); + /* "bgp nexthop-group" command. */ + install_element(CONFIG_NODE, &bgp_nhg_cmd); + install_element(CONFIG_NODE, &no_bgp_send_extra_data_cmd); /* "bgp confederation" commands. */ @@ -21952,6 +21994,7 @@ void bgp_vty_init(void) install_element(BGP_NODE, &no_bgp_sid_vpn_export_cmd); bgp_vty_if_init(); + bgp_nhg_vty_init(); } #include "memory.h" diff --git a/bgpd/bgp_vty.h b/bgpd/bgp_vty.h index f88f5c812569..c9559b380f0e 100644 --- a/bgpd/bgp_vty.h +++ b/bgpd/bgp_vty.h @@ -167,5 +167,6 @@ extern int bgp_show_summary_vty(struct vty *vty, const char *name, afi_t afi, extern bool peergroup_flag_check(struct peer *peer, uint64_t flag); extern bool peergroup_af_flag_check(struct peer *peer, afi_t afi, safi_t safi, uint64_t flag); +extern void bgp_nhg_configure_default(void); #endif /* _QUAGGA_BGP_VTY_H */ diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c index bffa5a0e6bf4..14ebad2fea48 100644 --- a/bgpd/bgp_zebra.c +++ b/bgpd/bgp_zebra.c @@ -37,6 +37,7 @@ #include "bgpd/bgp_errors.h" #include "bgpd/bgp_mpath.h" #include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_nhg.h" #include "bgpd/bgp_nht.h" #include "bgpd/bgp_bfd.h" #include "bgpd/bgp_label.h" @@ -1233,6 +1234,222 @@ static bool bgp_zebra_use_nhop_weighted(struct bgp *bgp, struct attr *attr, return true; } +static int bgp_nhg_notify_owner(ZAPI_CALLBACK_ARGS) +{ + enum zapi_nhg_notify_owner note; + uint32_t id; + + if (!zapi_nhg_notify_decode(zclient->ibuf, &id, ¬e)) + return -1; + + switch (note) { + case ZAPI_NHG_INSTALLED: + bgp_nhg_id_set_installed(id); + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Installed nhg %u", id); + break; + case ZAPI_NHG_FAIL_INSTALL: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Failed install of nhg %u", id); + break; + case ZAPI_NHG_REMOVED: + bgp_nhg_id_set_removed(id); + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Removed nhg %u", id); + break; + case ZAPI_NHG_REMOVE_FAIL: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Failed removal of nhg %u", id); + break; + } + + return 0; +} +static void bgp_zebra_nexthop_group_unlink(struct bgp_path_info *p_mpinfo[], + unsigned int *valid_nh_count) +{ + unsigned int i; + + for (i = 0; i < *valid_nh_count; i++) + bgp_nhg_path_unlink(p_mpinfo[i]); +} + +static void bgp_zebra_nexthop_group_configure(struct bgp_path_info *info, const struct prefix *p, + struct bgp *bgp, struct zapi_route *api, + unsigned int *valid_nh_count, uint32_t *nhg_id, + bool *allow_recursion, + struct bgp_path_info *p_mpinfo[]) + +{ + unsigned int i; + struct bgp_nhg_cache nhg = { 0 }, nhg_parent = { 0 }; + struct bgp_nhg_cache *p_nhg_childs[MULTIPATH_NUM], *p_nhg_parent; + bool creation = true; + char nexthop_buf[BGP_NEXTHOP_BUFFER_SIZE]; + bool route_need_update; + + for (i = 0; i < *valid_nh_count; i++) { + /* disallow nexthop interfaces from NHG (eg: mplsvpn routes) + * or blackhole routes + */ + if (api->nexthops[i].type == NEXTHOP_TYPE_IFINDEX || + api->nexthops[i].type == NEXTHOP_TYPE_BLACKHOLE) { + bgp_zebra_nexthop_group_unlink(p_mpinfo, valid_nh_count); + return; + } + /* disallow IPv4 Mapped IPv6 addresses + * it is not possible to attach IPv6 routes to IPv4 nexthops + */ + if (api->nexthops[i].type == NEXTHOP_TYPE_IPV6 && + IS_MAPPED_IPV6(&api->nexthops[i].gate.ipv6)) { + bgp_zebra_nexthop_group_unlink(p_mpinfo, valid_nh_count); + return; + } + /* disallow routes which resolve over blackhole routes + */ + if (p_mpinfo[i] && p_mpinfo[i]->nexthop && p_mpinfo[i]->nexthop->nexthop && + p_mpinfo[i]->nexthop->nexthop->type == NEXTHOP_TYPE_BLACKHOLE) { + bgp_zebra_nexthop_group_unlink(p_mpinfo, valid_nh_count); + return; + } + + if (!p_mpinfo[i] || !p_mpinfo[i]->nexthop) + continue; + + if (is_default_prefix(&p_mpinfo[i]->nexthop->resolved_prefix)) { + /* disallow routes which resolve over default route + */ + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug(" :%s: %pFX Resolved against default route", + __func__, p); + bgp_zebra_nexthop_group_unlink(p_mpinfo, valid_nh_count); + return; + } + if (prefix_same(p, &p_mpinfo[i]->nexthop->resolved_prefix) && !is_host_route(p)) { + /* disallow non host routes which resolve over themselves + */ + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug(" %s: %pFX, Matched against ourself and prefix length is not max bit length", + __func__, p); + bgp_zebra_nexthop_group_unlink(p_mpinfo, valid_nh_count); + return; + } + } + + nhg.nexthops.nexthop_num = 1; + SET_FLAG(nhg_parent.flags, BGP_NHG_FLAG_TYPE_PARENT); + if (*allow_recursion || CHECK_FLAG(api->flags, ZEBRA_FLAG_ALLOW_RECURSION)) { + SET_FLAG(nhg.flags, BGP_NHG_FLAG_ALLOW_RECURSION); + SET_FLAG(nhg_parent.flags, BGP_NHG_FLAG_ALLOW_RECURSION); + } + if (CHECK_FLAG(api->flags, ZEBRA_FLAG_IBGP)) { + SET_FLAG(nhg.flags, BGP_NHG_FLAG_IBGP); + SET_FLAG(nhg_parent.flags, BGP_NHG_FLAG_IBGP); + } + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_SRTE)) { + SET_FLAG(nhg.flags, BGP_NHG_FLAG_SRTE_PRESENCE); + SET_FLAG(nhg_parent.flags, BGP_NHG_FLAG_SRTE_PRESENCE); + } + for (i = 0; i < *valid_nh_count; i++) { + creation = false; + memset(&nhg.nexthops.nexthops[0], 0, sizeof(struct zapi_nexthop)); + memcpy(&nhg.nexthops.nexthops[0], &api->nexthops[i], sizeof(struct zapi_nexthop)); + p_nhg_childs[i] = bgp_nhg_cache_find(&nhg_cache_table, &nhg); + if (!p_nhg_childs[i]) { + p_nhg_childs[i] = bgp_nhg_new(nhg.flags, 1, nhg.nexthops.nexthops, NULL); + creation = true; + } + if (p_mpinfo[i]->bgp_nhg_nexthop != p_nhg_childs[i]) { + bgp_debug_zebra_nh_buffer(&p_nhg_childs[i]->nexthops.nexthops[0], + nexthop_buf, sizeof(nexthop_buf)); + if (p_mpinfo[i]->bgp_nhg_nexthop) { + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug("parse_nexthop: %pFX (peer %s), replacing old nexthop %u with %u (%s)", + p, PEER_HOSTNAME(p_mpinfo[i]->peer), + p_mpinfo[i]->bgp_nhg_nexthop->id, + p_nhg_childs[i]->id, nexthop_buf); + LIST_REMOVE(p_mpinfo[i], nhg_nexthop_cache_thread); + p_mpinfo[i]->bgp_nhg_nexthop->path_count--; + bgp_nhg_peer_cache_tree_update_path_count(p_mpinfo[i]->bgp_nhg_nexthop, + p_mpinfo[i]->peer, false); + if (LIST_EMPTY(&(p_mpinfo[i]->bgp_nhg_nexthop->paths))) + bgp_nhg_free(p_mpinfo[i]->bgp_nhg_nexthop); + } else { + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) { + zlog_debug("parse_nexthop: %pFX (peer %s), attaching %snexthop %u (%s)", + p, PEER_HOSTNAME(p_mpinfo[i]->peer), + creation ? "new " : "", p_nhg_childs[i]->id, + nexthop_buf); + } + } + /* updates NHG nexthop info list reference */ + p_mpinfo[i]->bgp_nhg_nexthop = p_nhg_childs[i]; + LIST_INSERT_HEAD(&(p_nhg_childs[i]->paths), p_mpinfo[i], + nhg_nexthop_cache_thread); + p_nhg_childs[i]->path_count++; + bgp_nhg_peer_cache_tree_update_path_count(p_nhg_childs[i], + p_mpinfo[i]->peer, true); + } + p_nhg_childs[i]->last_update = monotime(NULL); + + /* host p_nhg in a nhg group */ + nhg_parent.childs.child_num++; + nhg_parent.childs.childs[i] = p_nhg_childs[i]->id; + } + + /* sort to always send ordered information to zebra */ + bgp_nhg_parent_sort(nhg_parent.childs.childs, nhg_parent.childs.child_num); + + creation = false; + p_nhg_parent = bgp_nhg_parent_find_per_child(p_mpinfo, valid_nh_count, &nhg_parent); + if (!p_nhg_parent) { + p_nhg_parent = bgp_nhg_new(nhg_parent.flags, nhg_parent.childs.child_num, NULL, + nhg_parent.childs.childs); + creation = true; + } + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + bgp_nhg_debug_parent(p_nhg_parent->childs.childs, nhg_parent.childs.child_num, + nexthop_buf, sizeof(nexthop_buf)); + + /* unlink parent nhg from p_mpinfo[i], except for info */ + route_need_update = false; + for (i = 0; i < *valid_nh_count; i++) { + if (p_mpinfo[i]->bgp_nhg != p_nhg_parent) { + if (p_mpinfo[i]->bgp_nhg) { + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug("parse_nexthop: %pFX, replacing old NHG %u with %u (%s)", + p, p_mpinfo[i]->bgp_nhg->id, p_nhg_parent->id, + nexthop_buf); + LIST_REMOVE(p_mpinfo[i], nhg_cache_thread); + p_mpinfo[i]->bgp_nhg->path_count--; + if (LIST_EMPTY(&(p_mpinfo[i]->bgp_nhg->paths))) + bgp_nhg_free(p_mpinfo[i]->bgp_nhg); + p_mpinfo[i]->bgp_nhg = NULL; + } else if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug("parse_nexthop: %pFX, attaching %sNHG %u (%s)", p, + creation ? "new " : "", p_nhg_parent->id, nexthop_buf); + LIST_INSERT_HEAD(&(p_nhg_parent->paths), p_mpinfo[i], nhg_cache_thread); + p_mpinfo[i]->bgp_nhg = p_nhg_parent; + p_nhg_parent->path_count++; + route_need_update = true; + } else if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug("parse_nexthop: %pFX, NHG %u already attached (%s)", p, + p_nhg_parent->id, nexthop_buf); + } + if (!CHECK_FLAG(p_nhg_parent->state, BGP_NHG_STATE_INSTALLED)) + SET_FLAG(info->net->flags, BGP_NODE_FIB_UPDATE_OVERRIDE); + else if (route_need_update == false && + !CHECK_FLAG(info->net->flags, BGP_NODE_FIB_UPDATE_OVERRIDE)) + SET_FLAG(info->net->flags, BGP_NODE_FIB_UPDATE_UNNEEDED); + + p_nhg_parent->last_update = monotime(NULL); + + *nhg_id = p_nhg_parent->id; + zapi_route_set_nhg_id(api, nhg_id); + if (creation) + bgp_nhg_parent_link(p_nhg_childs, nhg_parent.childs.child_num, p_nhg_parent); +} + static void bgp_zebra_announce_parse_nexthop( struct bgp_path_info *info, const struct prefix *p, struct bgp *bgp, struct zapi_route *api, unsigned int *valid_nh_count, afi_t afi, @@ -1256,6 +1473,8 @@ static void bgp_zebra_announce_parse_nexthop( uint32_t bos = 0; uint32_t exp = 0; struct bgp_route_evpn *bre = NULL; + bool is_evpn_path = false; + struct bgp_path_info *p_mpinfo[MULTIPATH_NUM] = { NULL }; /* Determine if we're doing weighted ECMP or not */ do_wt_ecmp = bgp_path_info_mpath_chkwtd(bgp, info); @@ -1371,6 +1590,8 @@ static void bgp_zebra_announce_parse_nexthop( is_evpn = !!CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN); bre = bgp_attr_get_evpn_overlay(mpinfo->attr); + if (is_evpn) + is_evpn_path = true; /* Did we get proper nexthop info to update zebra? */ if (!nh_updated) @@ -1448,13 +1669,24 @@ static void bgp_zebra_announce_parse_nexthop( SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6); } + p_mpinfo[*valid_nh_count] = mpinfo; (*valid_nh_count)++; } + + if (!bgp_option_check(BGP_OPT_NHG)) + return; + + if (info->sub_type == BGP_ROUTE_AGGREGATE || do_wt_ecmp || is_evpn_path) { + bgp_zebra_nexthop_group_unlink(p_mpinfo, valid_nh_count); + return; + } + + bgp_zebra_nexthop_group_configure(info, p, bgp, api, valid_nh_count, nhg_id, + allow_recursion, p_mpinfo); } -static void bgp_debug_zebra_nh(struct zapi_route *api) +void bgp_debug_zebra_nh_buffer(struct zapi_nexthop *api_nh, char *nexthop_buf, size_t len) { - int i; int nh_family; char nh_buf[INET6_ADDRSTRLEN]; char eth_buf[ETHER_ADDR_STRLEN + 7] = { '\0' }; @@ -1462,59 +1694,56 @@ static void bgp_debug_zebra_nh(struct zapi_route *api) char label_buf[20]; char sid_buf[20]; char segs_buf[256]; - struct zapi_nexthop *api_nh; - int count; - count = api->nexthop_num; - for (i = 0; i < count; i++) { - api_nh = &api->nexthops[i]; - switch (api_nh->type) { - case NEXTHOP_TYPE_IFINDEX: - nh_buf[0] = '\0'; - break; - case NEXTHOP_TYPE_IPV4: - case NEXTHOP_TYPE_IPV4_IFINDEX: - nh_family = AF_INET; - inet_ntop(nh_family, &api_nh->gate, nh_buf, - sizeof(nh_buf)); - break; - case NEXTHOP_TYPE_IPV6: - case NEXTHOP_TYPE_IPV6_IFINDEX: - nh_family = AF_INET6; - inet_ntop(nh_family, &api_nh->gate, nh_buf, - sizeof(nh_buf)); - break; - case NEXTHOP_TYPE_BLACKHOLE: - strlcpy(nh_buf, "blackhole", sizeof(nh_buf)); - break; - default: - /* Note: add new nexthop case */ - assert(0); - break; - } + switch (api_nh->type) { + case NEXTHOP_TYPE_IFINDEX: + nh_buf[0] = '\0'; + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + nh_family = AF_INET; + inet_ntop(nh_family, &api_nh->gate, nh_buf, sizeof(nh_buf)); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + nh_family = AF_INET6; + inet_ntop(nh_family, &api_nh->gate, nh_buf, sizeof(nh_buf)); + break; + case NEXTHOP_TYPE_BLACKHOLE: + strlcpy(nh_buf, "blackhole", sizeof(nh_buf)); + break; + default: + /* Note: add new nexthop case */ + assert(0); + break; + } - label_buf[0] = '\0'; - eth_buf[0] = '\0'; - segs_buf[0] = '\0'; - if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL) && - !CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN)) - snprintf(label_buf, sizeof(label_buf), "label %u", - api_nh->labels[0]); - if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6) && - !CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN)) { - inet_ntop(AF_INET6, &api_nh->seg6_segs[0], sid_buf, - sizeof(sid_buf)); - snprintf(segs_buf, sizeof(segs_buf), "segs %s", sid_buf); - } - if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN) && - !is_zero_mac(&api_nh->rmac)) - snprintf(eth_buf, sizeof(eth_buf), " RMAC %s", - prefix_mac2str(&api_nh->rmac, buf1, - sizeof(buf1))); - zlog_debug(" nhop [%d]: %s if %u VRF %u wt %" PRIu64 - " %s %s %s", - i + 1, nh_buf, api_nh->ifindex, api_nh->vrf_id, - api_nh->weight, label_buf, segs_buf, eth_buf); + label_buf[0] = '\0'; + eth_buf[0] = '\0'; + segs_buf[0] = '\0'; + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL) && + !CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN)) + snprintf(label_buf, sizeof(label_buf), "label %u", api_nh->labels[0]); + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6) && + !CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN)) { + inet_ntop(AF_INET6, &api_nh->seg6_segs[0], sid_buf, sizeof(sid_buf)); + snprintf(segs_buf, sizeof(segs_buf), "segs %s", sid_buf); + } + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN) && !is_zero_mac(&api_nh->rmac)) + snprintf(eth_buf, sizeof(eth_buf), " RMAC %s", + prefix_mac2str(&api_nh->rmac, buf1, sizeof(buf1))); + snprintfrr(nexthop_buf, len, "%s if %u VRF %u wt %" PRIu64 "%s %s %s", nh_buf, + api_nh->ifindex, api_nh->vrf_id, api_nh->weight, label_buf, segs_buf, eth_buf); +} + +void bgp_debug_zebra_nh(struct zapi_nexthop api_nexthops[], int count) +{ + int i; + char nexthop_buf[BGP_NEXTHOP_BUFFER_SIZE]; + + for (i = 0; i < count; i++) { + bgp_debug_zebra_nh_buffer(&api_nexthops[i], nexthop_buf, sizeof(nexthop_buf)); + zlog_debug(" nhop [%d]: %s", i, nexthop_buf); } } @@ -1638,12 +1867,32 @@ bgp_zebra_announce_actual(struct bgp_dest *dest, struct bgp_path_info *info, api.distance = distance; } + if (nhg_id && info->bgp_nhg) { + if (!CHECK_FLAG(info->bgp_nhg->state, BGP_NHG_STATE_INSTALLED)) { + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug("NHG %u, BGP %s: ID not installed, postpone prefix %pFX install", + info->bgp_nhg->id, bgp->name_pretty, p); + return ZCLIENT_SEND_SUCCESS; + } + if (CHECK_FLAG(dest->flags, BGP_NODE_FIB_INSTALLED) && + !CHECK_FLAG(dest->flags, BGP_NODE_FIB_UPDATE_OVERRIDE) && + CHECK_FLAG(dest->flags, BGP_NODE_FIB_UPDATE_UNNEEDED)) { + if (BGP_DEBUG(nexthop_group, NEXTHOP_GROUP_DETAIL)) + zlog_debug("NHG %u, BGP %s: ID and route already installed, no re-install %pFX", + info->bgp_nhg->id, bgp->name_pretty, p); + UNSET_FLAG(dest->flags, BGP_NODE_FIB_UPDATE_UNNEEDED); + return ZCLIENT_SEND_SUCCESS; + } + } + + UNSET_FLAG(dest->flags, BGP_NODE_FIB_UPDATE_OVERRIDE); + if (bgp_debug_zebra(p)) { zlog_debug("Tx route add %s (table id %u) %pFX metric %u tag %" ROUTE_TAG_PRI " count %d nhg %d", bgp->name_pretty, api.tableid, &api.prefix, api.metric, api.tag, api.nexthop_num, nhg_id); - bgp_debug_zebra_nh(&api); + bgp_debug_zebra_nh(api.nexthops, api.nexthop_num); zlog_debug("%s: %pFX: announcing to zebra (recursion %sset)", __func__, p, (allow_recursion ? "" : "NOT ")); @@ -3830,9 +4079,9 @@ static zclient_handler *const bgp_handlers[] = { [ZEBRA_ROUTE_NOTIFY_OWNER] = bgp_zebra_route_notify_owner, [ZEBRA_SRV6_LOCATOR_ADD] = bgp_zebra_process_srv6_locator_add, [ZEBRA_SRV6_LOCATOR_DELETE] = bgp_zebra_process_srv6_locator_delete, - [ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK] = - bgp_zebra_process_srv6_locator_chunk, + [ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK] = bgp_zebra_process_srv6_locator_chunk, [ZEBRA_SRV6_SID_NOTIFY] = bgp_zebra_srv6_sid_notify, + [ZEBRA_NHG_NOTIFY_OWNER] = bgp_nhg_notify_owner, }; static int bgp_if_new_hook(struct interface *ifp) diff --git a/bgpd/bgp_zebra.h b/bgpd/bgp_zebra.h index 8deecba747b3..f4a65f17de5b 100644 --- a/bgpd/bgp_zebra.h +++ b/bgpd/bgp_zebra.h @@ -19,6 +19,9 @@ /* Default weight for next hop, if doing weighted ECMP. */ #define BGP_ZEBRA_DEFAULT_NHOP_WEIGHT 1 +/* bgp nexthop buffer size to pass when using bgp_debug_zebra_nh_buffer() */ +#define BGP_NEXTHOP_BUFFER_SIZE 512 + extern void bgp_zebra_init(struct event_loop *master, unsigned short instance); extern void bgp_if_init(void); extern void bgp_zebra_init_tm_connect(struct bgp *bgp); @@ -135,4 +138,6 @@ extern void bgp_zebra_release_label_range(uint32_t start, uint32_t end); extern enum zclient_send_status bgp_zebra_withdraw_actual(struct bgp_dest *dest, struct bgp_path_info *info, struct bgp *bgp); +void bgp_debug_zebra_nh(struct zapi_nexthop api_nexthops[], int count); +void bgp_debug_zebra_nh_buffer(struct zapi_nexthop *api_nh, char *nexthop_buf, size_t len); #endif /* _QUAGGA_BGP_ZEBRA_H */ diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index a186243ffcfc..b8c8b7ad0a70 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -177,6 +177,7 @@ int bgp_option_set(int flag) case BGP_OPT_NO_FIB: case BGP_OPT_NO_LISTEN: case BGP_OPT_NO_ZEBRA: + case BGP_OPT_NHG: SET_FLAG(bm->options, flag); break; default: @@ -190,6 +191,7 @@ int bgp_option_unset(int flag) switch (flag) { case BGP_OPT_NO_ZEBRA: case BGP_OPT_NO_FIB: + case BGP_OPT_NHG: UNSET_FLAG(bm->options, flag); break; default: @@ -203,6 +205,29 @@ int bgp_option_check(int flag) return CHECK_FLAG(bm->options, flag); } +void bgp_option_nhg_update(void) +{ + struct bgp *bgp; + struct listnode *node; + afi_t afi; + safi_t safi; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) { + FOREACH_AFI_SAFI (afi, safi) { + /* + * Stop a crash, more work is needed + * here to properly add/remove these types of + * routes from zebra. + */ + if (!bgp_fibupd_safi(safi)) + continue; + + bgp_zebra_withdraw_table_all_subtypes(bgp, afi, safi); + bgp_zebra_announce_table_all_subtypes(bgp, afi, safi); + } + } +} + /* set the bgp no-rib option during runtime and remove installed routes */ void bgp_option_norib_set_runtime(void) { @@ -2593,7 +2618,7 @@ static bool non_peergroup_deactivate_af(struct peer *peer, afi_t afi, bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_MP, CAPABILITY_ACTION_UNSET); - bgp_clear_route(peer, afi, safi); + bgp_clear_route(peer, afi, safi, false); peer->pcount[afi][safi] = 0; } else { bgp_notify_send(peer->connection, @@ -8930,3 +8955,72 @@ const char *bgp_martian_type2str(enum bgp_martian_type mt) { return lookup_msg(bgp_martian_type_str, mt, "Unknown Martian Type"); } + +static void bgp_path_info_show_flags(uint32_t flags, json_object *json) +{ + json_object *json_flags = NULL; + + if (!json) + return; + + json_flags = json_object_new_object(); + json_object_boolean_add(json_flags, "igpChanged", CHECK_FLAG(flags, BGP_PATH_IGP_CHANGED)); + json_object_boolean_add(json_flags, "damped", CHECK_FLAG(flags, BGP_PATH_DAMPED)); + json_object_boolean_add(json_flags, "history", CHECK_FLAG(flags, BGP_PATH_HISTORY)); + json_object_boolean_add(json_flags, "bestpath", CHECK_FLAG(flags, BGP_PATH_SELECTED)); + json_object_boolean_add(json_flags, "valid", CHECK_FLAG(flags, BGP_PATH_VALID)); + json_object_boolean_add(json_flags, "attrChanged", CHECK_FLAG(flags, BGP_PATH_ATTR_CHANGED)); + json_object_boolean_add(json_flags, "deterministicMedCheck", + CHECK_FLAG(flags, BGP_PATH_DMED_CHECK)); + json_object_boolean_add(json_flags, "deterministicMedSelected", + CHECK_FLAG(flags, BGP_PATH_DMED_SELECTED)); + json_object_boolean_add(json_flags, "stale", CHECK_FLAG(flags, BGP_PATH_STALE)); + json_object_boolean_add(json_flags, "removed", CHECK_FLAG(flags, BGP_PATH_REMOVED)); + json_object_boolean_add(json_flags, "counted", CHECK_FLAG(flags, BGP_PATH_COUNTED)); + json_object_boolean_add(json_flags, "multipath", CHECK_FLAG(flags, BGP_PATH_MULTIPATH)); + json_object_boolean_add(json_flags, "multipathChanged", + CHECK_FLAG(flags, BGP_PATH_MULTIPATH_CHG)); + json_object_boolean_add(json_flags, "ribAttributeChanged", + CHECK_FLAG(flags, BGP_PATH_RIB_ATTR_CHG)); + json_object_boolean_add(json_flags, "nexthopSelf", CHECK_FLAG(flags, BGP_PATH_ANNC_NH_SELF)); + json_object_boolean_add(json_flags, "linkBandwidthChanged", + CHECK_FLAG(flags, BGP_PATH_LINK_BW_CHG)); + json_object_boolean_add(json_flags, "acceptOwn", CHECK_FLAG(flags, BGP_PATH_ACCEPT_OWN)); + json_object_object_add(json, "flags", json_flags); +} + +void bgp_path_info_display(struct bgp_path_info *path, struct vty *vty, json_object *json_path) +{ + struct bgp_dest *dest; + afi_t afi; + safi_t safi; + struct bgp_table *table; + struct bgp *bgp_path; + + dest = path->net; + assert(dest && bgp_dest_table(dest)); + afi = family2afi(bgp_dest_get_prefix(dest)->family); + table = bgp_dest_table(dest); + safi = table->safi; + bgp_path = table->bgp; + if (json_path) { + json_object_string_add(json_path, "afi", afi2str(afi)); + json_object_string_add(json_path, "safi", safi2str(safi)); + json_object_string_addf(json_path, "prefix", "%pBD", dest); + if (dest->pdest) + json_object_string_addf(json_path, "rd", + BGP_RD_AS_FORMAT(bgp_path->asnotation), + (struct prefix_rd *)bgp_dest_get_prefix(dest->pdest)); + json_object_string_add(json_path, "vrf", vrf_id_to_name(bgp_path->vrf_id)); + bgp_path_info_show_flags(path->flags, json_path); + return; + } + if (dest->pdest) { + vty_out(vty, " %d/%d %pBD RD ", afi, safi, dest); + vty_out(vty, BGP_RD_AS_FORMAT(bgp_path->asnotation), + (struct prefix_rd *)bgp_dest_get_prefix(dest->pdest)); + vty_out(vty, " %s flags 0x%x\n", bgp_path->name_pretty, path->flags); + } else + vty_out(vty, " %d/%d %pBD %s flags 0x%x\n", afi, safi, dest, + bgp_path->name_pretty, path->flags); +} diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index 852efdf19d31..558a3435263d 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -132,7 +132,7 @@ struct bgp_master { #define BGP_OPT_TRAPS_RFC4273 (1 << 3) #define BGP_OPT_TRAPS_BGP4MIBV2 (1 << 4) #define BGP_OPT_TRAPS_RFC4382 (1 << 5) - +#define BGP_OPT_NHG (1 << 6) uint64_t updgrp_idspace; uint64_t subgrp_idspace; @@ -2314,6 +2314,9 @@ extern void bgp_option_norib_set_runtime(void); /* unset the bgp no-rib option during runtime and reset all peers */ extern void bgp_option_norib_unset_runtime(void); +/* update the route entries with/without the nhg mechanism */ +extern void bgp_option_nhg_update(void); + extern int bgp_get(struct bgp **bgp, as_t *as, const char *name, enum bgp_instance_type kind, const char *as_pretty, enum asnotation_mode asnotation); @@ -2863,6 +2866,9 @@ extern void srv6_function_free(struct bgp_srv6_function *func); extern void bgp_session_reset_safe(struct peer *peer, struct listnode **nnode); +extern void bgp_path_info_display(struct bgp_path_info *path, struct vty *vty, + json_object *json_path); + #ifdef _FRR_ATTRIBUTE_PRINTFRR /* clang-format off */ #pragma FRR printfrr_ext "%pBP" (struct peer *) diff --git a/bgpd/subdir.am b/bgpd/subdir.am index 6d6fad00745f..23a5f28c314c 100644 --- a/bgpd/subdir.am +++ b/bgpd/subdir.am @@ -141,6 +141,7 @@ noinst_HEADERS += \ bgpd/bgp_mplsvpn_snmp.h \ bgpd/bgp_network.h \ bgpd/bgp_nexthop.h \ + bgpd/bgp_nhg_private.h \ bgpd/bgp_nht.h \ bgpd/bgp_open.h \ bgpd/bgp_packet.h \ @@ -217,6 +218,7 @@ clippy_scan += \ bgpd/bgp_vty.c \ bgpd/bgp_nexthop.c \ bgpd/bgp_snmp.c \ + bgpd/bgp_nhg.c \ # end nodist_bgpd_bgpd_SOURCES = \ diff --git a/doc/developer/bgp-nexthop-group.rst b/doc/developer/bgp-nexthop-group.rst new file mode 100644 index 000000000000..e483b13e0197 --- /dev/null +++ b/doc/developer/bgp-nexthop-group.rst @@ -0,0 +1,115 @@ +BGP Next Hop Group +================== + +BGP nexthop group is an optimization feature that splits the route message to send +in two, by dissociating the nexthop information with the prefix information. This +results in reducing the number of route messages to send when a failover happens. + +Goal +---- + +The goal is the following: + +- **Stability**: reduce the number of messages to send from *bgp* to *zebra* when a failover + happens. Today, flapping interface triggers as many messages as there are prefixes to + update. And the more the flapping happens, the more the memory taken will increase, + and the most likely *bgp* or *zebra* process may make an `Out of Memory` issue. + +- **Convergence**: improve the resilience by reducing the *zebra* time convergence when + a next-hop becomes unreachable. + +Example +------- + +Let us imagine a BGP router with two devices at nexthop NH1 and NH2: peer1 and peer2. +BGP addpath feature is enabled. BGP receives BGP updates with different ADDPATH-ID +identifiers. The route structure looks like below: + + pDest = 192.0.2.1 + path_1_1, NH1 from peer1 + path_1_2, NH2 from peer2 + pDest = 192.0.2.2 + path_2_1, NH1 from peer1 + path_2_2, NH2 from peer2 + +To support the nexthop info, a 'bgp_nhg_cache' structure is used and hosts the nexthop information. +This nexthop information is based on the incoming BGP update + the BGP characteristics +(ebgp/ibgp, color community, ..). This is a zapi_nexthop structure. An unique 'nhg_id' identifier +is used. + + NEXTHOP_1 (bgp_nhg_cache) : TYPE_NEXTHOP + -> struct zapi_nexthop znh; + -> uint32_t nhg_id; + -> list bgp_path_info + +To support ECMP or WECMP cases, there is need to group the nexthops together. the 'bgp_nhg_cache' +structure hosts the nexthop group information. The below drawing illustrates an ECMP +nexthop with NH1 and NH2. Three 'bgp_nhg_cache' structures are used to represent it. Each strucure +owns an unique 'nhg_id' identifier. + + NHG_0 (bgp_nhg_cache) : TYPE_GROUP + -> uint32_t nhg_id; --> NHG_0 + -> nhg_childs ---> NEXTHOP_1 (bgp_nhg_cache) : TYPE_NEXTHOP + nhg_parents ---> NHG_1 + ---> NEXTHOP_2 (bgp_nhg_cache) : TYPE_NEXTHOP + nhg_parents ---> NHG_2 + -> nhg_parents (NULL) + -> list bgp_path_info + +If the nexthop-group feature is enabled, the bgp_path_info structure owns 2 back-pointers: +bgp_nhg and bgp_nhg_nexthop. The referenced structures are 'bgp_nhg_cache' structures. +The `bgp_nhg_nexthop` pointer is used for all SELECTED and MULTIPATH paths. +The `bgp_nhg` pointer is used for all SELECTED paths. + + pDest = 192.0.2.1 + path_1_1, NH1 from peer1 -------> bgp_nhg_nexthop (bgp_nhg_cache) : NEXTHOP_1 + -------> bgp_nhg (bgp_nhg_cache) : NHG_0 + +If we assume all the 4 paths are selected, and an ECMP group is formed with NH1 and NH2, +the internal structures are referenced like below: + + pDest = 192.0.2.1 + path_1_1, NH1 from peer1 -----+-----------------------------> bgp_nhg_nexthop (nhg) : NEXTHOP_1 + -----|----+-+--+--> bgp_nhg (nhg) : NHG_0 + path_1_2, NH2 from peer2 -----|--+-|-|--|-------------------> bgp_nhg_nexthop (nhg) : NEXTHOP_2 + -----|--|-+ | | + pDest = 192.0.2.2 | | | | + path_2_1, NH1 from peer1 -----+ | | | + --------|---+ | + path_2_2, NH2 from peer2 --------+ | + ---------------+ + +nhg (NHG_0) has the {path_1_1, path_2_1} paths referenced. +nhg (NEXTHOP_1) has the {path_1_1, path_2_1} paths referenced. +nhg (NEXTHOP_2) has the {path_1_2, path_2_2} paths referenced. + +BGP sends the following operations to ZEBRA. + + NHG_ADD(NHG_ID1) + NHG_ADD(NHG_ID2) + NHG_CHILD_ADD(NHG_0, {NHG_ID1, NHG_ID2}) + ROUTE_ADD(p1, NHG_0) + ROUTE_ADD(p2, NHG_0) + +Some events like peer2 becoming unreachable, or BGP BFD failure require a full flush of the ADJ-RIB-IN +of peer2. The BGP router will detect those events and will group the failover changes together, by +detaching all the bgp_nhg_nexthop from the concerned BGP updates. + +- The NH2 nexthop represented by bgp_nhg_nexthop (NHG_ID2) is dereferenced from peer2 updates. +- If NHG_ID2 paths references is empty, then NHG_ID2 can be removed +- The NHG_0 can be detached from NHG_ID2, and updated to ZEBRA + +Only the {path_1_1, path_2_1} paths are maintained as {path_2_1} and {path_2_2} will be removed. + + path_1_1(Prefix P1 = 192.0.2.1, NH1 from peer1) -> bgp_nhg_nexthop ------> nhg (NHG_ID1) + bgp_nhg -------------> nhg (NHG_ID3) + + path_2_1(Prefix P2 = 192.0.2.2, NH1 from peer1) -> bgp_nhg_nexthop ------> nhg (NHG_ID1) + bgp_nhg -------------> nhg (NHG_0) + +BGP sends the following operations to ZEBRA: + + NHG_CHILD_ADD(NHG_0, {NHG_ID1}) + NHG_DEL(NHG_ID2) + +Note that there is no need to send ROUTE_ADD messages, as only the nexthop changed. diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index 6a46128d72d2..ee4a805ef191 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -4051,6 +4051,61 @@ When default route is not present in R2'2 BGP table, 10.139.224.0/20 and 192.0.2 Total number of prefixes 3 Router2# +BGP nexthop-group handling +-------------------------- + +Some scenarios like BGP peering with multihoming, require BGP to converge as +fast as possible, when switching the routes from the primary gateway with the +secondary one. More generally, when pushing and updating thousands of routes +to the system, a bottleneck may happen at the communication level between +*bgpd* and *zebra*, and may alter the system (`Out of Memory` or high CPU +consumption issues). To avoid those problems, the global ``bgp nexthop-group`` +command changes the way internal messaging happens from *bgpd* to *zebra*: +nexthop messages are introduced, are further used by route messages, and when a +failover event happens at BGP level, then *bgpd* will only refresh the necessary +nexthop messages. This specific case avoids sending thousands of additional +messages to *zebra*. + +.. clicmd:: bgp nexthop-group + + Enable on all BGP instances the usage of nexthop messages between *bgpd* and + *zebra*. By default, the functionality is enabled. + +.. clicmd:: show bgp nexthop-group [vrf ] [(0-4294967295)] [detail] [json] + + Display the configured nexthop-groups at *bgpd* level. The `detail` keyword + displays the attached routes. + +.. code-block:: frr + + Router2# show bgp vrf RED_A nexthop-group detail + ID: 75757653, #paths 2 + Flags: 0x0003 (allowRecursion, internalBgp) + State: 0x0001 (Installed) + via 1001::1 (vrf RED_A) inactive, weight 1 + parent list count 1 + parent(s) 75757655 + Paths: + 2/1 1::2/128 VRF RED_A flags 0x418 + 2/1 1::1/128 VRF RED_A flags 0x418 + ID: 75757655, #paths 2 + Flags: 0x000b (allowRecursion, internalBgp, TypeGroup) + State: 0x0001 (Installed) + child list count 1 + child(s) 75757653 + Paths: + 2/1 1::2/128 VRF RED_A flags 0x418 + 2/1 1::1/128 VRF RED_A flags 0x418 + +.. note:: + + When using BGP nexthop-groups, the mechanism will not apply to the + following routes: routes resolving over blackhole routes, or default + routes, blackhole or interface based routes (MPLS-based L3VPN routes), + routes with IPv4 mapped IPv6 nexthops. Also, an internal issue prevents + from using both :ref:`BGP route filtering with + BGP nexthop-group configuration. + .. _bgp-debugging: Debugging @@ -4124,6 +4179,10 @@ Debugging Enable or disable debugging of communications between *bgpd* and *zebra*. +.. clicmd:: debug bgp nexthop-group + + Enable or disable debugging of nexthop-group messaging between *bgpd* and *zebra*. + Dumping Messages and Routing Tables ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/user/nexthop_groups.rst b/doc/user/nexthop_groups.rst index 45f64eecb7e6..57ed29bcac01 100644 --- a/doc/user/nexthop_groups.rst +++ b/doc/user/nexthop_groups.rst @@ -12,7 +12,7 @@ listing of ECMP nexthops used to forward packets. sub-mode where you can specify individual nexthops. To exit this mode type exit or end as per normal conventions for leaving a sub-mode. -.. clicmd:: nexthop [A.B.C.D|X:X::X:XX] [interface [onlink]] [nexthop-vrf NAME] [label LABELS] +.. clicmd:: nexthop [A.B.C.D|X:X::X:XX] [interface [onlink]] [nexthop-vrf NAME] [label LABELS] [color (1-4294967295)] Create a v4 or v6 nexthop. All normal rules for creating nexthops that you are used to are allowed here. The syntax was intentionally kept the same as @@ -27,3 +27,16 @@ listing of ECMP nexthops used to forward packets. will be automatically re-assigned. This cli command must be the first command entered currently. Additionally this command only works with linux 5.19 kernels or newer. + +.. clicmd:: allow-recursion + + By default, a nexthop group is only marked as active when its nexthop is + directly connected. The ``allow-recursion`` option allows zebra to resolve the + nexthop using other types of routes. + +.. clicmd:: child-group NAME + + Append a child nexthop group in the current nexthop group. The protocol daemon + using it will ensure that the child group is configured at the protocol level, + and installed at zebra level, before installing the parent nexthop group. + This option is very useful to consider nexthop groups having multiple paths. diff --git a/lib/log.c b/lib/log.c index 04b789b5da5d..0deae0e2c41f 100644 --- a/lib/log.c +++ b/lib/log.c @@ -428,6 +428,7 @@ static const struct zebra_desc_table command_types[] = { DESC_ENTRY(ZEBRA_MLAG_FORWARD_MSG), DESC_ENTRY(ZEBRA_NHG_ADD), DESC_ENTRY(ZEBRA_NHG_DEL), + DESC_ENTRY(ZEBRA_NHG_CHILD_ADD), DESC_ENTRY(ZEBRA_NHG_NOTIFY_OWNER), DESC_ENTRY(ZEBRA_EVPN_REMOTE_NH_ADD), DESC_ENTRY(ZEBRA_EVPN_REMOTE_NH_DEL), diff --git a/lib/nexthop_group.c b/lib/nexthop_group.c index cb1ebb5d09b9..32fc8a2fdde6 100644 --- a/lib/nexthop_group.c +++ b/lib/nexthop_group.c @@ -18,6 +18,7 @@ #include "lib/nexthop_group_clippy.c" DEFINE_MTYPE_STATIC(LIB, NEXTHOP_GROUP, "Nexthop Group"); +DEFINE_MTYPE_STATIC(LIB, CHILD_GROUP, "Child Nexthop Group"); /* * Internal struct used to hold nhg config strings @@ -31,15 +32,16 @@ struct nexthop_hold { vni_t vni; uint32_t weight; char *backup_str; + uint32_t color; }; struct nexthop_group_hooks { void (*new)(const char *name); void (*modify)(const struct nexthop_group_cmd *nhgc); - void (*add_nexthop)(const struct nexthop_group_cmd *nhg, - const struct nexthop *nhop); - void (*del_nexthop)(const struct nexthop_group_cmd *nhg, - const struct nexthop *nhop); + void (*add_nexthop_or_child_group)(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop); + void (*del_nexthop_or_child_group)(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop); void (*delete)(const char *name); }; @@ -51,7 +53,7 @@ nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1, RB_GENERATE(nhgc_entry_head, nexthop_group_cmd, nhgc_entry, nexthop_group_cmd_compare) -static struct nhgc_entry_head nhgc_entries; +struct nhgc_entry_head nhgc_entries; static inline int nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1, @@ -258,10 +260,25 @@ void nexthop_group_copy(struct nexthop_group *to, const struct nexthop_group *from) { to->nhgr = from->nhgr; + to->flags = from->flags; /* Copy everything, including recursive info */ copy_nexthops(&to->nexthop, from->nexthop, NULL); } +/* append nexthops from 'from' nexthop-group to 'to' nexthop-group + */ +void nexthop_group_append_nexthops(struct nexthop_group *to, + const struct nexthop_group *from) +{ + struct nexthop *nexthop = nexthop_group_tail(to); + + /* Copy everything, including recursive info */ + if (nexthop) + copy_nexthops(&nexthop, from->nexthop, NULL); + else + copy_nexthops(&to->nexthop, from->nexthop, NULL); +} + void nexthop_group_delete(struct nexthop_group **nhg) { /* OK to call with NULL group */ @@ -483,8 +500,8 @@ static void nhgc_delete_nexthops(struct nexthop_group_cmd *nhgc) struct nexthop *next = nexthop_next(nexthop); _nexthop_del(&nhgc->nhg, nexthop); - if (nhg_hooks.del_nexthop) - nhg_hooks.del_nexthop(nhgc, nexthop); + if (nhg_hooks.del_nexthop_or_child_group) + nhg_hooks.del_nexthop_or_child_group(nhgc, nexthop); nexthop_free(nexthop); @@ -566,6 +583,16 @@ static void nhgl_delete(struct nexthop_hold *nh) XFREE(MTYPE_TMP, nh); } +static int nhgl_child_group_cmp(char *group1, char *group2) +{ + return nhgc_cmp_helper(group1, group2); +} + +static void nhgl_child_group_delete(char *group1) +{ + XFREE(MTYPE_CHILD_GROUP, group1); +} + static struct nexthop_group_cmd *nhgc_get(const char *name) { struct nexthop_group_cmd *nhgc; @@ -582,6 +609,12 @@ static struct nexthop_group_cmd *nhgc_get(const char *name) nhgc->nhg_list->cmp = (int (*)(void *, void *))nhgl_cmp; nhgc->nhg_list->del = (void (*)(void *))nhgl_delete; + nhgc->nhg_child_group_list = list_new(); + nhgc->nhg_child_group_list->cmp = + (int (*)(void *, void *))nhgl_child_group_cmp; + nhgc->nhg_child_group_list->del = + (void (*)(void *))nhgl_child_group_delete; + if (nhg_hooks.new) nhg_hooks.new(name); } @@ -599,11 +632,46 @@ static void nhgc_delete(struct nexthop_group_cmd *nhgc) RB_REMOVE(nhgc_entry_head, &nhgc_entries, nhgc); list_delete(&nhgc->nhg_list); + list_delete(&nhgc->nhg_child_group_list); QOBJ_UNREG(nhgc); XFREE(MTYPE_TMP, nhgc); } +/* remove group configuration + * return true if found, false if not + */ +static bool nexthop_group_unsave_child_group(struct nexthop_group_cmd *nhgc, + const char *group_name) +{ + char *child_group_name; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(nhgc->nhg_child_group_list, node, nnode, + child_group_name)) { + if (nhgc_cmp_helper(group_name, child_group_name) == 0) { + list_delete_node(nhgc->nhg_child_group_list, node); + nhgl_child_group_delete(child_group_name); + return true; + } + } + + return false; +} + +static bool nexthop_group_save_child_group(struct nexthop_group_cmd *nhgc, + const char *group_name) +{ + char *child_group_name; + + if (listnode_lookup(nhgc->nhg_child_group_list, group_name)) + return false; + + child_group_name = XSTRDUP(MTYPE_CHILD_GROUP, group_name); + listnode_add_sort(nhgc->nhg_child_group_list, child_group_name); + return true; +} + DEFINE_QOBJ_TYPE(nexthop_group_cmd); DEFUN_NOSH(nexthop_group, nexthop_group_cmd, "nexthop-group NHGNAME", @@ -646,6 +714,72 @@ DEFPY(nexthop_group_backup, nexthop_group_backup_cmd, return CMD_SUCCESS; } +DEFPY(nexthop_group_allow_recursion, + nexthop_group_allow_recursion_cmd, + "[no] allow-recursion", + NO_STR + "Allow recursion when the nexthop is not directly connected\n") +{ + VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); + + if (!!no == !CHECK_FLAG(nhgc->nhg.flags, NEXTHOP_GROUP_ALLOW_RECURSION)) + return CMD_SUCCESS; + + if (no) + UNSET_FLAG(nhgc->nhg.flags, NEXTHOP_GROUP_ALLOW_RECURSION); + else + SET_FLAG(nhgc->nhg.flags, NEXTHOP_GROUP_ALLOW_RECURSION); + + if (nhg_hooks.modify) + nhg_hooks.modify(nhgc); + + return CMD_SUCCESS; +} + +DEFPY(nexthop_child_group, + nexthop_child_group_cmd, + "[no$no] child-group NHGNAME$name", + NO_STR + "Specify a group name containing nexthops\n" + "The name of the group\n") +{ + VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); + + /* if already nexthops, forbid */ + if (listcount(nhgc->nhg_list)) { + vty_out(vty, + "%% child nexthop group not possible when 'nexthop' is configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (no) { + if (nexthop_group_unsave_child_group(nhgc, name) && + nhg_hooks.del_nexthop_or_child_group) + nhg_hooks.del_nexthop_or_child_group(nhgc, NULL); + } else { + if (nexthop_group_save_child_group(nhgc, name) && + nhg_hooks.add_nexthop_or_child_group) + nhg_hooks.add_nexthop_or_child_group(nhgc, NULL); + } + + return CMD_SUCCESS; +} + +static void nhgc_configure_color(struct nexthop_group_cmd *nhgc, bool enable) +{ + if (enable == + !!CHECK_FLAG(nhgc->nhg.message, NEXTHOP_GROUP_MESSAGE_SRTE)) + return; + + if (enable) + SET_FLAG(nhgc->nhg.message, NEXTHOP_GROUP_MESSAGE_SRTE); + else + UNSET_FLAG(nhgc->nhg.message, NEXTHOP_GROUP_MESSAGE_SRTE); + + if (nhg_hooks.modify) + nhg_hooks.modify(nhgc); +} + DEFPY(no_nexthop_group_backup, no_nexthop_group_backup_cmd, "no backup-group [WORD$name]", NO_STR @@ -703,12 +837,11 @@ DEFPY(no_nexthop_group_resilience, return CMD_SUCCESS; } -static void nexthop_group_save_nhop(struct nexthop_group_cmd *nhgc, - const char *nhvrf_name, - const union sockunion *addr, - const char *intf, bool onlink, - const char *labels, const uint32_t weight, - const char *backup_str) +static void +nexthop_group_save_nhop(struct nexthop_group_cmd *nhgc, const char *nhvrf_name, + const union sockunion *addr, const char *intf, + bool onlink, const char *labels, const uint32_t weight, + const char *backup_str, const uint32_t color) { struct nexthop_hold *nh; @@ -727,6 +860,8 @@ static void nexthop_group_save_nhop(struct nexthop_group_cmd *nhgc, nh->weight = weight; + nh->color = color; + if (backup_str) nh->backup_str = XSTRDUP(MTYPE_TMP, backup_str); @@ -775,8 +910,8 @@ static bool nexthop_group_parse_nexthop(struct nexthop *nhop, const union sockunion *addr, const char *intf, bool onlink, const char *name, const char *labels, - vni_t vni, int *lbl_ret, - uint32_t weight, const char *backup_str) + vni_t vni, int *lbl_ret, uint32_t weight, + const char *backup_str, uint32_t color) { int ret = 0; struct vrf *vrf; @@ -843,6 +978,7 @@ static bool nexthop_group_parse_nexthop(struct nexthop *nhop, } nhop->weight = weight; + nhop->srte_color = color; if (backup_str) { /* Parse backup indexes */ @@ -864,9 +1000,11 @@ static bool nexthop_group_parse_nexthop(struct nexthop *nhop, static bool nexthop_group_parse_nhh(struct nexthop *nhop, const struct nexthop_hold *nhh) { - return (nexthop_group_parse_nexthop( - nhop, nhh->addr, nhh->intf, nhh->onlink, nhh->nhvrf_name, - nhh->labels, nhh->vni, NULL, nhh->weight, nhh->backup_str)); + return (nexthop_group_parse_nexthop(nhop, nhh->addr, nhh->intf, + nhh->onlink, nhh->nhvrf_name, + nhh->labels, nhh->vni, NULL, + nhh->weight, nhh->backup_str, + nhh->color)); } DEFPY(ecmp_nexthops, ecmp_nexthops_cmd, @@ -881,6 +1019,7 @@ DEFPY(ecmp_nexthops, ecmp_nexthops_cmd, |vni (1-16777215) \ |weight (1-255) \ |backup-idx WORD \ + |color (1-4294967295) \ }]", NO_STR "Specify one of the nexthops in this ECMP group\n" @@ -898,7 +1037,9 @@ DEFPY(ecmp_nexthops, ecmp_nexthops_cmd, "Weight to be used by the nexthop for purposes of ECMP\n" "Weight value to be used\n" "Specify backup nexthop indexes in another group\n" - "One or more indexes in the range (0-254) separated by ','\n") + "One or more indexes in the range (0-254) separated by ','\n" + SRTE_STR + SRTE_COLOR_STR) { VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); struct nexthop nhop; @@ -907,8 +1048,15 @@ DEFPY(ecmp_nexthops, ecmp_nexthops_cmd, bool legal; int num; uint8_t backups[NEXTHOP_MAX_BACKUPS]; - bool yes = !no; + bool yes = !no, color_config = false; + struct nexthop_hold *nhh; + struct listnode *node; + if (listcount(nhgc->nhg_child_group_list)) { + vty_out(vty, + "%% nexthop not possible when 'child-group' is configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } /* Pre-parse backup string to validate */ if (backup_idx) { lbl_ret = nexthop_str2backups(backup_idx, &num, backups); @@ -920,7 +1068,7 @@ DEFPY(ecmp_nexthops, ecmp_nexthops_cmd, legal = nexthop_group_parse_nexthop(&nhop, addr, intf, !!onlink, vrf_name, label, vni, &lbl_ret, - weight, backup_idx); + weight, backup_idx, color); if (nhop.type == NEXTHOP_TYPE_IPV6 && IN6_IS_ADDR_LINKLOCAL(&nhop.gate.ipv6)) { @@ -965,8 +1113,8 @@ DEFPY(ecmp_nexthops, ecmp_nexthops_cmd, if (nh) { nexthop_unlink(&nhgc->nhg, nh); - if (nhg_hooks.del_nexthop) - nhg_hooks.del_nexthop(nhgc, nh); + if (nhg_hooks.del_nexthop_or_child_group) + nhg_hooks.del_nexthop_or_child_group(nhgc, nh); nexthop_free(nh); } @@ -983,10 +1131,17 @@ DEFPY(ecmp_nexthops, ecmp_nexthops_cmd, /* Save config always */ nexthop_group_save_nhop(nhgc, vrf_name, addr, intf, !!onlink, - label, weight, backup_idx); + label, weight, backup_idx, color); + nhgc_configure_color(nhgc, !!color); - if (legal && nhg_hooks.add_nexthop) - nhg_hooks.add_nexthop(nhgc, nh); + if (legal && nhg_hooks.add_nexthop_or_child_group) + nhg_hooks.add_nexthop_or_child_group(nhgc, nh); + } else { + for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) { + if (nhh->color) + color_config = true; + } + nhgc_configure_color(nhgc, color_config); } return CMD_SUCCESS; @@ -1059,6 +1214,9 @@ void nexthop_group_write_nexthop(struct vty *vty, const struct nexthop *nh) if (nh->weight) vty_out(vty, " weight %u", nh->weight); + if (nh->srte_color) + vty_out(vty, " color %u", nh->srte_color); + if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) { vty_out(vty, " backup-idx %d", nh->backup_idx[0]); @@ -1116,6 +1274,9 @@ void nexthop_group_json_nexthop(json_object *j, const struct nexthop *nh) if (nh->weight) json_object_int_add(j, "weight", nh->weight); + if (nh->srte_color) + json_object_int_add(j, "color", nh->srte_color); + if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) { json_backups = json_object_new_array(); for (i = 0; i < nh->backup_num; i++) @@ -1153,6 +1314,9 @@ static void nexthop_group_write_nexthop_internal(struct vty *vty, if (nh->weight) vty_out(vty, " weight %u", nh->weight); + if (nh->color) + vty_out(vty, " color %u", nh->color); + if (nh->backup_str) vty_out(vty, " backup-idx %s", nh->backup_str); @@ -1163,12 +1327,16 @@ static int nexthop_group_write(struct vty *vty) { struct nexthop_group_cmd *nhgc; struct nexthop_hold *nh; + char *child_group; RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { struct listnode *node; vty_out(vty, "nexthop-group %s\n", nhgc->name); + if (CHECK_FLAG(nhgc->nhg.flags, NEXTHOP_GROUP_ALLOW_RECURSION)) + vty_out(vty, " allow-recursion\n"); + if (nhgc->nhg.nhgr.buckets) vty_out(vty, " resilient buckets %u idle-timer %u unbalanced-timer %u\n", @@ -1185,6 +1353,10 @@ static int nexthop_group_write(struct vty *vty) nexthop_group_write_nexthop_internal(vty, nh); } + for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_child_group_list, node, + child_group)) + vty_out(vty, " child-group %s\n", child_group); + vty_out(vty, "exit\n"); vty_out(vty, "!\n"); } @@ -1220,8 +1392,8 @@ void nexthop_group_enable_vrf(struct vrf *vrf) memcpy(nh, &nhop, sizeof(nhop)); _nexthop_add(&nhgc->nhg.nexthop, nh); - if (nhg_hooks.add_nexthop) - nhg_hooks.add_nexthop(nhgc, nh); + if (nhg_hooks.add_nexthop_or_child_group) + nhg_hooks.add_nexthop_or_child_group(nhgc, nh); } } } @@ -1230,10 +1402,18 @@ void nexthop_group_disable_vrf(struct vrf *vrf) { struct nexthop_group_cmd *nhgc; struct nexthop_hold *nhh; + char *child_group; RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { struct listnode *node, *nnode; + for (ALL_LIST_ELEMENTS(nhgc->nhg_child_group_list, node, nnode, + child_group)) { + if (nhg_hooks.del_nexthop_or_child_group) + nhg_hooks.del_nexthop_or_child_group(nhgc, NULL); + nhgl_child_group_delete(child_group); + list_delete_node(nhgc->nhg_child_group_list, node); + } for (ALL_LIST_ELEMENTS(nhgc->nhg_list, node, nnode, nhh)) { struct nexthop nhop; struct nexthop *nh; @@ -1251,8 +1431,8 @@ void nexthop_group_disable_vrf(struct vrf *vrf) _nexthop_del(&nhgc->nhg, nh); - if (nhg_hooks.del_nexthop) - nhg_hooks.del_nexthop(nhgc, nh); + if (nhg_hooks.del_nexthop_or_child_group) + nhg_hooks.del_nexthop_or_child_group(nhgc, nh); nexthop_free(nh); @@ -1263,6 +1443,25 @@ void nexthop_group_disable_vrf(struct vrf *vrf) } } +void nexthop_group_child_group_match( + const char *nhgc_name, + void (*cb_func)(const struct nexthop_group_cmd *nhgc)) +{ + struct nexthop_group_cmd *nhgc_tmp; + struct listnode *node; + char *child_group; + + RB_FOREACH (nhgc_tmp, nhgc_entry_head, &nhgc_entries) { + for (ALL_LIST_ELEMENTS_RO(nhgc_tmp->nhg_child_group_list, node, + child_group)) { + if (strmatch(child_group, nhgc_name) && cb_func) { + (*cb_func)(nhgc_tmp); + break; + } + } + } +} + void nexthop_group_interface_state_change(struct interface *ifp, ifindex_t oldifindex) { @@ -1303,8 +1502,9 @@ void nexthop_group_interface_state_change(struct interface *ifp, memcpy(nh, &nhop, sizeof(nhop)); _nexthop_add(&nhgc->nhg.nexthop, nh); - if (nhg_hooks.add_nexthop) - nhg_hooks.add_nexthop(nhgc, nh); + if (nhg_hooks.add_nexthop_or_child_group) + nhg_hooks.add_nexthop_or_child_group(nhgc, + nh); } } else { struct nexthop *next_nh; @@ -1327,8 +1527,9 @@ void nexthop_group_interface_state_change(struct interface *ifp, _nexthop_del(&nhgc->nhg, nh); - if (nhg_hooks.del_nexthop) - nhg_hooks.del_nexthop(nhgc, nh); + if (nhg_hooks.del_nexthop_or_child_group) + nhg_hooks.del_nexthop_or_child_group(nhgc, + nh); nexthop_free(nh); } @@ -1349,13 +1550,14 @@ static const struct cmd_variable_handler nhg_name_handlers[] = { {.tokenname = "NHGNAME", .completions = nhg_name_autocomplete}, {.completions = NULL}}; -void nexthop_group_init(void (*new)(const char *name), - void (*modify)(const struct nexthop_group_cmd *nhgc), - void (*add_nexthop)(const struct nexthop_group_cmd *nhg, - const struct nexthop *nhop), - void (*del_nexthop)(const struct nexthop_group_cmd *nhg, - const struct nexthop *nhop), - void (*delete)(const char *name)) +void nexthop_group_init( + void (*new)(const char *name), + void (*modify)(const struct nexthop_group_cmd *nhgc), + void (*add_nexthop_or_child_group)(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop), + void (*del_nexthop_or_child_group)(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop), + void (*delete)(const char *name)) { RB_INIT(nhgc_entry_head, &nhgc_entries); @@ -1367,11 +1569,13 @@ void nexthop_group_init(void (*new)(const char *name), install_default(NH_GROUP_NODE); install_element(NH_GROUP_NODE, &nexthop_group_backup_cmd); + install_element(NH_GROUP_NODE, &nexthop_child_group_cmd); install_element(NH_GROUP_NODE, &no_nexthop_group_backup_cmd); install_element(NH_GROUP_NODE, &ecmp_nexthops_cmd); install_element(NH_GROUP_NODE, &nexthop_group_resilience_cmd); install_element(NH_GROUP_NODE, &no_nexthop_group_resilience_cmd); + install_element(NH_GROUP_NODE, &nexthop_group_allow_recursion_cmd); memset(&nhg_hooks, 0, sizeof(nhg_hooks)); @@ -1379,10 +1583,12 @@ void nexthop_group_init(void (*new)(const char *name), nhg_hooks.new = new; if (modify) nhg_hooks.modify = modify; - if (add_nexthop) - nhg_hooks.add_nexthop = add_nexthop; - if (del_nexthop) - nhg_hooks.del_nexthop = del_nexthop; + if (add_nexthop_or_child_group) + nhg_hooks.add_nexthop_or_child_group = + add_nexthop_or_child_group; + if (del_nexthop_or_child_group) + nhg_hooks.del_nexthop_or_child_group = + del_nexthop_or_child_group; if (delete) nhg_hooks.delete = delete; } diff --git a/lib/nexthop_group.h b/lib/nexthop_group.h index 910329941884..c5a581082e57 100644 --- a/lib/nexthop_group.h +++ b/lib/nexthop_group.h @@ -34,6 +34,14 @@ struct nexthop_group { struct nexthop *nexthop; struct nhg_resilience nhgr; + + /* nexthop group flags */ +#define NEXTHOP_GROUP_ALLOW_RECURSION (1 << 0) +#define NEXTHOP_GROUP_IBGP (1 << 1) + uint8_t flags; + /* nexthop group messages */ +#define NEXTHOP_GROUP_MESSAGE_SRTE (1 << 0) + uint8_t message; }; struct nexthop_group *nexthop_group_new(void); @@ -41,6 +49,8 @@ void nexthop_group_delete(struct nexthop_group **nhg); void nexthop_group_copy(struct nexthop_group *to, const struct nexthop_group *from); +void nexthop_group_append_nexthops(struct nexthop_group *to, + const struct nexthop_group *from); /* * Copy a list of nexthops in 'nh' to an nhg, enforcing canonical sort order @@ -93,12 +103,15 @@ struct nexthop_group_cmd { struct list *nhg_list; + struct list *nhg_child_group_list; + QOBJ_FIELDS; }; RB_HEAD(nhgc_entry_head, nexthp_group_cmd); RB_PROTOTYPE(nhgc_entry_head, nexthop_group_cmd, nhgc_entry, nexthop_group_cmd_compare) DECLARE_QOBJ_TYPE(nexthop_group_cmd); +extern struct nhgc_entry_head nhgc_entries; /* * Initialize nexthop_groups. If you are interested in when @@ -116,10 +129,10 @@ DECLARE_QOBJ_TYPE(nexthop_group_cmd); void nexthop_group_init( void (*create)(const char *name), void (*modify)(const struct nexthop_group_cmd *nhgc), - void (*add_nexthop)(const struct nexthop_group_cmd *nhgc, - const struct nexthop *nhop), - void (*del_nexthop)(const struct nexthop_group_cmd *nhgc, - const struct nexthop *nhop), + void (*add_nexthop_or_child_group)(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop), + void (*del_nexthop_or_child_group)(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop), void (*destroy)(const char *name)); void nexthop_group_enable_vrf(struct vrf *vrf); @@ -127,6 +140,9 @@ void nexthop_group_disable_vrf(struct vrf *vrf); void nexthop_group_interface_state_change(struct interface *ifp, ifindex_t oldifindex); +void nexthop_group_child_group_match( + const char *nhgc_name, + void (*cb_func)(const struct nexthop_group_cmd *nhgc)); extern struct nexthop *nexthop_exists(const struct nexthop_group *nhg, const struct nexthop *nh); /* This assumes ordered */ diff --git a/lib/zclient.c b/lib/zclient.c index 557d9c3eb9b4..37e073b58b4f 100644 --- a/lib/zclient.c +++ b/lib/zclient.c @@ -951,7 +951,7 @@ static int zapi_nexthop_cmp_no_labels(const struct zapi_nexthop *next1, return 0; } -static int zapi_nexthop_cmp(const void *item1, const void *item2) +int zapi_nexthop_cmp(const void *item1, const void *item2) { int ret = 0; @@ -1156,6 +1156,44 @@ int zapi_srv6_locator_decode(struct stream *s, struct srv6_locator *l) return -1; } +static int zapi_nhg_child_encode(struct stream *s, int cmd, + struct zapi_nhg_group *api_nhg_group) +{ + int i; + + if (cmd != ZEBRA_NHG_CHILD_ADD) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: Specified zapi NHG command (%d) doesn't exist", + __func__, cmd); + return -1; + } + + stream_reset(s); + zclient_create_header(s, cmd, VRF_DEFAULT); + + stream_putw(s, api_nhg_group->proto); + stream_putl(s, api_nhg_group->id); + + stream_putw(s, api_nhg_group->child_group_num); + for (i = 0; i < api_nhg_group->child_group_num; i++) + stream_putl(s, api_nhg_group->child_group_id[i]); + + stream_putw(s, api_nhg_group->backup_child_group_num); + for (i = 0; i < api_nhg_group->backup_child_group_num; i++) + stream_putl(s, api_nhg_group->backup_child_group_id[i]); + + stream_putw(s, api_nhg_group->resilience.buckets); + stream_putl(s, api_nhg_group->resilience.idle_timer); + stream_putl(s, api_nhg_group->resilience.unbalanced_timer); + + stream_putc(s, api_nhg_group->flags); + stream_putc(s, api_nhg_group->message); + + stream_putw_at(s, 0, stream_get_endp(s)); + + return 0; +} + static int zapi_nhg_encode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) { int i; @@ -1184,6 +1222,9 @@ static int zapi_nhg_encode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) stream_putl(s, api_nhg->resilience.idle_timer); stream_putl(s, api_nhg->resilience.unbalanced_timer); + stream_putl(s, api_nhg->message); + stream_putc(s, api_nhg->flags); + if (cmd == ZEBRA_NHG_ADD) { /* Nexthops */ zapi_nexthop_group_sort(api_nhg->nexthops, @@ -1192,14 +1233,15 @@ static int zapi_nhg_encode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) stream_putw(s, api_nhg->nexthop_num); for (i = 0; i < api_nhg->nexthop_num; i++) - zapi_nexthop_encode(s, &api_nhg->nexthops[i], 0, 0); + zapi_nexthop_encode(s, &api_nhg->nexthops[i], 0, + api_nhg->message); /* Backup nexthops */ stream_putw(s, api_nhg->backup_nexthop_num); for (i = 0; i < api_nhg->backup_nexthop_num; i++) zapi_nexthop_encode(s, &api_nhg->backup_nexthops[i], 0, - 0); + api_nhg->message); } stream_putw_at(s, 0, stream_get_endp(s)); @@ -1218,6 +1260,18 @@ enum zclient_send_status zclient_nhg_send(struct zclient *zclient, int cmd, return zclient_send_message(zclient); } +enum zclient_send_status +zclient_nhg_child_send(struct zclient *zclient, int cmd, + struct zapi_nhg_group *api_nhg_group) +{ + api_nhg_group->proto = zclient->redist_default; + + if (zapi_nhg_child_encode(zclient->obuf, cmd, api_nhg_group)) + return -1; + + return zclient_send_message(zclient); +} + int zapi_route_encode(uint8_t cmd, struct stream *s, struct zapi_route *api) { struct zapi_nexthop *api_nh; diff --git a/lib/zclient.h b/lib/zclient.h index 6da9558aa560..b341005d6f49 100644 --- a/lib/zclient.h +++ b/lib/zclient.h @@ -201,6 +201,7 @@ typedef enum { ZEBRA_MLAG_FORWARD_MSG, ZEBRA_NHG_ADD, ZEBRA_NHG_DEL, + ZEBRA_NHG_CHILD_ADD, ZEBRA_NHG_NOTIFY_OWNER, ZEBRA_EVPN_REMOTE_NH_ADD, ZEBRA_EVPN_REMOTE_NH_DEL, @@ -485,6 +486,28 @@ struct zapi_nhg { uint16_t backup_nexthop_num; struct zapi_nexthop backup_nexthops[MULTIPATH_NUM]; + + /* Zebra API message flag: ZAPI_MESSAGE_XXX */ + uint32_t message; + + /* nexthop group flags */ + uint8_t flags; +}; + +struct zapi_nhg_group { + uint16_t proto; + uint32_t id; + + uint16_t child_group_num; + uint32_t child_group_id[MULTIPATH_NUM]; + + uint16_t backup_child_group_num; + uint32_t backup_child_group_id[MULTIPATH_NUM]; + + struct nhg_resilience resilience; + + uint8_t flags; + uint8_t message; }; /* @@ -1159,6 +1182,9 @@ bool zapi_srv6_sid_notify_decode(struct stream *s, struct srv6_sid_ctx *ctx, /* Nexthop-group message apis */ extern enum zclient_send_status zclient_nhg_send(struct zclient *zclient, int cmd, struct zapi_nhg *api_nhg); +extern enum zclient_send_status +zclient_nhg_child_send(struct zclient *zclient, int cmd, + struct zapi_nhg_group *api_nhg_group); #define ZEBRA_IPSET_NAME_SIZE 32 @@ -1375,6 +1401,8 @@ extern int zapi_client_close_notify_decode(struct stream *s, extern int zclient_send_zebra_gre_request(struct zclient *client, struct interface *ifp); +extern int zapi_nexthop_cmp(const void *item1, const void *item2); + #ifdef __cplusplus } #endif diff --git a/pbrd/pbr_nht.c b/pbrd/pbr_nht.c index ff252f850534..5a30b57a91b8 100644 --- a/pbrd/pbr_nht.c +++ b/pbrd/pbr_nht.c @@ -245,6 +245,8 @@ void pbr_nhgroup_add_nexthop_cb(const struct nexthop_group_cmd *nhgc, /* No nhgc but range not exhausted? Then alloc it */ pnhgc = hash_get(pbr_nhg_hash, &pnhgc_find, pbr_nhgc_alloc); } + if (!nhop) + return; /* create & insert new pnhc into pnhgc->nhh */ pnhc_find.nexthop = *nhop; @@ -281,7 +283,7 @@ void pbr_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhgc, struct pbr_nexthop_group_cache *pnhgc; struct pbr_nexthop_cache pnhc_find = {}; struct pbr_nexthop_cache *pnhc; - enum nexthop_types_t nh_type = nhop->type; + enum nexthop_types_t nh_type; /* find pnhgc by name */ strlcpy(pnhgc_find.name, nhgc->name, sizeof(pnhgc_find.name)); @@ -294,6 +296,9 @@ void pbr_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhgc, if (!pnhgc) return; + if (!nhop) + return; + /* delete pnhc from pnhgc->nhh */ pnhc_find.nexthop = *nhop; pnhc = hash_release(pnhgc->nhh, &pnhc_find); @@ -307,6 +312,8 @@ void pbr_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhgc, __func__, debugstr, nhgc->name); } + nh_type = nhop->type; + if (pnhgc->nhh->count) pbr_nht_install_nexthop_group(pnhgc, nhgc->nhg); else diff --git a/sharpd/sharp_nht.c b/sharpd/sharp_nht.c index 6d64fcfb259b..83b4211b7e94 100644 --- a/sharpd/sharp_nht.c +++ b/sharpd/sharp_nht.c @@ -66,6 +66,7 @@ struct sharp_nhg { char name[NHG_NAME_LEN]; bool installed; + bool to_be_removed; }; static uint32_t nhg_id; @@ -129,7 +130,104 @@ static void sharp_nhgroup_modify_cb(const struct nexthop_group_cmd *nhgc) if (nhgc->backup_list_name[0]) bnhgc = nhgc_find(nhgc->backup_list_name); - nhg_add(snhg->id, &nhgc->nhg, (bnhgc ? &bnhgc->nhg : NULL)); + nhg_add(snhg->id, nhgc, (bnhgc ? &bnhgc->nhg : NULL)); +} + +static void +sharp_nhgroup_child_add_nexthop_cb(const struct nexthop_group_cmd *nhgc) +{ + struct listnode *node; + struct sharp_nhg lookup; + char *child_group; + struct sharp_nhg *snhg, *snhg_tmp; + uint32_t id, nh_num = 0; + + if (!listcount(nhgc->nhg_child_group_list)) + return; + + strlcpy(lookup.name, nhgc->name, sizeof(nhgc->name)); + snhg = sharp_nhg_rb_find(&nhg_head, &lookup); + if (!snhg || !snhg->id) + return; + id = snhg->id; + + for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_child_group_list, node, + child_group)) { + strlcpy(lookup.name, child_group, sizeof(lookup.name)); + snhg_tmp = sharp_nhg_rb_find(&nhg_head, &lookup); + if (!snhg_tmp) { + zlog_debug("%s() : nhg %s, child group %s not found", + __func__, nhgc->name, child_group); + continue; + } + if (!snhg_tmp->id) { + zlog_debug("%s() : nhg %s, child group %s has no valid id %p", + __func__, nhgc->name, child_group, snhg_tmp); + continue; + } + if (!sharp_nhgroup_id_is_installed(snhg_tmp->id)) { + zlog_debug("%s() : nhg %s, child group %s not installed (%u)", + __func__, nhgc->name, child_group, + snhg_tmp->id); + continue; + } + + if (sharp_nhgroup_id_needs_removal(snhg_tmp->id)) + continue; + + /* assumption a dependent next-hop has only 1 next-hop */ + nh_num++; + } + if (nh_num) + nhg_add(id, nhgc, NULL); +} + +static void +sharp_nhgroup_child_del_nexthop_cb(const struct nexthop_group_cmd *nhgc) +{ + struct listnode *node; + struct sharp_nhg lookup; + char *child_group; + struct sharp_nhg *snhg; + int nh_num = 0, id = 0; + + if (!listcount(nhgc->nhg_child_group_list)) + return; + + for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_child_group_list, node, + child_group)) { + strlcpy(lookup.name, child_group, sizeof(lookup.name)); + snhg = sharp_nhg_rb_find(&nhg_head, &lookup); + if (!snhg) { + zlog_debug("%s() : nhg %s, child group %s not found", + __func__, nhgc->name, child_group); + continue; + } + if (!snhg->id) { + zlog_debug("%s() : nhg %s, child group %s has no valid id %p", + __func__, nhgc->name, child_group, snhg); + continue; + } + + if (sharp_nhgroup_id_needs_removal(snhg->id)) + continue; + + /* assumption a dependent next-hop has only 1 next-hop */ + nh_num++; + } + strlcpy(lookup.name, nhgc->name, sizeof(lookup.name)); + snhg = sharp_nhg_rb_find(&nhg_head, &lookup); + if (snhg) + id = snhg->id; + if (nh_num) { + zlog_debug("%s() : nhg %s, id %u needs update, now has %u groups", + __func__, nhgc->name, id, nh_num); + nhg_add(snhg->id, nhgc, NULL); + } else if (sharp_nhgroup_id_is_installed(snhg->id)) { + zlog_debug("%s() : nhg %s, id %u needs delete, no valid nh_num", + __func__, nhgc->name, id); + nhg_del(snhg->id); + } } static void sharp_nhgroup_add_nexthop_cb(const struct nexthop_group_cmd *nhgc, @@ -141,11 +239,28 @@ static void sharp_nhgroup_add_nexthop_cb(const struct nexthop_group_cmd *nhgc, strlcpy(lookup.name, nhgc->name, sizeof(lookup.name)); snhg = sharp_nhg_rb_find(&nhg_head, &lookup); + if (!snhg) { + zlog_debug("%s() : nexthop %s not found", __func__, snhg->name); + return; + } + if (!snhg->id) { + zlog_debug("%s() : nexthop %s has no valid id %p", __func__, + snhg->name, snhg); + return; + } + if (!listcount(nhgc->nhg_child_group_list)) { + if (nhgc->backup_list_name[0]) + bnhgc = nhgc_find(nhgc->backup_list_name); + nhg_add(snhg->id, nhgc, (bnhgc ? &bnhgc->nhg : NULL)); + } - if (nhgc->backup_list_name[0]) - bnhgc = nhgc_find(nhgc->backup_list_name); - - nhg_add(snhg->id, &nhgc->nhg, (bnhgc ? &bnhgc->nhg : NULL)); + /* lookup dependent nexthops */ + if (nhop) { + nexthop_group_child_group_match(nhgc->name, + sharp_nhgroup_child_add_nexthop_cb); + return; + } + sharp_nhgroup_child_add_nexthop_cb(nhgc); } static void sharp_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhgc, @@ -154,14 +269,52 @@ static void sharp_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhgc, struct sharp_nhg lookup; struct sharp_nhg *snhg; struct nexthop_group_cmd *bnhgc = NULL; + struct nexthop *nh = NULL; + int nh_num = 0; + + if (!listcount(nhgc->nhg_child_group_list)) { + strlcpy(lookup.name, nhgc->name, sizeof(lookup.name)); + snhg = sharp_nhg_rb_find(&nhg_head, &lookup); + if (nhgc->backup_list_name[0]) + bnhgc = nhgc_find(nhgc->backup_list_name); + + for (ALL_NEXTHOPS_PTR(&nhgc->nhg, nh)) { + if (nh_num >= MULTIPATH_NUM) { + zlog_warn("%s: number of nexthops greater than max multipath size, truncating", + __func__); + break; + } + + /* Unresolved nexthops will lead to failure - only send + * nexthops that zebra will consider valid. + */ + if (nh->ifindex == 0) + continue; + + nh_num++; + } + if (nh_num == 0 && sharp_nhgroup_id_is_installed(snhg->id)) { + /* before deleting, notify other users */ + snhg->to_be_removed = true; + nexthop_group_child_group_match(nhgc->name, + sharp_nhgroup_child_del_nexthop_cb); + zlog_debug("%s: nhg %s, id %u: no nexthops, deleting nexthop group", + __func__, nhgc->name, snhg->id); + nhg_del(snhg->id); + snhg->to_be_removed = false; + return; + } + + nhg_add(snhg->id, nhgc, (bnhgc ? &bnhgc->nhg : NULL)); + } - strlcpy(lookup.name, nhgc->name, sizeof(lookup.name)); - snhg = sharp_nhg_rb_find(&nhg_head, &lookup); - - if (nhgc->backup_list_name[0]) - bnhgc = nhgc_find(nhgc->backup_list_name); - - nhg_add(snhg->id, &nhgc->nhg, (bnhgc ? &bnhgc->nhg : NULL)); + /* lookup dependent nexthops */ + if (nhop) { + nexthop_group_child_group_match(nhgc->name, + sharp_nhgroup_child_del_nexthop_cb); + return; + } + sharp_nhgroup_child_del_nexthop_cb(nhgc); } static void sharp_nhgroup_delete_cb(const char *name) @@ -193,6 +346,18 @@ uint32_t sharp_nhgroup_get_id(const char *name) return snhg->id; } +void sharp_nhgroup_child_trigger_add_nexthop(uint32_t id) +{ + struct sharp_nhg *snhg; + + snhg = sharp_nhgroup_find_id(id); + if (!snhg) + return; + /* lookup dependent nexthops */ + nexthop_group_child_group_match(snhg->name, + sharp_nhgroup_child_add_nexthop_cb); +} + void sharp_nhgroup_id_set_installed(uint32_t id, bool installed) { struct sharp_nhg *snhg; @@ -219,6 +384,18 @@ bool sharp_nhgroup_id_is_installed(uint32_t id) return snhg->installed; } +bool sharp_nhgroup_id_needs_removal(uint32_t id) +{ + struct sharp_nhg *snhg; + + snhg = sharp_nhgroup_find_id(id); + if (!snhg) { + zlog_debug("%s: nhg %u not found", __func__, id); + return false; + } + return snhg->to_be_removed; +} + void sharp_nhgroup_init(void) { sharp_nhg_rb_init(&nhg_head); diff --git a/sharpd/sharp_nht.h b/sharpd/sharp_nht.h index b27952ac511c..0616e8b28a86 100644 --- a/sharpd/sharp_nht.h +++ b/sharpd/sharp_nht.h @@ -15,6 +15,8 @@ struct sharp_nh_tracker { uint32_t nhop_num; uint32_t updates; + + uint32_t color; }; extern struct sharp_nh_tracker *sharp_nh_tracker_get(struct prefix *p); @@ -25,6 +27,8 @@ extern void sharp_nh_tracker_dump(struct vty *vty); extern uint32_t sharp_nhgroup_get_id(const char *name); extern void sharp_nhgroup_id_set_installed(uint32_t id, bool installed); extern bool sharp_nhgroup_id_is_installed(uint32_t id); +extern bool sharp_nhgroup_id_needs_removal(uint32_t id); +extern void sharp_nhgroup_child_trigger_add_nexthop(uint32_t id); extern void sharp_nhgroup_init(void); #endif diff --git a/sharpd/sharp_vty.c b/sharpd/sharp_vty.c index 21c596bf7124..917f9ba4c81c 100644 --- a/sharpd/sharp_vty.c +++ b/sharpd/sharp_vty.c @@ -82,7 +82,7 @@ DEFPY(watch_redistribute, watch_redistribute_cmd, } DEFPY(watch_nexthop_v6, watch_nexthop_v6_cmd, - "sharp watch [vrf NAME$vrf_name] [connected$connected]", + "sharp watch [vrf NAME$vrf_name] [connected$connected] [color (1-4294967295)]", "Sharp routing Protocol\n" "Watch for changes\n" "The vrf we would like to watch if non-default\n" @@ -91,11 +91,14 @@ DEFPY(watch_nexthop_v6, watch_nexthop_v6_cmd, "The v6 nexthop to signal for watching\n" "Watch for import check changes\n" "The v6 prefix to signal for watching\n" - "Should the route be connected\n") + "Should the route be connected\n" + SRTE_STR + SRTE_COLOR_STR) { struct vrf *vrf; struct prefix p; bool type_import; + struct sharp_nh_tracker *nht; if (!vrf_name) vrf_name = VRF_DEFAULT_NAME; @@ -118,7 +121,10 @@ DEFPY(watch_nexthop_v6, watch_nexthop_v6_cmd, prefix_copy(&p, inhop); } - sharp_nh_tracker_get(&p); + nht = sharp_nh_tracker_get(&p); + if (color) + nht->color = color; + sharp_zebra_nexthop_watch(&p, vrf->vrf_id, type_import, true, !!connected); @@ -126,7 +132,7 @@ DEFPY(watch_nexthop_v6, watch_nexthop_v6_cmd, } DEFPY(watch_nexthop_v4, watch_nexthop_v4_cmd, - "sharp watch [vrf NAME$vrf_name] [connected$connected]", + "sharp watch [vrf NAME$vrf_name] [connected$connected] [color (1-4294967295)]", "Sharp routing Protocol\n" "Watch for changes\n" "The vrf we would like to watch if non-default\n" @@ -135,11 +141,14 @@ DEFPY(watch_nexthop_v4, watch_nexthop_v4_cmd, "The v4 address to signal for watching\n" "Watch for import check changes\n" "The v4 prefix for import check to watch\n" - "Should the route be connected\n") + "Should the route be connected\n" + SRTE_STR + SRTE_COLOR_STR) { struct vrf *vrf; struct prefix p; bool type_import; + struct sharp_nh_tracker *nht; if (!vrf_name) vrf_name = VRF_DEFAULT_NAME; @@ -163,7 +172,9 @@ DEFPY(watch_nexthop_v4, watch_nexthop_v4_cmd, prefix_copy(&p, inhop); } - sharp_nh_tracker_get(&p); + nht = sharp_nh_tracker_get(&p); + if (color) + nht->color = color; sharp_zebra_nexthop_watch(&p, vrf->vrf_id, type_import, true, !!connected); @@ -206,7 +217,7 @@ DEFPY (install_routes, |\ nexthop-group NHGNAME$nexthop_group>\ [backup$backup ] \ - (1-1000000)$routes [instance (0-255)$instance] [repeat (2-1000)$rpt] [opaque WORD] [no-recurse$norecurse]", + (1-1000000)$routes [instance (0-255)$instance] [repeat (2-1000)$rpt] [opaque WORD] [no-recurse$norecurse] [use-protocol-nexthop-group$useprotonhg]", "Sharp routing Protocol\n" "install some routes\n" "Routes to install\n" @@ -229,7 +240,8 @@ DEFPY (install_routes, "How many times to repeat this command\n" "What opaque data to send down\n" "The opaque data\n" - "No recursive nexthops\n") + "No recursive nexthops\n" + "Force to use protocol nexthop-groups\n") { struct vrf *vrf; struct prefix prefix; @@ -291,7 +303,14 @@ DEFPY (install_routes, } nhgid = sharp_nhgroup_get_id(nexthop_group); - sg.r.nhgid = nhgid; + /* Only send via ID if nhgroup has been successfully installed or use-protocol-nexthop-group is used */ + if (nhgid && + (sharp_nhgroup_id_is_installed(nhgid) || useprotonhg)) + sg.r.nhgid = nhgid; + else { + sg.r.nhgid = 0; + nhgid = 0; + } sg.r.nhop_group.nexthop = nhgc->nhg.nexthop; /* Use group's backup nexthop info if present */ diff --git a/sharpd/sharp_zebra.c b/sharpd/sharp_zebra.c index 1048436b4313..f43cd2b4a5b9 100644 --- a/sharpd/sharp_zebra.c +++ b/sharpd/sharp_zebra.c @@ -133,11 +133,6 @@ int sharp_install_lsps_helper(bool install_p, bool update_p, nh->nh_label->num_labels == 0) continue; - if (nh->type == NEXTHOP_TYPE_IFINDEX || - nh->type == NEXTHOP_TYPE_BLACKHOLE) - /* Hmm - can't really deal with these types */ - continue; - ret = zapi_nexthop_from_nexthop(znh, nh); if (ret < 0) return -1; @@ -168,11 +163,6 @@ int sharp_install_lsps_helper(bool install_p, bool update_p, nh->nh_label->num_labels == 0) return -1; - if (nh->type == NEXTHOP_TYPE_IFINDEX || - nh->type == NEXTHOP_TYPE_BLACKHOLE) - /* Hmm - can't really deal with these types */ - return -1; - ret = zapi_nexthop_from_nexthop(znh, nh); if (ret < 0) return -1; @@ -248,8 +238,7 @@ static bool route_add(const struct prefix *p, vrf_id_t vrf_id, uint8_t instance, api.flags = flags; - /* Only send via ID if nhgroup has been successfully installed */ - if (nhgid && sharp_nhgroup_id_is_installed(nhgid)) { + if (nhgid) { zapi_route_set_nhg_id(&api, &nhgid); } else { SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); @@ -529,16 +518,79 @@ void vrf_label_add(vrf_id_t vrf_id, afi_t afi, mpls_label_t label) zclient_send_vrf_label(zclient, vrf_id, afi, label, ZEBRA_LSP_SHARP); } -void nhg_add(uint32_t id, const struct nexthop_group *nhg, - const struct nexthop_group *backup_nhg) +static void nhg_add_parent(uint32_t id, const struct nexthop_group_cmd *nhgc) +{ + struct zapi_nhg_group api_nhg_group = {}; + struct zapi_nhg api_nhg = {}; + int child_group_num = 0; + const struct nexthop_group *nhg = &nhgc->nhg; + uint32_t group_id; + char *groupname; + struct listnode *node; + + if (!listcount(nhgc->nhg_child_group_list)) + return; + + api_nhg_group.id = id; + + api_nhg_group.flags = nhg->flags; + api_nhg_group.resilience = nhg->nhgr; + + if (listcount(nhgc->nhg_child_group_list) >= MULTIPATH_NUM) { + zlog_warn("%s: %s, number of nexthops groups greater than max multipath size, truncating", + __func__, nhgc->name); + return; + } + child_group_num = 0; + for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_child_group_list, node, groupname)) { + group_id = sharp_nhgroup_get_id(groupname); + if (group_id == 0) { + zlog_warn("%s: nhg %s, child group %s has no identifier", + __func__, nhgc->name, groupname); + continue; + } + + if (sharp_nhgroup_id_needs_removal(group_id)) + continue; + + api_nhg_group.child_group_id[child_group_num++] = group_id; + } + api_nhg_group.child_group_num = child_group_num; + if (api_nhg_group.child_group_num == 0) { + if (sharp_nhgroup_id_is_installed(id)) { + api_nhg.id = id; + api_nhg.flags = nhg->flags; + api_nhg.resilience = nhg->nhgr; + + zlog_debug("%s: nhg %u: no child groups, deleting nexthop group", + __func__, id); + zclient_nhg_send(zclient, ZEBRA_NHG_DEL, &api_nhg); + return; + } + zlog_debug("%s: nhg %u not sent: no valid child groups", + __func__, id); + return; + } + + zclient_nhg_child_send(zclient, ZEBRA_NHG_CHILD_ADD, &api_nhg_group); +} + +static void nhg_add_nexthop(uint32_t id, const struct nexthop_group_cmd *nhgc, + const struct nexthop_group *backup_nhg) { struct zapi_nhg api_nhg = {}; struct zapi_nexthop *api_nh; struct nexthop *nh; bool is_valid = true; + const struct nexthop_group *nhg = &nhgc->nhg; api_nhg.id = id; + api_nhg.flags = nhg->flags; + + if (CHECK_FLAG(nhg->message, NEXTHOP_GROUP_MESSAGE_SRTE)) + SET_FLAG(api_nhg.message, ZAPI_MESSAGE_SRTE); + api_nhg.resilience = nhg->nhgr; for (ALL_NEXTHOPS_PTR(nhg, nh)) { @@ -549,11 +601,9 @@ void nhg_add(uint32_t id, const struct nexthop_group *nhg, break; } - /* Unresolved nexthops will lead to failure - only send - * nexthops that zebra will consider valid. + /* + * Let zebra decide if the nexthop is valid or not */ - if (nh->ifindex == 0) - continue; api_nh = &api_nhg.nexthops[api_nhg.nexthop_num]; @@ -562,12 +612,7 @@ void nhg_add(uint32_t id, const struct nexthop_group *nhg, } if (api_nhg.nexthop_num == 0) { - if (sharp_nhgroup_id_is_installed(id)) { - zlog_debug("%s: nhg %u: no nexthops, deleting nexthop group", __func__, - id); - zclient_nhg_send(zclient, ZEBRA_NHG_DEL, &api_nhg); - return; - } + /* assumption that child nhg are removed before when id is installed */ zlog_debug("%s: nhg %u not sent: no valid nexthops", __func__, id); is_valid = false; @@ -583,18 +628,9 @@ void nhg_add(uint32_t id, const struct nexthop_group *nhg, break; } - /* Unresolved nexthop: will be rejected by zebra. - * That causes a problem, since the primary nexthops - * rely on array indexing into the backup nexthops. If - * that array isn't valid, the backup indexes won't be - * valid. + /* + * Let zebra decide if the nexthop is valid or not */ - if (nh->ifindex == 0) { - zlog_debug("%s: nhg %u: invalid backup nexthop", - __func__, id); - is_valid = false; - break; - } api_nh = &api_nhg.backup_nexthops [api_nhg.backup_nexthop_num]; @@ -609,6 +645,14 @@ void nhg_add(uint32_t id, const struct nexthop_group *nhg, zclient_nhg_send(zclient, ZEBRA_NHG_ADD, &api_nhg); } +void nhg_add(uint32_t id, const struct nexthop_group_cmd *nhgc, + const struct nexthop_group *backup_nhg) +{ + if (listcount(nhgc->nhg_child_group_list)) + return nhg_add_parent(id, nhgc); + return nhg_add_nexthop(id, nhgc, backup_nhg); +} + void nhg_del(uint32_t id) { struct zapi_nhg api_nhg = {}; @@ -677,15 +721,60 @@ static void sharp_nexthop_update(struct vrf *vrf, struct prefix *matched, struct zapi_route *nhr) { struct sharp_nh_tracker *nht; + struct nexthop_group_cmd *nhgc; + struct nexthop_group *nhg; + struct nexthop *nexthop; + uint32_t nhg_id; zlog_debug("Received update for %pFX actual match: %pFX metric: %u", matched, &nhr->prefix, nhr->metric); nht = sharp_nh_tracker_get(matched); - nht->nhop_num = nhr->nexthop_num; - nht->updates++; + + if (!nht->color || nhr->srte_color == nht->color) { + nht->nhop_num = nhr->nexthop_num; + nht->updates++; + } sharp_debug_nexthops(nhr); + + if (!nht->color) + return; + + /* check any nhg with same match, color */ + RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { + nhg_id = sharp_nhgroup_get_id(nhgc->name); + if (!nhg_id) + continue; + + nhg = &nhgc->nhg; + + if (listcount(nhgc->nhg_child_group_list)) + continue; + + if (!CHECK_FLAG(nhg->message, NEXTHOP_GROUP_MESSAGE_SRTE)) + continue; + + for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) { + if (nexthop->srte_color != nht->color) { + nexthop = nexthop->next; + continue; + } + if (matched->family == AF_INET && + (nexthop->type == NEXTHOP_TYPE_IPV4_IFINDEX || + nexthop->type == NEXTHOP_TYPE_IPV4) && + IPV4_ADDR_SAME(&matched->u.prefix4, + &nexthop->gate.ipv4)) { + nhg_add(nhg_id, nhgc, NULL); + } else if (matched->family == AF_INET6 && + (nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX || + nexthop->type == NEXTHOP_TYPE_IPV6) && + IPV6_ADDR_SAME(&matched->u.prefix6, + &nexthop->gate.ipv6)) { + nhg_add(nhg_id, nhgc, NULL); + } + } + } } static int sharp_redistribute_route(ZAPI_CALLBACK_ARGS) @@ -927,8 +1016,11 @@ static int nhg_notify_owner(ZAPI_CALLBACK_ARGS) switch (note) { case ZAPI_NHG_INSTALLED: - sharp_nhgroup_id_set_installed(id, true); + if (sharp_nhgroup_id_is_installed(id)) + break; zlog_debug("Installed nhg %u", id); + sharp_nhgroup_id_set_installed(id, true); + sharp_nhgroup_child_trigger_add_nexthop(id); break; case ZAPI_NHG_FAIL_INSTALL: zlog_debug("Failed install of nhg %u", id); diff --git a/sharpd/sharp_zebra.h b/sharpd/sharp_zebra.h index 5cbcc146654c..91ced32c9996 100644 --- a/sharpd/sharp_zebra.h +++ b/sharpd/sharp_zebra.h @@ -15,7 +15,7 @@ int sharp_zclient_create(uint32_t session_id); int sharp_zclient_delete(uint32_t session_id); extern void vrf_label_add(vrf_id_t vrf_id, afi_t afi, mpls_label_t label); -extern void nhg_add(uint32_t id, const struct nexthop_group *nhg, +extern void nhg_add(uint32_t id, const struct nexthop_group_cmd *nhgc, const struct nexthop_group *backup_nhg); extern void nhg_del(uint32_t id); extern void sharp_zebra_nexthop_watch(struct prefix *p, vrf_id_t vrf_id, diff --git a/tests/topotests/all_protocol_startup/r1/ip_nht.ref b/tests/topotests/all_protocol_startup/r1/ip_nht.ref index 2b4363b69e88..975523ae9281 100644 --- a/tests/topotests/all_protocol_startup/r1/ip_nht.ref +++ b/tests/topotests/all_protocol_startup/r1/ip_nht.ref @@ -53,6 +53,22 @@ VRF default: 6.6.6.4 unresolved Client list: pbr(fd XX) +172.31.0.200 + resolved via static, prefix 172.31.0.200/32 + via 192.168.0.44, r1-eth0 (vrf default), weight 1 + Client list: sharp(fd XX) pbr(fd XX) +192.0.2.100 + resolved via static, prefix 192.0.2.0/24 + via 192.168.0.208, r1-eth0 (vrf default), weight 1 + Client list: pbr(fd XX) +192.0.2.110 + resolved via static, prefix 192.0.2.0/24 + via 192.168.0.208, r1-eth0 (vrf default), weight 1 + Client list: pbr(fd XX) +192.0.2.200 + resolved via static, prefix 192.0.2.0/24 + via 192.168.0.208, r1-eth0 (vrf default), weight 1 + Client list: pbr(fd XX) 192.168.0.2 resolved via connected, prefix 192.168.0.0/24 is directly connected, r1-eth0 (vrf default), weight 1 @@ -61,6 +77,26 @@ VRF default: resolved via connected, prefix 192.168.0.0/24 is directly connected, r1-eth0 (vrf default), weight 1 Client list: static(fd XX) +192.168.0.44 + resolved via connected, prefix 192.168.0.0/24 + is directly connected, r1-eth0 (vrf default), weight 1 + Client list: static(fd XX) +192.168.0.202 + resolved via connected, prefix 192.168.0.0/24 + is directly connected, r1-eth0 (vrf default), weight 1 + Client list: pbr(fd XX) +192.168.0.205 + resolved via connected, prefix 192.168.0.0/24 + is directly connected, r1-eth0 (vrf default), weight 1 + Client list: pbr(fd XX) +192.168.0.207 + resolved via connected, prefix 192.168.0.0/24 + is directly connected, r1-eth0 (vrf default), weight 1 + Client list: pbr(fd XX) +192.168.0.208 + resolved via connected, prefix 192.168.0.0/24 + is directly connected, r1-eth0 (vrf default), weight 1 + Client list: static(fd XX) 192.168.7.10 resolved via connected, prefix 192.168.7.0/26 is directly connected, r1-eth7 (vrf default), weight 1 diff --git a/tests/topotests/all_protocol_startup/r1/ipv4_routes.ref b/tests/topotests/all_protocol_startup/r1/ipv4_routes.ref index 33c44780b454..d14feb15072d 100644 --- a/tests/topotests/all_protocol_startup/r1/ipv4_routes.ref +++ b/tests/topotests/all_protocol_startup/r1/ipv4_routes.ref @@ -28,6 +28,8 @@ S>* 1.1.1.5/32 [1/0] is directly connected, r1-eth5, weight 1, XX:XX:XX S>* 1.1.1.6/32 [1/0] is directly connected, r1-eth6, weight 1, XX:XX:XX S>* 1.1.1.7/32 [1/0] is directly connected, r1-eth7, weight 1, XX:XX:XX S>* 1.1.1.8/32 [1/0] is directly connected, r1-eth8, weight 1, XX:XX:XX +S>* 172.31.0.200/32 [1/0] via 192.168.0.44, r1-eth0, weight 1, XX:XX:XX +S>* 192.0.2.0/24 [1/0] via 192.168.0.208, r1-eth0, weight 1, XX:XX:XX S>* 4.5.6.10/32 [1/0] via 192.168.0.2, r1-eth0, weight 1, XX:XX:XX S>* 4.5.6.11/32 [1/0] via 192.168.0.2, r1-eth0, weight 1, XX:XX:XX S>* 4.5.6.12/32 [1/0] is directly connected, r1-eth0, weight 1, XX:XX:XX diff --git a/tests/topotests/all_protocol_startup/r1/pathd.conf b/tests/topotests/all_protocol_startup/r1/pathd.conf new file mode 100644 index 000000000000..6fb037066941 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/pathd.conf @@ -0,0 +1,12 @@ +segment-routing + traffic-eng + segment-list seg1 + index 10 mpls label 400 + index 20 mpls label 600 + exit + policy color 1 endpoint 172.31.0.200 + name steer_color1 + candidate-path preference 10 name to210 explicit segment-list seg1 + exit + exit +exit diff --git a/tests/topotests/all_protocol_startup/r1/zebra.conf b/tests/topotests/all_protocol_startup/r1/zebra.conf index c5ef79630e21..f75d7377b8a2 100644 --- a/tests/topotests/all_protocol_startup/r1/zebra.conf +++ b/tests/topotests/all_protocol_startup/r1/zebra.conf @@ -35,6 +35,7 @@ ip route 1.1.1.5/32 r1-eth5 ip route 1.1.1.6/32 r1-eth6 ip route 1.1.1.7/32 r1-eth7 ip route 1.1.1.8/32 r1-eth8 +ip route 172.31.0.200/32 192.168.0.44 # Create a route that has overlapping distance # so we have backups @@ -46,6 +47,13 @@ ip route 4.5.6.16/32 192.168.0.4 10 ip route 4.5.6.17/32 192.168.0.2 tag 9000 ip route 4.5.6.17/32 192.168.0.2 tag 10000 +! Create a static route to test recursive +! resolution +ip route 192.0.2.0/24 192.168.0.208 + +# Create mpls lsp entry to resolve TE +# seg1 segment-list needs to resolve label 400 +mpls lsp 400 192.168.0.209 500 ! interface r1-eth0 description to sw0 - no routing protocol diff --git a/tests/topotests/all_protocol_startup/test_all_protocol_startup.py b/tests/topotests/all_protocol_startup/test_all_protocol_startup.py index 80bd2505a73d..cbbd43b0c272 100644 --- a/tests/topotests/all_protocol_startup/test_all_protocol_startup.py +++ b/tests/topotests/all_protocol_startup/test_all_protocol_startup.py @@ -19,6 +19,7 @@ import sys import pytest import glob +from functools import partial from time import sleep pytestmark = [ @@ -38,8 +39,13 @@ required_linux_kernel_version, ) +from lib.nexthopgroup import ( + route_get_nhg_id, + verify_nexthop_group, + verify_route_nexthop_group, +) + import json -import functools # Global that must be set on a failure to stop subsequent tests from being run fatal_error = "" @@ -108,6 +114,7 @@ def setup_module(module): net["r{}".format(i)].loadConf("nhrpd", "{}/r{}/nhrpd.conf".format(thisDir, i)) net["r{}".format(i)].loadConf("babeld", "{}/r{}/babeld.conf".format(thisDir, i)) net["r{}".format(i)].loadConf("pbrd", "{}/r{}/pbrd.conf".format(thisDir, i)) + net["r{}".format(i)].loadConf("pathd", "{}/r{}/pathd.conf".format(thisDir, i)) tgen.gears["r{}".format(i)].start() # For debugging after starting FRR daemons, uncomment the next line @@ -401,111 +408,121 @@ def test_converge_protocols(): assert failures == 0, "IPv6 Routing table failed for r{}\n{}".format(i, diff) -def route_get_nhg_id(route_str): - global fatal_error +def verify_nexthop_group_inactive(nhg_id): + net = get_topogen().net + inactive = None + uninstalled = None + found = False + count = 0 - def get_func(route_str): - net = get_topogen().net - output = net["r1"].cmd( - 'vtysh -c "show ip route {} nexthop-group"'.format(route_str) - ) - match = re.search(r"Nexthop Group ID: (\d+)", output) - if match is not None: - nhg_id = int(match.group(1)) - return nhg_id - else: - return None + while not found and count < 10: + count += 1 + # Verify NHG is invalid/not installed + output = net["r1"].cmd('vtysh -c "show nexthop-group rib {}"'.format(nhg_id)) + inactive = re.search(r"inactive", output) + if inactive is None: + sleep(1) + continue + uninstalled = not re.search(r"Installed", output) + if uninstalled is None: + sleep(1) + continue + found = True - test_func = functools.partial(get_func, route_str) - _, nhg_id = topotest.run_and_expect_type(test_func, int, count=30, wait=1) - if nhg_id == None: - fatal_error = "Nexthop Group ID not found for route {}".format(route_str) - assert nhg_id != None, fatal_error - else: - return nhg_id + assert inactive is not None, "Nexthop Group ID={} not marked inactive".format( + nhg_id + ) + assert uninstalled is not None, "Nexthop Group ID={} marked Installed".format( + nhg_id + ) -def verify_nexthop_group(nhg_id, recursive=False, ecmp=0): +def verify_nexthop_group_duplicate(nhg_id): net = get_topogen().net + duplicate = None count = 0 - valid = None - ecmpcount = None - depends = None - resolved_id = None - installed = None found = False while not found and count < 10: count += 1 - # Verify NHG is valid/installed - output = net["r1"].cmd('vtysh -c "show nexthop-group rib {}"'.format(nhg_id)) - valid = re.search(r"Valid", output) - if valid is None: - found = False + # Verify any nexthop is duplicated + output = net["r1"].cmd( + 'vtysh -c "show nexthop-group rib {} json"'.format(nhg_id) + ) + joutput = json.loads(output) + for nhgid in joutput: + n = joutput[nhgid] + for nh in n["nexthops"]: + if "duplicate" not in nh.keys() or not nh["duplicate"]: + continue + if "fib" not in nh.keys() or not nh["fib"]: + found = True + if not found: sleep(1) continue - if ecmp or recursive: - ecmpcount = re.search(r"Depends:.*\n", output) - if ecmpcount is None: - found = False - sleep(1) - continue + assert found, "Nexthop Group ID={} does not have duplicated nexthops".format(nhg_id) - # list of IDs in group - depends = re.findall(r"\((\d+)\)", ecmpcount.group(0)) - if ecmp: - if len(depends) != ecmp: - found = False - sleep(1) - continue - else: - # If recursive, we need to look at its resolved group - if len(depends) != 1: - found = False - sleep(1) - continue - - resolved_id = int(depends[0]) - verify_nexthop_group(resolved_id, False) - else: - installed = re.search(r"Installed", output) - if installed is None: - found = False - sleep(1) - continue - found = True - - assert valid is not None, "Nexthop Group ID={} not marked Valid".format(nhg_id) - if ecmp or recursive: - assert ecmpcount is not None, "Nexthop Group ID={} has no depends".format( - nhg_id - ) - if ecmp: - assert ( - len(depends) == ecmp - ), "Nexthop Group ID={} doesn't match ecmp size".format(nhg_id) - else: - assert ( - len(depends) == 1 - ), "Nexthop Group ID={} should only have one recursive depend".format( - nhg_id - ) +def verify_nexthop_group_inactive_nexthop(nexthop, client="sharp"): + net = get_topogen().net + if client: + cmd_str = 'vtysh -c "show nexthop-group rib {} json"'.format(client) else: - assert installed is not None, "Nexthop Group ID={} not marked Installed".format( - nhg_id - ) + cmd_str = 'vtysh -c "show nexthop-group rib json"'.format(client) + output = net["r1"].cmd(cmd_str) + joutput = json.loads(output) -def verify_route_nexthop_group(route_str, recursive=False, ecmp=0): - global fatal_error + for nhgid in joutput: + n = joutput[nhgid] + if "nexthops" not in n: + continue + if "ip" not in n["nexthops"][0].keys(): + continue + if n["nexthops"][0]["ip"] == nexthop: + if "active" in n["nexthops"][0].keys() and n["nexthops"][0]["active"]: + assert 0, "nexthop-group {} marked active, expected inactive".format( + nhgid + ) - # Verify route and that zebra created NHGs for and they are valid/installed - nhg_id = route_get_nhg_id(route_str) +def check_show_nexthop_group_nexthops(nhg_id, nexthop, srte_color=None, labels=None): + net = get_topogen().net - verify_nexthop_group(nhg_id, recursive, ecmp) + try: + joutput = json.loads( + net["r1"].cmd('vtysh -c "show nexthop-group rib {} json"'.format(nhg_id)) + ) + except: + joutput = [] + jnexthops = joutput[str(nhg_id)]["nexthops"] + for jnexthop in jnexthops: + if "recursive" in jnexthop.keys(): + if "srteColor" in jnexthop.keys(): + if srte_color is not None and jnexthop["srteColor"] != srte_color: + return f'nexthop IP {jnexthop["ip"]}, srteColor not colored {jnexthop["srteColor"]}' + continue + elif srte_color is None: + continue + return f'nexthop IP {jnexthop["ip"]}, srteColor expected' + # recursive nexthop + if "ip" in jnexthop.keys(): + if nexthop is None: + return f'unexpected nexthop IP address present : {jnexthop["ip"]}' + if jnexthop["ip"] != nexthop: + return f'unexpected nexthop IP address in nexthop : {jnexthop["ip"]}' + elif nexthop is not None: + return f"unexpected nexthop IP address not present" + + if "labels" in jnexthop.keys(): + if labels == None: + return f'unexpected label in nexthop present: {jnexthop["labels"]}' + if jnexthop["labels"] != labels: + return f'unexpected label in nexthop present: {jnexthop["labels"]}' + elif labels is not None: + return f"unexpected label in nexthop not present" + return None def test_nexthop_groups(): @@ -530,7 +547,7 @@ def test_nexthop_groups(): # Create with sharpd using nexthop-group net["r1"].cmd('vtysh -c "sharp install routes 2.2.2.1 nexthop-group basic 1"') - verify_route_nexthop_group("2.2.2.1/32") + verify_route_nexthop_group("2.2.2.1/32", "r1") ## Connected @@ -539,7 +556,7 @@ def test_nexthop_groups(): ) net["r1"].cmd('vtysh -c "sharp install routes 2.2.2.2 nexthop-group connected 1"') - verify_route_nexthop_group("2.2.2.2/32") + verify_route_nexthop_group("2.2.2.2/32", "r1") ## Recursive @@ -551,7 +568,7 @@ def test_nexthop_groups(): 'vtysh -c "sharp install routes 3.3.3.1 nexthop-group basic-recursive 1"' ) - verify_route_nexthop_group("3.3.3.1/32", True) + verify_route_nexthop_group("3.3.3.1/32", "r1", True) ## Duplicate @@ -561,7 +578,7 @@ def test_nexthop_groups(): net["r1"].cmd('vtysh -c "sharp install routes 3.3.3.2 nexthop-group duplicate 1"') - verify_route_nexthop_group("3.3.3.2/32") + verify_route_nexthop_group("3.3.3.2/32", "r1") ## Two 4-Way ECMP @@ -572,7 +589,7 @@ def test_nexthop_groups(): net["r1"].cmd('vtysh -c "sharp install routes 4.4.4.1 nexthop-group fourA 1"') - verify_route_nexthop_group("4.4.4.1/32") + verify_route_nexthop_group("4.4.4.1/32", "r1") net["r1"].cmd( 'vtysh -c "c t" -c "nexthop-group fourB" -c "nexthop 1.1.1.5" -c "nexthop 1.1.1.6" \ @@ -581,7 +598,7 @@ def test_nexthop_groups(): net["r1"].cmd('vtysh -c "sharp install routes 4.4.4.2 nexthop-group fourB 1"') - verify_route_nexthop_group("4.4.4.2/32") + verify_route_nexthop_group("4.4.4.2/32", "r1") ## Recursive to 8-Way ECMP @@ -593,7 +610,7 @@ def test_nexthop_groups(): 'vtysh -c "sharp install routes 5.5.5.1 nexthop-group eight-recursive 1"' ) - verify_route_nexthop_group("5.5.5.1/32") + verify_route_nexthop_group("5.5.5.1/32", "r1") ## 4-way ECMP Routes Pointing to Each Other @@ -634,7 +651,7 @@ def test_nexthop_groups(): # Get routes and test if has too many (duplicate) nexthops count = 0 dups = [] - nhg_id = route_get_nhg_id("6.6.6.1/32") + nhg_id = route_get_nhg_id("6.6.6.1/32", "r1") while (len(dups) != 4) and count < 10: output = net["r1"].cmd('vtysh -c "show nexthop-group rib {}"'.format(nhg_id)) @@ -650,6 +667,206 @@ def test_nexthop_groups(): nhg_id ) + ## nexthop-group TESTALLOWRECURSION + ## create a static route, and use that static route to resolve the nexthop-group + tgen = get_topogen() + net["r1"].cmd( + 'vtysh -c "configure terminal" \ + -c "nexthop-group TESTALLOWRECURSION" \ + -c "nexthop 192.0.2.200"' + ) + # the TESTALLOWRECURSION nexthop-group should be inactive + # check that the sharp nexthop-group for 9.9.9.9 is inactive. + verify_nexthop_group_inactive_nexthop("192.0.2.200") + + net["r1"].cmd( + 'vtysh -c "configure terminal" \ + -c "nexthop-group TESTALLOWRECURSION" \ + -c "allow-recursion"' + ) + + net["r1"].cmd( + 'vtysh -c "sharp install routes 9.9.9.9 nexthop-group TESTALLOWRECURSION 1"' + ) + + nhg_id = route_get_nhg_id("9.9.9.9/32", "r1") + + verify_nexthop_group(nhg_id, "r1") + + net["r1"].cmd( + 'vtysh -c "configure terminal" \ + -c "nexthop-group TESTALLOWRECURSION" \ + -c "no allow-recursion"' + ) + nhg_id_local = route_get_nhg_id("9.9.9.9/32", "r1") + assert ( + nhg_id == nhg_id_local + ), "r1, after disabling recursion, NHG ID changed for 9.9.9.9/32, old {}, new {}".format( + nhg_id, nhg_id_local + ) + + verify_nexthop_group_inactive(nhg_id) + + ## Colored nexthop test + # create a non colored nexthop, and expect that the nexthop is steered by default + net["r1"].cmd( + 'vtysh -c "configure terminal" \ + -c "nexthop-group TESTSRTE" \ + -c "allow-recursion" \ + -c "nexthop 172.31.0.200"' + ) + + net["r1"].cmd( + 'vtysh -c "sharp install routes 10.10.10.10 nexthop-group TESTSRTE 1"' + ) + + nhg_id = route_get_nhg_id("10.10.10.10/32", "r1") + verify_nexthop_group(nhg_id, "r1") + # Use the json output to check the nexthops validity + # - main nexthop has no srteColor, and ip set to 172.31.0.200 + # - recursive nexthop has no label values and recurses to 192.168.0.44, as per static route: + # ip route 172.31.0.200/32 192.168.0.44 + test_func = partial( + check_show_nexthop_group_nexthops, nhg_id, nexthop="192.168.0.44" + ) + success, _ = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r1, route to 10.10.10.10 nexthop 172.31.0.200 with no SRTE color, nexthop not expected" + + ## register to nexthop tracking events to 172.31.0.200 color 1 + net["r1"].cmd('vtysh -c "sharp watch nexthop 172.31.0.200 color 1"') + + ## modify nexthop-group to use the color, and expect that the nexthop is steered by pathd + net["r1"].cmd( + 'vtysh -c "configure terminal" \ + -c "nexthop-group TESTSRTE" \ + -c "allow-recursion" \ + -c "nexthop 172.31.0.200 color 1"' + ) + + # Use the json output to check the nexthops validity + # - main nexthop has srteColor=1 + # - recursive nexthop has ip set to 192.168.0.209 as per lsp entry: + # mpls lsp 400 192.168.0.209 500 + # - recursive nexthop has label [400, 500] values as per pathd segment-list + test_func = partial( + check_show_nexthop_group_nexthops, + nhg_id, + nexthop="192.168.0.209", + srte_color=1, + labels=[400, 600], + ) + success, _ = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r1, route to 10.10.10.10 nexthop 172.31.0.200 SRTE color 1, nexthop not expected" + + # remove srte policy + output = net["r1"].cmd( + 'vtysh -c "configure terminal" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "no policy color 1 endpoint 172.31.0.200"' + ) + + # Use the json output to check the nexthops validity + # - main nexthop has srteColor=1, and ip set to 172.31.0.200 + # - recursive nexthop has no label values + test_func = partial( + check_show_nexthop_group_nexthops, + nhg_id, + nexthop="192.168.0.44", + srte_color=1, + labels=None, + ) + success, _ = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r1, route to 10.10.10.10 nexthop 172.31.0.200 SRTE color 1, nexthop not expected" + + ## modify nexthop-group to not use the color, and expect that the nexthop is steered by staticd + net["r1"].cmd( + 'vtysh -c "configure terminal" \ + -c "nexthop-group TESTSRTE" \ + -c "allow-recursion" \ + -c "nexthop 172.31.0.200"' + ) + + # Use the json output to check the nexthops validity + # - main nexthop has no srteColor, and ip set to 172.31.0.200 + # - recursive nexthop has no label values and recurses to 192.168.0.44, as per static route: + # ip route 172.31.0.200/32 192.168.0.44 + test_func = partial( + check_show_nexthop_group_nexthops, nhg_id, nexthop="192.168.0.44" + ) + success, _ = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r1, route to 10.10.10.10 nexthop 172.31.0.200 with no SRTE color, nexthop not expected" + + net["r1"].cmd('vtysh -c "sharp remove routes 10.10.10.10 1"') + + ## child-group configration + net["r1"].cmd( + 'vtysh -c "configure terminal" \ + -c "nexthop-group GROUP1" \ + -c "child-group ECMP1" \ + -c "child-group ECMP2" \ + -c "exit" \ + -c "nexthop-group ECMP1" \ + -c "nexthop 192.168.0.202 r1-eth0" \ + -c "exit" \ + -c "nexthop-group ECMP2" \ + -c "nexthop 192.168.0.205 r1-eth0"' + ) + sleep(1) + net["r1"].cmd('vtysh -c "sharp install routes 8.8.8.8 nexthop-group GROUP1 1\n"') + verify_route_nexthop_group("8.8.8.8/32", "r1", ecmp=2) + + net["r1"].cmd( + 'vtysh -c "configure terminal" \ + -c "nexthop-group GROUP1" \ + -c "no child-group ECMP2\n"' + ) + sleep(1) + verify_route_nexthop_group("8.8.8.8/32", "r1", ecmp=1) + net["r1"].cmd( + 'vtysh -c "configure terminal" \ + -c "nexthop-group ECMP3" \ + -c "nexthop 192.168.0.207 r1-eth0" \ + -c "nexthop-group GROUP1" \ + -c "child-group ECMP3" \ + -c "child-group ECMP2"' + ) + sleep(1) + verify_route_nexthop_group("8.8.8.8/32", "r1", ecmp=3) + + ## duplicate configration + net["r1"].cmd( + 'vtysh -c "configure terminal" \ + -c "nexthop-group GROUP2" \ + -c "allow-recursion" \ + -c "child-group ECMP4" \ + -c "child-group ECMP5" \ + -c "exit" \ + -c "nexthop-group ECMP4" \ + -c "allow-recursion" \ + -c "nexthop 192.0.2.100" \ + -c "exit" \ + -c "nexthop-group ECMP5" \ + -c "allow-recursion" \ + -c "nexthop 192.0.2.110"' + ) + net["r1"].cmd( + 'vtysh -c "sharp install routes 12.12.12.12 nexthop-group GROUP2 1\n"' + ) + + verify_route_nexthop_group("12.12.12.12/32", "r1", ecmp=2) + + nhg_id = route_get_nhg_id("12.12.12.12/32", "r1") + verify_nexthop_group_duplicate(nhg_id) + ## Remove all NHG routes net["r1"].cmd('vtysh -c "sharp remove routes 2.2.2.1 1"') @@ -660,6 +877,8 @@ def test_nexthop_groups(): net["r1"].cmd('vtysh -c "sharp remove routes 4.4.4.2 1"') net["r1"].cmd('vtysh -c "sharp remove routes 5.5.5.1 1"') net["r1"].cmd('vtysh -c "sharp remove routes 6.6.6.1 4"') + net["r1"].cmd('vtysh -c "sharp remove routes 9.9.9.9 1"') + net["r1"].cmd('vtysh -c "sharp remove routes 8.8.8.8 1"') net["r1"].cmd('vtysh -c "c t" -c "no ip route 6.6.6.0/24 1.1.1.1"') @@ -1478,7 +1697,7 @@ def test_nexthop_groups_with_route_maps(): 'vtysh -c "sharp install routes {} nexthop-group test 1"'.format(route_str) ) - verify_route_nexthop_group("{}/32".format(route_str)) + verify_route_nexthop_group("{}/32".format(route_str), "r1") # Only a valid test on linux using nexthop objects if sys.platform.startswith("linux"): @@ -1522,14 +1741,14 @@ def test_nexthop_groups_with_route_maps(): ) ) - verify_route_nexthop_group("{}/32".format(permit_route_str)) + verify_route_nexthop_group("{}/32".format(permit_route_str), "r1") # This route should be denied net["r1"].cmd( 'vtysh -c "sharp install routes {} nexthop-group test 1"'.format(deny_route_str) ) - nhg_id = route_get_nhg_id(deny_route_str) + nhg_id = route_get_nhg_id(deny_route_str, "r1") output = net["r1"].cmd('vtysh -c "show nexthop-group rib {}"'.format(nhg_id)) match = re.search(r"Valid", output) @@ -1586,7 +1805,7 @@ def test_nexthop_group_replace(): # Create with sharpd using nexthop-group net["r1"].cmd('vtysh -c "sharp install routes 3.3.3.1 nexthop-group replace 1"') - verify_route_nexthop_group("3.3.3.1/32") + verify_route_nexthop_group("3.3.3.1/32", "r1") # Change the nexthop group net["r1"].cmd( @@ -1594,7 +1813,7 @@ def test_nexthop_group_replace(): ) # Verify it updated. We can just check install and ecmp count here. - verify_route_nexthop_group("3.3.3.1/32", False, 3) + verify_route_nexthop_group("3.3.3.1/32", "r1", False, 3) def test_mpls_interfaces(): @@ -1707,7 +1926,7 @@ def _show_func(): fatal_error = "Resilient NHG not found in json output" assert "buckets" in n, fatal_error - verify_nexthop_group(int(nhgid)) + verify_nexthop_group(int(nhgid), "r1") # Remove NHG net["r1"].cmd('vtysh -c "conf" -c "no nexthop-group resilience"') diff --git a/tests/topotests/bgp_nhg_topo1/__init__.py b/tests/topotests/bgp_nhg_topo1/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/topotests/bgp_nhg_topo1/ce10/bgpd.conf b/tests/topotests/bgp_nhg_topo1/ce10/bgpd.conf new file mode 100644 index 000000000000..4aae2b98987f --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/ce10/bgpd.conf @@ -0,0 +1,11 @@ +router bgp 64500 + bgp router-id 192.0.2.9 + no bgp ebgp-requires-policy + neighbor 172.31.21.5 remote-as 64500 + neighbor 172.31.22.6 remote-as 64500 + neighbor 172.31.23.8 remote-as 64500 + address-family ipv4 unicast + network 192.0.2.9/32 + ! +! + diff --git a/tests/topotests/bgp_nhg_topo1/ce10/zebra.conf b/tests/topotests/bgp_nhg_topo1/ce10/zebra.conf new file mode 100644 index 000000000000..f5303fd7b394 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/ce10/zebra.conf @@ -0,0 +1,13 @@ +log stdout +interface lo + ip address 192.0.2.9/32 +! +interface ce10-eth0 + ip address 172.31.21.9/24 +! +interface ce10-eth1 + ip address 172.31.22.9/24 +! +interface ce10-eth3 + ip address 172.31.23.9/24 +! diff --git a/tests/topotests/bgp_nhg_topo1/ce7/bgpd.conf b/tests/topotests/bgp_nhg_topo1/ce7/bgpd.conf new file mode 100644 index 000000000000..eb03f4323e3f --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/ce7/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 64499 + bgp router-id 192.0.2.7 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 172.31.10.1 remote-as 64500 + address-family ipv4 unicast + network 192.0.2.7/32 +! + diff --git a/tests/topotests/bgp_nhg_topo1/ce7/zebra.conf b/tests/topotests/bgp_nhg_topo1/ce7/zebra.conf new file mode 100644 index 000000000000..b8f098122df1 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/ce7/zebra.conf @@ -0,0 +1,7 @@ +log stdout +interface lo + ip address 192.0.2.7/32 +! +interface ce7-eth0 + ip address 172.31.10.7/24 +! diff --git a/tests/topotests/bgp_nhg_topo1/ce8/bgpd.conf b/tests/topotests/bgp_nhg_topo1/ce8/bgpd.conf new file mode 100644 index 000000000000..52f3c920a38f --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/ce8/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 64498 + bgp router-id 192.0.2.8 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 172.31.11.1 remote-as 64500 + address-family ipv4 unicast + network 192.0.2.7/32 +! + diff --git a/tests/topotests/bgp_nhg_topo1/ce8/zebra.conf b/tests/topotests/bgp_nhg_topo1/ce8/zebra.conf new file mode 100644 index 000000000000..1fbacba070ec --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/ce8/zebra.conf @@ -0,0 +1,7 @@ +log stdout +interface lo + ip address 192.0.2.8/32 +! +interface ce8-eth0 + ip address 172.31.11.8/24 +! diff --git a/tests/topotests/bgp_nhg_topo1/ce9/bgpd.conf b/tests/topotests/bgp_nhg_topo1/ce9/bgpd.conf new file mode 100644 index 000000000000..b2fc858ab93f --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/ce9/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 64500 + bgp router-id 192.0.2.7 + no bgp ebgp-requires-policy + neighbor 172.31.12.5 remote-as 64500 + neighbor 172.31.13.6 remote-as 64500 + address-family ipv4 unicast + network 192.0.2.9/32 + ! +! + diff --git a/tests/topotests/bgp_nhg_topo1/ce9/zebra.conf b/tests/topotests/bgp_nhg_topo1/ce9/zebra.conf new file mode 100644 index 000000000000..a9784fecd6bd --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/ce9/zebra.conf @@ -0,0 +1,13 @@ +log stdout +interface lo + ip address 192.0.2.9/32 +! +interface loop2 + ip address 192.0.2.9/32 +! +interface ce9-eth0 + ip address 172.31.12.9/24 +! +interface ce9-eth1 + ip address 172.31.13.9/24 +! diff --git a/tests/topotests/bgp_nhg_topo1/r1/bgpd.conf b/tests/topotests/bgp_nhg_topo1/r1/bgpd.conf new file mode 100644 index 000000000000..3ff0972d0ba5 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r1/bgpd.conf @@ -0,0 +1,35 @@ +bgp nexthop-group +router bgp 64500 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + neighbor rrserver peer-group + neighbor rrserver remote-as 64500 + neighbor rrserver update-source lo + neighbor rrserver timers connect 2 + neighbor 192.0.2.3 peer-group rrserver + address-family ipv4 unicast + neighbor rrserver next-hop-self + neighbor rrserver activate + exit-address-family + address-family ipv4 vpn + neighbor rrserver next-hop-self + neighbor rrserver activate + exit-address-family +! +router bgp 64500 vrf vrf1 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + neighbor 172.31.10.7 remote-as 64499 + address-family ipv4 unicast + label vpn export 101 + rd vpn export 444:1 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +router bgp 64500 vrf vrf2 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + neighbor 172.31.11.8 remote-as 64498 +! diff --git a/tests/topotests/bgp_nhg_topo1/r1/isisd.conf b/tests/topotests/bgp_nhg_topo1/r1/isisd.conf new file mode 100644 index 000000000000..9660577f4e8f --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r1/isisd.conf @@ -0,0 +1,26 @@ +hostname r1 +interface lo + ip router isis 1 + isis passive +! +interface r1-eth1 + ip router isis 1 + isis network point-to-point +! +interface r1-eth2 + ip router isis 1 + isis network point-to-point +! +interface r1-eth4 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0001.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.1/32 index 1 +! diff --git a/tests/topotests/bgp_nhg_topo1/r1/zebra.conf b/tests/topotests/bgp_nhg_topo1/r1/zebra.conf new file mode 100644 index 000000000000..2e3549a57401 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r1/zebra.conf @@ -0,0 +1,24 @@ +log stdout +interface lo + ip address 192.0.2.1/32 +! +interface r1-eth0 + ip address 172.31.10.1/24 +! +interface r1-eth1 + ip address 172.31.0.1/24 + mpls enable +! +interface r1-eth2 + ip address 172.31.2.1/24 + mpls enable +! +interface r1-eth3 + ip address 172.31.11.1/24 + mpls enable +! +interface r1-eth4 + ip address 172.31.8.1/24 + mpls enable +! + diff --git a/tests/topotests/bgp_nhg_topo1/r3/bgpd.conf b/tests/topotests/bgp_nhg_topo1/r3/bgpd.conf new file mode 100644 index 000000000000..d4563a355d03 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r3/bgpd.conf @@ -0,0 +1,16 @@ +router bgp 64500 + bgp router-id 192.0.2.3 + neighbor rr peer-group + neighbor rr remote-as 64500 + neighbor rr update-source lo + bgp listen range 192.0.2.0/24 peer-group rr + ! + address-family ipv4 unicast + neighbor rr activate + neighbor rr route-reflector-client + exit-address-family + address-family ipv4 vpn + neighbor rr activate + neighbor rr route-reflector-client + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_topo1/r3/isisd.conf b/tests/topotests/bgp_nhg_topo1/r3/isisd.conf new file mode 100644 index 000000000000..ae6bddee9208 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r3/isisd.conf @@ -0,0 +1,38 @@ +hostname r3 +interface lo + ip router isis 1 + isis passive +! +interface r3-eth0 + ip router isis 1 + isis network point-to-point +! +interface r3-eth1 + ip router isis 1 + isis network point-to-point +! +interface r3-eth2 + ip router isis 1 + isis network point-to-point +! +interface r3-eth3 + ip router isis 1 + isis network point-to-point +! +interface r3-eth4 + ip router isis 1 + isis network point-to-point +! +interface r3-eth5 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0003.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.3/32 index 3 +! diff --git a/tests/topotests/bgp_nhg_topo1/r3/zebra.conf b/tests/topotests/bgp_nhg_topo1/r3/zebra.conf new file mode 100644 index 000000000000..05b3769fb8bf --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r3/zebra.conf @@ -0,0 +1,16 @@ +log stdout +interface lo + ip address 192.0.2.3/32 +! +interface r3-eth0 + ip address 172.31.0.3/24 + mpls enable +! +interface r3-eth1 + ip address 172.31.4.3/24 + mpls enable +! +interface r3-eth2 + ip address 172.31.5.3/24 + mpls enable +! diff --git a/tests/topotests/bgp_nhg_topo1/r4/isisd.conf b/tests/topotests/bgp_nhg_topo1/r4/isisd.conf new file mode 100644 index 000000000000..4d49d0de0a45 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r4/isisd.conf @@ -0,0 +1,30 @@ +hostname r4 +interface lo + ip router isis 1 + isis passive +! +interface r4-eth0 + ip router isis 1 + isis network point-to-point +! +interface r4-eth1 + ip router isis 1 + isis network point-to-point +! +interface r4-eth2 + ip router isis 1 + isis network point-to-point +! +interface r4-eth3 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0004.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.4/32 index 4 +! diff --git a/tests/topotests/bgp_nhg_topo1/r4/zebra.conf b/tests/topotests/bgp_nhg_topo1/r4/zebra.conf new file mode 100644 index 000000000000..002da72bf907 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r4/zebra.conf @@ -0,0 +1,21 @@ +log stdout +interface lo + ip address 192.0.2.4/32 +! +interface r4-eth0 + ip address 172.31.2.4/24 + mpls enable + shutdown +! +interface r4-eth1 + ip address 172.31.6.4/24 + mpls enable +! +interface r4-eth2 + ip address 172.31.7.4/24 + mpls enable +! +interface r4-eth3 + mpls enable +! + diff --git a/tests/topotests/bgp_nhg_topo1/r5/bgpd.conf b/tests/topotests/bgp_nhg_topo1/r5/bgpd.conf new file mode 100644 index 000000000000..5b38eeff4108 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r5/bgpd.conf @@ -0,0 +1,32 @@ +router bgp 64500 + bgp router-id 192.0.2.5 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor rrserver peer-group + neighbor rrserver remote-as 64500 + neighbor rrserver update-source lo + neighbor rrserver timers connect 2 + neighbor 192.0.2.3 peer-group rrserver + address-family ipv4 unicast + network 192.0.2.9/32 + neighbor rrserver activate + neighbor rrserver next-hop-self + exit-address-family + address-family ipv4 vpn + neighbor rrserver activate + neighbor rrserver next-hop-self + exit-address-family +! +router bgp 64500 vrf vrf1 + bgp router-id 192.0.2.5 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 172.31.21.9 remote-as 64500 + address-family ipv4 unicast + label vpn export 500 + rd vpn export 444:2 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_topo1/r5/isisd.conf b/tests/topotests/bgp_nhg_topo1/r5/isisd.conf new file mode 100644 index 000000000000..06d01c18316c --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r5/isisd.conf @@ -0,0 +1,26 @@ +hostname r5 +interface lo + ip router isis 1 + isis passive +! +interface r5-eth1 + ip router isis 1 + isis network point-to-point +! +interface r5-eth2 + ip router isis 1 + isis network point-to-point +! +interface r5-eth3 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0005.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.5/32 index 5 +! diff --git a/tests/topotests/bgp_nhg_topo1/r5/zebra.conf b/tests/topotests/bgp_nhg_topo1/r5/zebra.conf new file mode 100644 index 000000000000..6f326561e733 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r5/zebra.conf @@ -0,0 +1,19 @@ +log stdout +mpls label dynamic-block 5000 5999 +interface lo + ip address 192.0.2.5/32 +! +interface r5-eth0 + ip address 172.31.12.5/24 +! +interface r5-eth1 + ip address 172.31.4.5/24 + mpls enable +! +interface r5-eth2 + ip address 172.31.7.5/24 + mpls enable +! +interface r5-eth3 + ip address 172.31.21.5/24 +! diff --git a/tests/topotests/bgp_nhg_topo1/r6/bgpd.conf b/tests/topotests/bgp_nhg_topo1/r6/bgpd.conf new file mode 100644 index 000000000000..9fd8e4f6ff12 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r6/bgpd.conf @@ -0,0 +1,32 @@ +router bgp 64500 + bgp router-id 192.0.2.6 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor rrserver peer-group + neighbor rrserver remote-as 64500 + neighbor rrserver update-source lo + neighbor 192.0.2.3 peer-group rrserver + address-family ipv4 unicast + network 192.0.2.9/32 + neighbor rrserver activate + neighbor rrserver next-hop-self + exit-address-family + address-family ipv4 vpn + neighbor rrserver activate + neighbor rrserver next-hop-self + exit-address-family +! +router bgp 64500 vrf vrf1 + bgp router-id 192.0.2.6 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 172.31.22.9 remote-as 64500 + address-family ipv4 unicast + label vpn export allocation-mode per-nexthop + label vpn export 600 + rd vpn export 444:3 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_topo1/r6/isisd.conf b/tests/topotests/bgp_nhg_topo1/r6/isisd.conf new file mode 100644 index 000000000000..4f5b2b2c91dd --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r6/isisd.conf @@ -0,0 +1,23 @@ +hostname r6 +interface lo + ip router isis 1 + isis passive + isis metric 5 +! +interface r6-eth1 + ip router isis 1 + isis network point-to-point +! +interface r6-eth2 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0006.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.6/32 index 6 +! diff --git a/tests/topotests/bgp_nhg_topo1/r6/zebra.conf b/tests/topotests/bgp_nhg_topo1/r6/zebra.conf new file mode 100644 index 000000000000..cda62d7e87a0 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r6/zebra.conf @@ -0,0 +1,20 @@ +log stdout +mpls label dynamic-block 6000 6999 +interface lo + ip address 192.0.2.6/32 +! +interface r6-eth0 + ip address 172.31.13.6/24 +! +interface r6-eth1 + ip address 172.31.5.6/24 + mpls enable +! +interface r6-eth2 + ip address 172.31.6.6/24 + mpls enable +! +interface r6-eth3 + ip address 172.31.22.6/24 +! + diff --git a/tests/topotests/bgp_nhg_topo1/r7/isisd.conf b/tests/topotests/bgp_nhg_topo1/r7/isisd.conf new file mode 100644 index 000000000000..c9bddd538cdc --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r7/isisd.conf @@ -0,0 +1,22 @@ +hostname r7 +interface lo + ip router isis 1 + isis passive +! +interface r7-eth0 + ip router isis 1 + isis network point-to-point +! +interface r7-eth1 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0007.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.7/32 index 7 +! diff --git a/tests/topotests/bgp_nhg_topo1/r7/zebra.conf b/tests/topotests/bgp_nhg_topo1/r7/zebra.conf new file mode 100644 index 000000000000..991d877e474d --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r7/zebra.conf @@ -0,0 +1,14 @@ +log stdout +interface lo + ip address 192.0.2.7/32 +! +interface r7-eth0 + ip address 172.31.8.7/24 + mpls enable + shutdown +! +interface r7-eth1 + ip address 172.31.9.7/24 + mpls enable +! + diff --git a/tests/topotests/bgp_nhg_topo1/r8/bgpd.conf b/tests/topotests/bgp_nhg_topo1/r8/bgpd.conf new file mode 100644 index 000000000000..abf1861b7b28 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r8/bgpd.conf @@ -0,0 +1,32 @@ +router bgp 64500 + bgp router-id 192.0.2.8 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor rrserver peer-group + neighbor rrserver remote-as 64500 + neighbor rrserver update-source lo + neighbor 192.0.2.3 peer-group rrserver + address-family ipv4 unicast + network 192.0.2.9/32 + neighbor rrserver activate + neighbor rrserver next-hop-self + exit-address-family + address-family ipv4 vpn + neighbor rrserver activate + neighbor rrserver next-hop-self + exit-address-family +! +router bgp 64500 vrf vrf1 + bgp router-id 192.0.2.8 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 172.31.23.9 remote-as 64500 + address-family ipv4 unicast + label vpn export allocation-mode per-nexthop + label vpn export 1800 + rd vpn export 444:4 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_topo1/r8/isisd.conf b/tests/topotests/bgp_nhg_topo1/r8/isisd.conf new file mode 100644 index 000000000000..7259e6aade38 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r8/isisd.conf @@ -0,0 +1,19 @@ +hostname r8 +interface lo + ip router isis 1 + isis passive + isis metric 10 +! +interface r8-eth0 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0008.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.8/32 index 8 +! diff --git a/tests/topotests/bgp_nhg_topo1/r8/zebra.conf b/tests/topotests/bgp_nhg_topo1/r8/zebra.conf new file mode 100644 index 000000000000..b94a6ba0b37a --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/r8/zebra.conf @@ -0,0 +1,16 @@ +log stdout +mpls label dynamic-block 6000 6999 +interface lo + ip address 192.0.2.8/32 +! +interface r8-eth0 + ip address 172.31.9.8/24 + mpls enable +! +interface r8-eth1 + ip address 172.31.14.8/24 +! +interface r8-eth2 + ip address 172.31.23.8/24 +! + diff --git a/tests/topotests/bgp_nhg_topo1/test_bgp_nhg_topo1_1.py b/tests/topotests/bgp_nhg_topo1/test_bgp_nhg_topo1_1.py new file mode 100644 index 000000000000..3c999a95f5c3 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/test_bgp_nhg_topo1_1.py @@ -0,0 +1,1449 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_nhg_topo1.py +# +# Copyright 2024 6WIND S.A. +# + +""" + test_bgp_nhg_topo1.py: Test the FRR BGP daemon with bgp nexthop groups + Check BGP nexthop groups with MPLSVPN and unicast paths. + + ++--------+ +---+----+ +---+----+ +--------+ +| | | | | + | | +| ce7 +----------+ r1 +----------+ r3 +----------+ r5 +----------------+ +| | | | | rr + +-----+ | +--+-+--+ +--+++--+ ++--------+ +++-+----+ +--------+\ / +--------+ | | | | + || | \/ | ce9 | | ce10 | + || | /\ |unicast| | vpn | ++--------+ || | +--------+/ \ +--------+ +---+-+-+ +---+-+-+ +| | || | | + +-----+ +----------------+ | +| ce8 +-----------+| +---------------+ r4 +----------+ r6 +------+ | | +| | | | | | | | | ++--------+ | +--------+ +--------+ | | + | | | + | +--------+ +--------+ | | + | | | | +--------+ | + +-----------------+ r7 +----------+ r8 +------------------+ + | | | | + +--------+ +--------+ +""" + +import os +import sys +import json +from functools import partial +import pytest +import functools + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.bgpcheck import bgp_check_path_selection_unicast, bgp_check_path_selection_vpn + +from lib.common_check import ( + ip_check_path_not_present, + ip_check_path_selection, + iproute2_check_path_not_present, + iproute2_check_path_selection, +) +from lib.common_config import step +from lib.nexthopgroup import route_check_nhg_id_is_protocol +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + +nhg_id_1 = 0 +nhg_id_2 = 0 + + +def build_topo(tgen): + "Build function" + + # Create 2 routers. + tgen.add_router("ce7") + tgen.add_router("ce8") + tgen.add_router("ce9") + tgen.add_router("ce10") + # Create 7 PE routers. + tgen.add_router("r1") + tgen.add_router("r3") + tgen.add_router("r4") + tgen.add_router("r5") + tgen.add_router("r6") + tgen.add_router("r7") + tgen.add_router("r8") + + # switch + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["ce7"]) + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["ce9"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["ce9"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s7") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s8") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s9") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s10") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s11") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s12") + switch.add_link(tgen.gears["r5"]) + switch.add_link(tgen.gears["ce10"]) + + switch = tgen.add_switch("s13") + switch.add_link(tgen.gears["r6"]) + switch.add_link(tgen.gears["ce10"]) + + switch = tgen.add_switch("s14") + switch.add_link(tgen.gears["ce8"]) + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s15") + switch.add_link(tgen.gears["r7"]) + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s16") + switch.add_link(tgen.gears["r7"]) + switch.add_link(tgen.gears["r8"]) + + switch = tgen.add_switch("s17") + switch.add_link(tgen.gears["r8"]) + switch.add_link(tgen.gears["ce9"]) + + switch = tgen.add_switch("s18") + switch.add_link(tgen.gears["r8"]) + switch.add_link(tgen.gears["ce10"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list = [ + "ip link add loop2 type dummy", + "ip link set dev loop2 up", + ] + + for name in ("ce7", "ce9"): + for cmd in cmds_list: + logger.info("input: " + cmd) + output = tgen.net[name].cmd(cmd) + logger.info("output: " + output) + + cmds_list = [ + "ip link add loop2 type dummy", + "ip link set dev loop2 up", + ] + + output = tgen.net["r1"].cmd("ip link add vrf1 type vrf table 101") + output = tgen.net["r1"].cmd("ip link set dev vrf1 up") + output = tgen.net["r1"].cmd("ip link set dev r1-eth0 master vrf1") + output = tgen.net["r1"].cmd("ip link add vrf2 type vrf table 102") + output = tgen.net["r1"].cmd("ip link set dev vrf2 up") + output = tgen.net["r1"].cmd("ip link set dev r1-eth3 master vrf2") + output = tgen.net["r5"].cmd("ip link add vrf1 type vrf table 101") + output = tgen.net["r5"].cmd("ip link set dev vrf1 up") + output = tgen.net["r5"].cmd("ip link set dev r5-eth3 master vrf1") + output = tgen.net["r6"].cmd("ip link add vrf1 type vrf table 101") + output = tgen.net["r6"].cmd("ip link set dev vrf1 up") + output = tgen.net["r6"].cmd("ip link set dev r6-eth3 master vrf1") + output = tgen.net["r8"].cmd("ip link add vrf1 type vrf table 101") + output = tgen.net["r8"].cmd("ip link set dev vrf1 up") + output = tgen.net["r8"].cmd("ip link set dev r8-eth2 master vrf1") + + cmds_list = [ + "modprobe mpls_router", + "echo 100000 > /proc/sys/net/mpls/platform_labels", + ] + + for name in ("r1", "r3", "r4", "r5", "r6", "r7", "r8"): + for cmd in cmds_list: + logger.info("input: " + cmd) + output = tgen.net[name].cmd(cmd) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + _populate_iface() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + if rname in ("r1", "r3", "r4", "r5", "r6", "r7", "r8"): + router.load_config( + TopoRouter.RD_ISIS, os.path.join(CWD, "{}/isisd.conf".format(rname)) + ) + if rname in ("r1", "r3", "r5", "r6", "r8", "ce7", "ce8", "ce9", "ce10"): + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + if rname in ("r1", "r3", "r5", "r6", "r8", "ce7", "ce8", "ce9", "ce10"): + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def test_bgp_ipv4_route_presence(): + """ + Assert that the 192.0.2.9/32 prefix is present in unicast and vpn RIB + Check the presence of routes with r6 as nexthop for 192.0.2.9/32 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info( + "Check that 192.0.2.9/32 unicast entry has 1 entry with 192.0.2.6 nexthop" + ) + expected = { + "paths": [ + { + "origin": "IGP", + "metric": 0, + "valid": True, + "bestpath": { + "overall": True, + }, + "originatorId": "192.0.2.6", + "nexthops": [{"ip": "192.0.2.6", "metric": 25}], + "peer": { + "peerId": "192.0.2.3", + }, + }, + ] + } + test_func = functools.partial( + bgp_check_path_selection_unicast, tgen.gears["r1"], expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.9/32 unicast entry has one next-hop to 192.0.2.6" + + logger.info( + "Check that 192.0.2.9/32 mpls vpn entry has 1 selected entry with 192.0.2.6 nexthop" + ) + expected = { + "paths": [ + { + "valid": True, + "bestpath": { + "overall": True, + }, + "origin": "IGP", + "metric": 0, + "originatorId": "192.0.2.6", + "remoteLabel": 6000, + "nexthops": [{"ip": "192.0.2.6", "metric": 25}], + }, + { + "valid": True, + "origin": "IGP", + "metric": 0, + "originatorId": "192.0.2.5", + "remoteLabel": 500, + "nexthops": [{"ip": "192.0.2.5", "metric": 30}], + }, + ] + } + test_func = functools.partial( + bgp_check_path_selection_vpn, tgen.gears["r1"], "192.0.2.9/32", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.9/32 has one next-hop to 192.0.2.6" + # debug + tgen.gears["r1"].vtysh_cmd(f"show bgp nexthop-group detail") + + +def test_bgp_vrf_ipv4_route_presence(): + """ + Assert that the 192.0.2.7/32 prefix is present in two VRFs + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Check that 192.0.2.7/32 entry has 1 selected entry with 172.31.10.7 nexthop in vrf1" + ) + expected = { + "paths": [ + { + "valid": True, + "bestpath": { + "overall": True, + }, + "origin": "IGP", + "metric": 0, + "nexthops": [{"ip": "172.31.10.7", "metric": 0, "used": True}], + }, + ] + } + test_func = functools.partial( + bgp_check_path_selection_vpn, + tgen.gears["r1"], + "192.0.2.7/32", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.7/32 has one next-hop to 172.31.10.7 in vrf1" + + step( + "Check that 192.0.2.7/32 entry has 1 selected entry with 172.31.11.8 nexthop in vrf2" + ) + expected = { + "paths": [ + { + "valid": True, + "bestpath": { + "overall": True, + }, + "origin": "IGP", + "metric": 0, + "nexthops": [{"ip": "172.31.11.8", "metric": 0, "used": True}], + }, + ] + } + test_func = functools.partial( + bgp_check_path_selection_vpn, + tgen.gears["r1"], + "192.0.2.7/32", + expected, + vrf_name="vrf2", + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.7/32 has one next-hop to 172.31.11.8 in vrf2" + # debug + tgen.gears["r1"].vtysh_cmd(f"show bgp nexthop-group detail") + + +def test_bgp_vrf_ipv4_route_uses_vrf_nexthop_group(): + """ + Check that the installed 192.0.2.7/32 route uses two distinct BGP NHG + Which respectively uses the vrf1 and vrf2 nexthop. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Check that 192.0.2.7/32 has 1 path in vrf1") + expected = { + "192.0.2.7/32": [ + { + "prefix": "192.0.2.7/32", + "protocol": "bgp", + "vrfName": "vrf1", + "metric": 0, + "table": 101, + "nexthops": [ + { + "ip": "172.31.10.7", + "interfaceName": "r1-eth0", + "active": True, + }, + ], + } + ] + } + + test_func = functools.partial( + ip_check_path_selection, + tgen.gears["r1"], + "192.0.2.7/32", + expected, + vrf_name="vrf1", + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to check that 192.0.2.7/32 has 1 path in vrf1." + + local_nhg_id_1 = route_check_nhg_id_is_protocol( + "192.0.2.7/32", "r1", vrf_name="vrf1" + ) + + step("Check that 192.0.2.7/32 has 1 path in vrf1 in Linux") + expected = [ + { + "dst": "192.0.2.7", + "gateway": "172.31.10.7", + "dev": "r1-eth0", + "protocol": "bgp", + "metric": 20, + } + ] + + test_func = functools.partial( + iproute2_check_path_selection, + tgen.routers()["r1"], + "192.0.2.7/32", + expected, + vrf_name="vrf1", + nhg_id=local_nhg_id_1, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that Linux has 192.0.2.7/32 route in vrf1 with BGP ID." + + step("Check that 192.0.2.7/32 has 1 path in vrf2") + expected = { + "192.0.2.7/32": [ + { + "prefix": "192.0.2.7/32", + "protocol": "bgp", + "vrfName": "vrf2", + "metric": 0, + "table": 102, + "nexthops": [ + { + "ip": "172.31.11.8", + "interfaceName": "r1-eth3", + "active": True, + }, + ], + } + ] + } + + test_func = functools.partial( + ip_check_path_selection, + tgen.gears["r1"], + "192.0.2.7/32", + expected, + vrf_name="vrf2", + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to check that 192.0.2.7/32 has 1 path in vrf2." + + local_nhg_id_2 = route_check_nhg_id_is_protocol( + "192.0.2.7/32", "r1", vrf_name="vrf2" + ) + + step("Check that 192.0.2.7/32 has 1 path in vrf2 in Linux") + expected = [ + { + "dst": "192.0.2.7", + "gateway": "172.31.11.8", + "dev": "r1-eth3", + "protocol": "bgp", + "metric": 20, + } + ] + + test_func = functools.partial( + iproute2_check_path_selection, + tgen.routers()["r1"], + "192.0.2.7/32", + expected, + vrf_name="vrf2", + nhg_id=local_nhg_id_2, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that Linux has 192.0.2.7/32 route in vrf2 with BGP ID." + + step("Check that both routes do not share the same NHG") + + assert local_nhg_id_1 != local_nhg_id_2, ( + "The same NHG %d is used for both vrfs" % local_nhg_id_1 + ) + + +def test_bgp_ipv4_route_uses_nexthop_group(): + """ + Check that the installed route uses a BGP NHG + Check that the MPLS VPN route uses a different NHG + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + step("Check that 192.0.2.9/32 unicast entry has 1 BGP NHG") + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1", vrf_name="vrf1") + assert nhg_id_1 != nhg_id_2, ( + "The same NHG %d is used for both MPLS and unicast routes" % nhg_id_1 + ) + + +def test_bgp_ipv4_route_presence_after_igp_change(): + """ + The IGP is modified on r6 so that r5 will be selected + Check that routes to r5 are best. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Changing IGP metric on r6 from 5 to 40") + tgen.gears["r6"].vtysh_cmd( + """ + configure terminal\n + interface lo + isis metric 40\n + """, + isjson=False, + ) + + step("Check that 192.0.2.9/32 unicast entry has 1 entry with 192.0.2.5 nexthop") + expected = { + "paths": [ + { + "origin": "IGP", + "metric": 0, + "valid": True, + "bestpath": { + "overall": True, + }, + "originatorId": "192.0.2.5", + "nexthops": [{"ip": "192.0.2.5", "metric": 30}], + "peer": { + "peerId": "192.0.2.3", + }, + }, + ] + } + test_func = functools.partial( + bgp_check_path_selection_unicast, tgen.gears["r1"], expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.9/32 unicast entry has one next-hop to 192.0.2.5" + + step( + "Check that 192.0.2.9/32 mpls vpn entry has 1 selected entry with 192.0.2.5 nexthop" + ) + expected = { + "paths": [ + { + "valid": True, + "origin": "IGP", + "metric": 0, + "originatorId": "192.0.2.6", + "remoteLabel": 6000, + "nexthops": [{"ip": "192.0.2.6", "metric": 60}], + }, + { + "valid": True, + "bestpath": { + "overall": True, + }, + "origin": "IGP", + "metric": 0, + "originatorId": "192.0.2.5", + "remoteLabel": 500, + "nexthops": [{"ip": "192.0.2.5", "metric": 30}], + }, + ] + } + test_func = functools.partial( + bgp_check_path_selection_vpn, tgen.gears["r1"], "192.0.2.9/32", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.9/32 has one next-hop to 192.0.2.5" + + # debug + tgen.gears["r1"].vtysh_cmd(f"show bgp nexthop-group detail") + + +def test_bgp_ipv4_new_route_uses_nexthop_group(): + """ + Check that the installed route uses a BGP NHG + Check that the MPLS VPN route uses a different NHG + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + step("Check that 192.0.2.9/32 unicast entry has 1 BGP NHG") + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1", vrf_name="vrf1") + assert nhg_id_1 != nhg_id_2, ( + "The same NHG %d is used for both MPLS and unicast routes" % nhg_id_1 + ) + + +def test_bgp_ipv4_unconfigure_r6_network(): + """ + Only r5 will advertise the prefixes + Check that a change in the IGP is automatically modified + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + tgen.gears["r6"].vtysh_cmd( + """ + conf t\n + router bgp 64500 vrf vrf1\n + no neighbor 172.31.22.9 remote-as 64500\n + """, + isjson=False, + ) + tgen.gears["r6"].vtysh_cmd( + """ + conf t\n + router bgp 64500 vrf vrf1\n + address-family ipv4 unicast\n + no network 192.0.2.9/32\n + """, + isjson=False, + ) + + +def test_isis_ipv4_unshutdown_r4_eth0(): + """ + Unconfigure r4 to un-shutdown the r4-eth0 + Check that the 192.0.2.5/32 route is now multi path in the IGP + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + tgen.gears["r4"].vtysh_cmd( + """ + configure terminal\n + interface r4-eth0\n + no shutdown\n + """, + isjson=False, + ) + + step("Check that 192.0.2.5/32 has 2 paths now") + expected = { + "192.0.2.5/32": [ + { + "prefix": "192.0.2.5/32", + "protocol": "isis", + "metric": 30, + "table": 254, + "nexthops": [ + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16005, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16005, + ], + }, + ], + } + ] + } + + test_func = functools.partial( + ip_check_path_selection, tgen.gears["r1"], "192.0.2.5/32", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to check that 192.0.2.5/32 has 2 paths now" + + +def test_bgp_ipv4_convergence_igp(): + """ + Check that the BGP route to 192.0.2.9/32 route is now multi path + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Check that 192.0.2.9/32 unicast entry has 2 paths now") + expected = { + "192.0.2.9/32": [ + { + "prefix": "192.0.2.9/32", + "protocol": "bgp", + "metric": 0, + "table": 254, + "nexthopGroupId": nhg_id_1, + "installedNexthopGroupId": nhg_id_1, + "nexthops": [ + { + "ip": "192.0.2.5", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16005, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16005, + ], + }, + ], + } + ] + } + + test_func = functools.partial( + ip_check_path_selection, tgen.gears["r1"], "192.0.2.9/32", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to check that 192.0.2.9/32 has 2 paths now" + + local_nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + step("Check that 192.0.2.9/32 has 2 paths in Linux") + expected = [ + { + "dst": "192.0.2.9", + "protocol": "bgp", + "metric": 20, + "nexthops": [ + { + "encap": "mpls", + "dst": "16005", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16005", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ], + } + ] + + test_func = functools.partial( + iproute2_check_path_selection, + tgen.routers()["r1"], + "192.0.2.9/32", + expected, + nhg_id=local_nhg_id_1, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.9/32 has 2 paths now in Linux with BGP ID" + + step("Check that 192.0.2.9/32 mpls vpn entry has 2 paths now") + expected = { + "192.0.2.9/32": [ + { + "prefix": "192.0.2.9/32", + "protocol": "bgp", + "metric": 0, + "table": 101, + "nexthops": [ + { + "ip": "192.0.2.5", + "active": True, + "recursive": True, + "labels": [500], + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [16005, 500], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [16005, 500], + }, + ], + } + ] + } + + test_func = functools.partial( + ip_check_path_selection, + tgen.gears["r1"], + "192.0.2.9/32", + expected, + vrf_name="vrf1", + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.9/32 mpls vpn entry has 2 paths now" + + local_nhg_id_1 = route_check_nhg_id_is_protocol( + "192.0.2.9/32", "r1", vrf_name="vrf1" + ) + + step("Check that 192.0.2.9/32 mpls vpn entry has 2 paths now in Linux") + expected = [ + { + "dst": "192.0.2.9", + "protocol": "bgp", + "metric": 20, + "nexthops": [ + { + "encap": "mpls", + "dst": "16005/500", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16005/500", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ], + } + ] + + test_func = functools.partial( + iproute2_check_path_selection, + tgen.routers()["r1"], + "192.0.2.9/32", + expected, + vrf_name="vrf1", + nhg_id=local_nhg_id_1, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.9/32 mpls vpn entry has 2 paths now in Linux with BGP ID" + + # debug + tgen.gears["r1"].vtysh_cmd(f"show bgp nexthop-group detail") + + +def test_bgp_ipv4_convergence_igp_label_changed(): + """ + Change the r5 label value + Check that the BGP route to 192.0.2.9/32 route uses the new label value + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("On r5, change the index IS-IS SID to 55") + tgen.gears["r5"].vtysh_cmd( + """ + configure terminal\n + router isis 1\n + segment-routing prefix 192.0.2.5/32 index 55\n + """, + isjson=False, + ) + + step("Check that 192.0.2.9/32 unicast entry uses the IGP label 16055") + expected = { + "192.0.2.9/32": [ + { + "prefix": "192.0.2.9/32", + "protocol": "bgp", + "metric": 0, + "table": 254, + "nexthops": [ + { + "ip": "192.0.2.5", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16055, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16055, + ], + }, + ], + } + ] + } + + test_func = functools.partial( + ip_check_path_selection, tgen.gears["r1"], "192.0.2.9/32", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to check that 192.0.2.9/32 uses the IGP label 16055" + + local_nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + step("Check that 192.0.2.9/32 has 2 paths in Linux") + expected = [ + { + "dst": "192.0.2.9", + "protocol": "bgp", + "metric": 20, + "nexthops": [ + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ], + } + ] + + test_func = functools.partial( + iproute2_check_path_selection, + tgen.routers()["r1"], + "192.0.2.9/32", + expected, + nhg_id=local_nhg_id_1, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.9/32 has 2 paths now in Linux with BGP ID" + + step("Check that 192.0.2.9/32 mpls vpn entry uses the IGP label 16055") + expected = { + "192.0.2.9/32": [ + { + "prefix": "192.0.2.9/32", + "protocol": "bgp", + "metric": 0, + "table": 101, + "nexthops": [ + { + "ip": "192.0.2.5", + "active": True, + "recursive": True, + "labels": [500], + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [16055, 500], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [16055, 500], + }, + ], + } + ] + } + + test_func = functools.partial( + ip_check_path_selection, + tgen.gears["r1"], + "192.0.2.9/32", + expected, + vrf_name="vrf1", + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.9/32 mpls vpn entry uses the IGP label 16055" + + local_nhg_id_1 = route_check_nhg_id_is_protocol( + "192.0.2.9/32", "r1", vrf_name="vrf1" + ) + + step("Check that 192.0.2.9/32 mpls vpn entry uses the IGP label 16055 in Linux") + expected = [ + { + "dst": "192.0.2.9", + "protocol": "bgp", + "metric": 20, + "nexthops": [ + { + "encap": "mpls", + "dst": "16055/500", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16055/500", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ], + } + ] + + test_func = functools.partial( + iproute2_check_path_selection, + tgen.routers()["r1"], + "192.0.2.9/32", + expected, + vrf_name="vrf1", + nhg_id=local_nhg_id_1, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.9/32 mpls vpn entry uses the IGP label 16055 in Linux with BGP ID" + + # debug + tgen.gears["r1"].vtysh_cmd(f"show bgp nexthop-group detail") + + +def test_bgp_ipv4_r5_router_removed(): + """ + Remove the R5 router from the IGP + Remove the 192.0.2.9/32 network address on R6 + Check that the BGP route to 192.0.2.9/32 route is removed. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("On r5, remove the lo interface from the IGP") + tgen.gears["r5"].vtysh_cmd( + """ + configure terminal\n + interface lo\n + no ip router isis 1\n + """, + isjson=False, + ) + + step("On r6, remove the 192.0.2.9/32 network") + tgen.gears["r6"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + address-family ipv4 unicast\n + no network 192.0.2.9/32\n + """, + isjson=False, + ) + + step("Check that 192.0.2.9/32 is removed from the 'show ip route' table") + test_func = functools.partial( + ip_check_path_not_present, tgen.gears["r1"], "192.0.2.9/32" + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to check that 192.0.2.9/32 is not present." + + step("Check that 192.0.2.9/32 is removed from the 'iproute2' command on Linux") + test_func = functools.partial( + iproute2_check_path_not_present, tgen.gears["r1"], "192.0.2.9" + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to check that 192.0.2.9/32 is not present on Linux." + + # debug + tgen.gears["r1"].vtysh_cmd(f"show bgp nexthop-group detail") + + +def test_bgp_ipv4_r5_router_restored(): + """ + Restore the R5 router in the IGP + Restore the 192.0.2.9/32 network address on R6 + Check that the BGP route to 192.0.2.9/32 route is readded. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("On r5, restore the lo interface in the IGP") + tgen.gears["r5"].vtysh_cmd( + """ + configure terminal\n + interface lo\n + ip router isis 1\n + """, + isjson=False, + ) + + step("On r6, restore the 192.0.2.9/32 network") + tgen.gears["r6"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + address-family ipv4 unicast\n + network 192.0.2.9/32\n + """, + isjson=False, + ) + + step("Check that 192.0.2.9/32 unicast entry uses the IGP label 16055") + expected = { + "192.0.2.9/32": [ + { + "prefix": "192.0.2.9/32", + "protocol": "bgp", + "metric": 0, + "table": 254, + "nexthops": [ + { + "ip": "192.0.2.5", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16055, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16055, + ], + }, + ], + } + ] + } + + test_func = functools.partial( + ip_check_path_selection, tgen.gears["r1"], "192.0.2.9/32", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to check that 192.0.2.9/32 uses the IGP label 16055" + + local_nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + step("Check that 192.0.2.9/32 has 2 paths in Linux") + expected = [ + { + "dst": "192.0.2.9", + "protocol": "bgp", + "metric": 20, + "nexthops": [ + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ], + } + ] + + test_func = functools.partial( + iproute2_check_path_selection, + tgen.routers()["r1"], + "192.0.2.9/32", + expected, + nhg_id=local_nhg_id_1, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to check that 192.0.2.9/32 has 2 paths now in Linux with BGP ID" + + # debug + tgen.gears["r1"].vtysh_cmd(f"show bgp nexthop-group detail") + + +def check_ipv4_prefix_with_multiple_nexthops( + prefix, r5_path=True, r6_path=True, r8_path=False +): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + f"Check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path}" + ) + + r5_nh = [ + { + "ip": "192.0.2.5", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16055, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16055, + ], + }, + ] + + r6_nh = [ + { + "ip": "192.0.2.6", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16006, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16006, + ], + }, + ] + + r8_nh = [ + { + "ip": "192.0.2.8", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.8.7", + "interfaceName": "r1-eth4", + "active": True, + "labels": [ + 16008, + ], + }, + ] + + expected = { + prefix: [ + { + "prefix": prefix, + "protocol": "bgp", + "metric": 0, + "table": 254, + "nexthops": [], + } + ] + } + if r5_path: + for nh in r5_nh: + expected[prefix][0]["nexthops"].append(nh) + if r6_path: + for nh in r6_nh: + expected[prefix][0]["nexthops"].append(nh) + if r8_path: + for nh in r8_nh: + expected[prefix][0]["nexthops"].append(nh) + + test_func = functools.partial( + ip_check_path_selection, + tgen.gears["r1"], + prefix, + expected, + ignore_duplicate_nh=True, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), f"Failed to check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path}" + + +def check_ipv4_prefix_with_multiple_nexthops_linux( + prefix, nhg_id, r5_path=True, r6_path=True, r8_path=False +): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + f"Check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path} with NHID {nhg_id} on Linux" + ) + + r5_nh = [ + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ] + + r6_nh = [ + { + "encap": "mpls", + "dst": "16006", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16006", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ] + + r8_nh = [ + { + "encap": "mpls", + "dst": "16008", + "gateway": "172.31.8.7", + "dev": "r1-eth4", + } + ] + + expected_r8_nh_only = [ + { + "dst": prefix, + "protocol": "bgp", + "metric": 20, + "encap": "mpls", + "dst": "16008", + "gateway": "172.31.8.7", + "dev": "r1-eth4", + } + ] + expected = [ + { + "dst": prefix, + "protocol": "bgp", + "metric": 20, + "nexthops": [], + } + ] + + # only one path + if r8_path and not r5_path and not r6_path: + expected = expected_r8_nh_only + else: + if r5_path: + for nh in r5_nh: + expected[0]["nexthops"].append(nh) + if r6_path: + for nh in r6_nh: + expected[0]["nexthops"].append(nh) + if r8_path: + for nh in r8_nh: + expected[0]["nexthops"].append(nh) + + test_func = functools.partial( + iproute2_check_path_selection, + tgen.routers()["r1"], + prefix, + expected, + nhg_id=nhg_id, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), f"Failed to check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path} on Linux with BGP ID" + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_nhg_topo1/test_bgp_nhg_topo1_2.py b/tests/topotests/bgp_nhg_topo1/test_bgp_nhg_topo1_2.py new file mode 100644 index 000000000000..ac10e6e88e77 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo1/test_bgp_nhg_topo1_2.py @@ -0,0 +1,1525 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_nhg_topo1.py +# +# Copyright 2024 6WIND S.A. +# + +""" + test_bgp_nhg_topo1.py: Test the FRR BGP daemon with bgp nexthop groups + Check BGP nexthop groups with ECMP paths. + + ++--------+ +---+----+ +---+----+ +--------+ +| | | | | + | | +| ce7 +----------+ r1 +----------+ r3 +----------+ r5 +----------------+ +| | | | | rr + +-----+ | +--+-+--+ +--+++--+ ++--------+ +++-+----+ +--------+\ / +--------+ | | | | + || | \/ | ce9 | | ce10 | + || | /\ |unicast| | vpn | ++--------+ || | +--------+/ \ +--------+ +---+-+-+ +---+-+-+ +| | || | | + +-----+ +----------------+ | +| ce8 +-----------+| +---------------+ r4 +----------+ r6 +------+ | | +| | | | | | | | | ++--------+ | +--------+ +--------+ | | + | | | + | +--------+ +--------+ | | + | | | | +--------+ | + +-----------------+ r7 +----------+ r8 +------------------+ + | | | | + +--------+ +--------+ +""" + +import os +import sys +import json +from functools import partial +import pytest +import functools + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.bgpcheck import bgp_check_path_selection_unicast, bgp_check_path_selection_vpn + +from lib.common_check import ( + ip_check_path_not_present, + ip_check_path_selection, + iproute2_check_path_not_present, + iproute2_check_path_selection, +) +from lib.common_config import step +from lib.nexthopgroup import route_check_nhg_id_is_protocol +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + +nhg_id_1 = 0 +nhg_id_2 = 0 + + +def build_topo(tgen): + "Build function" + + # Create 2 routers. + tgen.add_router("ce7") + tgen.add_router("ce8") + tgen.add_router("ce9") + tgen.add_router("ce10") + # Create 7 PE routers. + tgen.add_router("r1") + tgen.add_router("r3") + tgen.add_router("r4") + tgen.add_router("r5") + tgen.add_router("r6") + tgen.add_router("r7") + tgen.add_router("r8") + + # switch + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["ce7"]) + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["ce9"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["ce9"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s7") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s8") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s9") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s10") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s11") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s12") + switch.add_link(tgen.gears["r5"]) + switch.add_link(tgen.gears["ce10"]) + + switch = tgen.add_switch("s13") + switch.add_link(tgen.gears["r6"]) + switch.add_link(tgen.gears["ce10"]) + + switch = tgen.add_switch("s14") + switch.add_link(tgen.gears["ce8"]) + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s15") + switch.add_link(tgen.gears["r7"]) + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s16") + switch.add_link(tgen.gears["r7"]) + switch.add_link(tgen.gears["r8"]) + + switch = tgen.add_switch("s17") + switch.add_link(tgen.gears["r8"]) + switch.add_link(tgen.gears["ce9"]) + + switch = tgen.add_switch("s18") + switch.add_link(tgen.gears["r8"]) + switch.add_link(tgen.gears["ce10"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list = [ + "ip link add loop2 type dummy", + "ip link set dev loop2 up", + ] + + for name in ("ce7", "ce9"): + for cmd in cmds_list: + logger.info("input: " + cmd) + output = tgen.net[name].cmd(cmd) + logger.info("output: " + output) + + cmds_list = [ + "ip link add loop2 type dummy", + "ip link set dev loop2 up", + ] + + output = tgen.net["r1"].cmd("ip link add vrf1 type vrf table 101") + output = tgen.net["r1"].cmd("ip link set dev vrf1 up") + output = tgen.net["r1"].cmd("ip link set dev r1-eth0 master vrf1") + output = tgen.net["r1"].cmd("ip link add vrf2 type vrf table 102") + output = tgen.net["r1"].cmd("ip link set dev vrf2 up") + output = tgen.net["r1"].cmd("ip link set dev r1-eth3 master vrf2") + output = tgen.net["r5"].cmd("ip link add vrf1 type vrf table 101") + output = tgen.net["r5"].cmd("ip link set dev vrf1 up") + output = tgen.net["r5"].cmd("ip link set dev r5-eth3 master vrf1") + output = tgen.net["r6"].cmd("ip link add vrf1 type vrf table 101") + output = tgen.net["r6"].cmd("ip link set dev vrf1 up") + output = tgen.net["r6"].cmd("ip link set dev r6-eth3 master vrf1") + output = tgen.net["r8"].cmd("ip link add vrf1 type vrf table 101") + output = tgen.net["r8"].cmd("ip link set dev vrf1 up") + output = tgen.net["r8"].cmd("ip link set dev r8-eth2 master vrf1") + + cmds_list = [ + "modprobe mpls_router", + "echo 100000 > /proc/sys/net/mpls/platform_labels", + ] + + for name in ("r1", "r3", "r4", "r5", "r6", "r7", "r8"): + for cmd in cmds_list: + logger.info("input: " + cmd) + output = tgen.net[name].cmd(cmd) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + _populate_iface() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + if rname in ("r1", "r3", "r4", "r5", "r6", "r7", "r8"): + router.load_config( + TopoRouter.RD_ISIS, os.path.join(CWD, "{}/isisd.conf".format(rname)) + ) + if rname in ("r1", "r3", "r5", "r6", "r8", "ce7", "ce8", "ce9", "ce10"): + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + if rname in ("r1", "r3", "r5", "r6", "r8", "ce7", "ce8", "ce9", "ce10"): + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def check_ipv4_prefix_with_multiple_nexthops( + prefix, r5_path=True, r6_path=True, r8_path=False +): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + f"Check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path}" + ) + + r5_nh = [ + { + "ip": "192.0.2.5", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16055, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16055, + ], + }, + ] + + r6_nh = [ + { + "ip": "192.0.2.6", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16006, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16006, + ], + }, + ] + + r8_nh = [ + { + "ip": "192.0.2.8", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.8.7", + "interfaceName": "r1-eth4", + "active": True, + "labels": [ + 16008, + ], + }, + ] + + expected = { + prefix: [ + { + "prefix": prefix, + "protocol": "bgp", + "metric": 0, + "table": 254, + "nexthops": [], + } + ] + } + if r5_path: + for nh in r5_nh: + expected[prefix][0]["nexthops"].append(nh) + if r6_path: + for nh in r6_nh: + expected[prefix][0]["nexthops"].append(nh) + if r8_path: + for nh in r8_nh: + expected[prefix][0]["nexthops"].append(nh) + + test_func = functools.partial( + ip_check_path_selection, + tgen.gears["r1"], + prefix, + expected, + ignore_duplicate_nh=True, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), f"Failed to check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path}" + + +def check_ipv4_prefix_with_multiple_nexthops_linux( + prefix, nhg_id, r5_path=True, r6_path=True, r8_path=False +): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + f"Check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path} with NHID {nhg_id} on Linux" + ) + + r5_nh = [ + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ] + + r6_nh = [ + { + "encap": "mpls", + "dst": "16006", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16006", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ] + + r8_nh = [ + { + "encap": "mpls", + "dst": "16008", + "gateway": "172.31.8.7", + "dev": "r1-eth4", + } + ] + + expected_r8_nh_only = [ + { + "dst": prefix, + "protocol": "bgp", + "metric": 20, + "encap": "mpls", + "dst": "16008", + "gateway": "172.31.8.7", + "dev": "r1-eth4", + } + ] + expected = [ + { + "dst": prefix, + "protocol": "bgp", + "metric": 20, + "nexthops": [], + } + ] + + # only one path + if r8_path and not r5_path and not r6_path: + expected = expected_r8_nh_only + else: + if r5_path: + for nh in r5_nh: + expected[0]["nexthops"].append(nh) + if r6_path: + for nh in r6_nh: + expected[0]["nexthops"].append(nh) + if r8_path: + for nh in r8_nh: + expected[0]["nexthops"].append(nh) + + test_func = functools.partial( + iproute2_check_path_selection, + tgen.routers()["r1"], + prefix, + expected, + nhg_id=nhg_id, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), f"Failed to check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path} on Linux with BGP ID" + + +def test_bgp_ipv4_update_config(): + """ + The config is modified to reflect initial state + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Init. On r6, update isis metric to 40") + + tgen.gears["r6"].vtysh_cmd( + """ + configure terminal\n + interface lo\n + isis metric 40\n + exit\n + router bgp 64500 vrf vrf1\n + no neighbor 172.31.22.9 remote-as 64500\n + """, + isjson=False, + ) + + step("Init. On r4, unshutdown r4-eth0") + + tgen.gears["r4"].vtysh_cmd( + """ + configure terminal\n + interface r4-eth0\n + no shutdown\n + """, + isjson=False, + ) + + step("Init. On r5, use prefix-sid 55 in ISIS") + + tgen.gears["r5"].vtysh_cmd( + """ + configure terminal\n + router isis 1\n + segment-routing prefix 192.0.2.5/32 index 55\n + """, + isjson=False, + ) + + +def test_bgp_ipv4_addpath_configured(): + """ + R6 lo metric is set to default + R1 addpath is configured + Change the r6 metric value + Check that the BGP route to 192.0.2.9/32 route uses BGP nexthops + Check that the BGP nexthop groups used are same in BGP and in ZEBRA + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("On r3, configure addpath") + tgen.gears["r3"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + address-family ipv4 unicast\n + neighbor rr addpath-tx-all-paths\n + """, + isjson=False, + ) + + step("On r6, change the IS-IS metric to default for lo interface") + tgen.gears["r6"].vtysh_cmd( + """ + configure terminal\n + interface lo\n + no isis metric\n + """, + isjson=False, + ) + + check_ipv4_prefix_with_multiple_nexthops("192.0.2.9/32") + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux("192.0.2.9", nhg_id=local_nhg_id) + + +def test_bgp_ipv4_three_ecmp_paths_configured(): + """ + R7 interface is unshutdown + Check that the BGP route to 192.0.2.9/32 route uses 3 BGP nexthops + Check that the 3 BGP nexthop groups are used. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r7"].vtysh_cmd( + """ + configure terminal\n + interface r7-eth0\n + no shutdown\n + """, + isjson=False, + ) + + step("Check that 192.0.2.9/32 unicast entry is installed with three endpoints") + check_ipv4_prefix_with_multiple_nexthops("192.0.2.9/32", r8_path=True) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=local_nhg_id, r8_path=True + ) + + step(f"Get 192.0.2.9/32 child nexthop-groups for ID {local_nhg_id}") + output = json.loads( + tgen.gears["r1"].vtysh_cmd(f"show bgp nexthop-group {local_nhg_id} json") + ) + assert ( + "childList" in output.keys() + ), f"ID {local_nhg_id}, BGP nexthop group with no child nexthop-group." + assert ( + "childListCount" in output.keys() and output["childListCount"] == 3 + ), f"ID {local_nhg_id}, expected 2 dependent nexthops." + + +def test_bgp_ipv4_one_additional_network_configured(): + """ + R5, R6, and R8 have a new network to declare: 192.0.2.20/32 + Check that 192.0.2.9/32 and 192.0.2.20/32 use the same NHG + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Check that 192.0.2.20/32 unicast entry is installed with three endpoints") + for rname in ("r5", "r6", "r8"): + tgen.gears[rname].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + address-family ipv4 unicast\n + network 192.0.2.20/32 + """, + isjson=False, + ) + check_ipv4_prefix_with_multiple_nexthops("192.0.2.20/32", r8_path=True) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=nhg_id_2, r8_path=True + ) + + step( + "Check that same NHG is used by both 192.0.2.9/32 and 192.0.2.20/32 unicast routes" + ) + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + assert nhg_id_1 == nhg_id_2, ( + "The same NHG %d is not used for both 192.0.2.9/32 and 192.0.2.20/32 unicast routes" + % nhg_id_1 + ) + + +def test_bgp_ipv4_additional_network_has_only_two_paths_configured(): + """ + On R6, we remove the update to 192.0.2.9/32 + Check that the same NHG is used by 192.0.2.9/32 unicast routes + Check that 192.0.2.9/32 and 192.0.2.20/32 do not use the same NHG + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Unconfigure 192.0.2.9/32 unicast entry on r6") + tgen.gears["r6"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + address-family ipv4 unicast\n + no network 192.0.2.9/32 + """, + isjson=False, + ) + + step("Check that 192.0.2.9/32 unicast entry is installed with two endpoints") + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.9/32", r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=nhg_id_1, r6_path=False, r8_path=True + ) + + check_ipv4_prefix_with_multiple_nexthops("192.0.2.20/32", r8_path=True) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + local_nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=local_nhg_id_2, r8_path=True + ) + + step("Check that the same NHG is used by 192.0.2.20/32 unicast routes") + assert ( + local_nhg_id_2 == nhg_id_2 + ), "The same NHG %d is not used by 192.0.2.20/32 unicast routes: %d" % ( + nhg_id_2, + local_nhg_id_2, + ) + + step( + "Check that different NHG is used by both 192.0.2.9/32 and 192.0.2.20/32 unicast routes" + ) + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + assert nhg_id_1 != nhg_id_2, ( + "The same NHG %d is used for both 192.0.2.9/32 and 192.0.2.20/32 unicast routes" + % nhg_id_1 + ) + + +def test_bgp_ipv4_additional_network_has_again_three_paths_configured(): + """ + On R6, we add back the update to 192.0.2.9/32 + Check that the same NHG is used by 192.0.2.20/32 unicast routes + Check that the same NHG is used by both 192.0.2.20/32 and 192.0.2.9/32 unicast routes + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Reconfigure 192.0.2.9/32 unicast entry on r6") + tgen.gears["r6"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + address-family ipv4 unicast\n + network 192.0.2.9/32 + """, + isjson=False, + ) + + step("Check that 192.0.2.20/32 unicast entry is installed with three endpoints") + check_ipv4_prefix_with_multiple_nexthops("192.0.2.9/32", r8_path=True) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=nhg_id_1, r8_path=True + ) + + check_ipv4_prefix_with_multiple_nexthops("192.0.2.20/32", r8_path=True) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + local_nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=local_nhg_id_2, r8_path=True + ) + + step("Check that the same NHG is used by 192.0.2.20/32 unicast routes") + assert ( + local_nhg_id_2 == nhg_id_2 + ), "The same NHG %d is not used by 192.0.2.20/32 unicast routes: %d" % ( + nhg_id_2, + local_nhg_id_2, + ) + + step( + "Check that same NHG is used by both 192.0.2.9/32 and 192.0.2.20/32 unicast routes" + ) + assert nhg_id_1 == nhg_id_2, ( + "The same NHG %d is not used for both 192.0.2.9/32 and 192.0.2.20/32 unicast routes" + % nhg_id_1 + ) + + +def test_bgp_ipv4_lower_preference_value_on_r5_and_r8_configured(): + """ + On R5, and R8, we add a route-map to lower local-preference + Check that only R6 is selected + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Reconfigure R5 and R8 to lower the preference value of advertised unicast networks" + ) + for rname in ("r5", "r8"): + tgen.gears[rname].vtysh_cmd( + """ + configure terminal\n + route-map rmap permit 1\n + set local-preference 50\n + """, + isjson=False, + ) + for prefix in ("192.0.2.9/32", "192.0.2.20/32"): + tgen.gears[rname].vtysh_cmd( + f""" + configure terminal\n + router bgp 64500\n + address-family ipv4 unicast\n + network {prefix} route-map rmap + """, + isjson=False, + ) + step("Check that 192.0.2.20/32 unicast entry is installed with one endpoints") + check_ipv4_prefix_with_multiple_nexthops("192.0.2.9/32", r5_path=False) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=nhg_id_1, r5_path=False + ) + + check_ipv4_prefix_with_multiple_nexthops("192.0.2.20/32", r5_path=False) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=nhg_id_2, r5_path=False + ) + + logger.info( + f"Get the nhg_id used for 192.0.2.9/32: {nhg_id_1}, and 192.0.2.20/32: {nhg_id_2}" + ) + + +def test_bgp_ipv4_increase_preference_value_on_r5_and_r8_configured(): + """ + On R5, and R8, we change the local-preference to a bigger value + Check that R5, and R8 are selected + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Reconfigure R5 and R8 to increase the preference value of advertised unicast networks" + ) + for rname in ("r5", "r8"): + tgen.gears[rname].vtysh_cmd( + """ + configure terminal\n + route-map rmap permit 1\n + set local-preference 220\n + """, + isjson=False, + ) + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.9/32", r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=nhg_id_1, r6_path=False, r8_path=True + ) + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.20/32", r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=nhg_id_2, r6_path=False, r8_path=True + ) + + step( + f"Get the nhg_id used for 192.0.2.9/32: {nhg_id_1}, and 192.0.2.20/32: {nhg_id_2}" + ) + + +def test_bgp_ipv4_simulate_r5_machine_going_down(): + """ + On R5, we shutdown the interface + Check that R8 is selected + Check that R5 failure did not change the NHG (EDGE implementation needed) + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Shutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + interface {ifname}\n + shutdown\n + """, + isjson=False, + ) + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.9/32", r5_path=False, r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + local_nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=local_nhg_id_1, r5_path=False, r6_path=False, r8_path=True + ) + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.20/32", r5_path=False, r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + local_nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=local_nhg_id_2, r5_path=False, r6_path=False, r8_path=True + ) + + step( + f"Get the nhg_id used for 192.0.2.9/32: {nhg_id_1}, and 192.0.2.20/32: {local_nhg_id_2}" + ) + step("Check that other NHG is used by 192.0.2.9/32 unicast routes") + assert local_nhg_id_1 == nhg_id_1, ( + "The same NHG %d is not used after R5 shutdown, EDGE implementation missing" + % nhg_id_1 + ) + + +def test_bgp_ipv4_simulate_r5_machine_going_up(): + """ + On R5, we unshutdown the interface + Check that R8 is selected + Check that the same NHG is used by both unicast routes + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Unshutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + interface {ifname}\n + no shutdown\n + """, + isjson=False, + ) + + logger.info("Check that routes from R5 are back again") + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.9/32", r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=nhg_id_1, r6_path=False, r8_path=True + ) + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.20/32", r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=nhg_id_2, r6_path=False, r8_path=True + ) + + logger.info( + f"Get the nhg_id used for 192.0.2.9/32: {nhg_id_1}, and 192.0.2.20/32: {nhg_id_2}" + ) + step( + "Check that the same NHG is used by both 192.0.2.9/32 and 192.0.2.20/32 unicast routes" + ) + assert nhg_id_1 == nhg_id_2, ( + "A different NHG %d is used after R5 unshutdown between 192.0.2.9 and 192.0.2.20" + % nhg_id_1 + ) + + +def test_bgp_ipv4_unpeering_with_r5(): + """ + On R5, we unconfigure R3 peering + Check that, on R1, routes from R5 are removed + """ + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("R5, unpeer with R3") + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + router bgp 64500\n + no neighbor 192.0.2.3 peer-group rrserver\n + """, + isjson=False, + ) + + logger.info("Check that routes from R5 are removed") + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.9/32", r5_path=False, r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + local_nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=local_nhg_id_1, r5_path=False, r6_path=False, r8_path=True + ) + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.20/32", r5_path=False, r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + local_nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=local_nhg_id_2, r5_path=False, r6_path=False, r8_path=True + ) + + +def test_bgp_ipv4_direct_peering_with_r5(): + """ + On R5, we configure a peering with R1 + On R1, we configure a peering with R5 + Check that routes from R5 are removed + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("R5, peer with R1") + tgen.gears["r5"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + neighbor rrserver bfd\n + neighbor rrserver bfd check-control-plane-failure + """, + isjson=False, + ) + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + router bgp 64500\n + neighbor 192.0.2.1 peer-group rrserver\n + """, + isjson=False, + ) + step("R1, peer with R5") + tgen.gears["r1"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + neighbor rrserver bfd\n + neighbor rrserver bfd check-control-plane-failure + """, + isjson=False, + ) + tgen.gears["r1"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + neighbor 192.0.2.5 peer-group rrserver\n + """, + isjson=False, + ) + + logger.info("Check that routes from R5 are readded") + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.9/32", r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=nhg_id_1, r6_path=False, r8_path=True + ) + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.20/32", r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=nhg_id_2, r6_path=False, r8_path=True + ) + + logger.info( + f"Get the nhg_id used for 192.0.2.9/32: {nhg_id_1}, and 192.0.2.20/32: {nhg_id_2}" + ) + step( + "Check that the same NHG is used by both 192.0.2.9/32 and 192.0.2.20/32 unicast routes" + ) + assert nhg_id_1 == nhg_id_2, ( + "A different NHG %d is used after R5 unshutdown between 192.0.2.9 and 192.0.2.20" + % nhg_id_1 + ) + + +def test_bgp_ipv4_simulate_r5_direct_peering_going_down(): + """ + On R5, we shutdown the interface + Check that R8 is selected + Check that R5 failure did not change the NHG (EDGE implementation needed) + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Shutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + interface {ifname}\n + shutdown\n + """, + isjson=False, + ) + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.9/32", r5_path=False, r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + local_nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=local_nhg_id_1, r5_path=False, r6_path=False, r8_path=True + ) + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.20/32", r5_path=False, r6_path=False, r8_path=True + ) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + local_nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=local_nhg_id_2, r5_path=False, r6_path=False, r8_path=True + ) + + logger.info( + f"Get the nhg_id used for 192.0.2.9/32: {local_nhg_id_1}, and 192.0.2.20/32: {local_nhg_id_2}" + ) + step("Check that previous NHG used by 192.0.2.9/32 unicast routes is same as now") + assert local_nhg_id_1 == nhg_id_1, ( + "The same NHG %d is not used after R5 shutdown, EDGE implementation missing" + % nhg_id_1 + ) + + +def test_bgp_ipv4_simulate_r5_direct_peering_up_again_with_three_paths(): + """ + On R5 and R8, we remove the route-map + On R5, we unshutdown the interface + Check that R8 and R5 are selected again + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Remove route-map from R5 and R8") + for rname in ("r5", "r8"): + for prefix in ("192.0.2.9/32", "192.0.2.20/32"): + tgen.gears[rname].vtysh_cmd( + f""" + configure terminal\n + router bgp 64500\n + address-family ipv4 unicast\n + network {prefix} + """, + isjson=False, + ) + + step("UnShutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + interface {ifname}\n + no shutdown\n + """, + isjson=False, + ) + check_ipv4_prefix_with_multiple_nexthops("192.0.2.9/32", r8_path=True) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=nhg_id_1, r8_path=True + ) + + check_ipv4_prefix_with_multiple_nexthops("192.0.2.20/32", r8_path=True) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=nhg_id_2, r8_path=True + ) + + logger.info( + f"Get the nhg_id used for 192.0.2.9/32: {nhg_id_1}, and 192.0.2.20/32: {nhg_id_2}" + ) + step( + "Check that the same NHG is used by both 192.0.2.9/32 and 192.0.2.20/32 unicast routes" + ) + assert nhg_id_1 == nhg_id_2, ( + "A different NHG %d is used after R5 unshutdown between 192.0.2.9 and 192.0.2.20" + % nhg_id_1 + ) + + +def test_bgp_ipv4_simulate_r5_direct_peering_going_down_two_path_remain(): + """ + On R5, we shutdown the interface + Check that R8 and R6 are selected + Check that R5 failure did not change the NHG (EDGE implementation needed) + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Shutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + interface {ifname}\n + shutdown\n + """, + isjson=False, + ) + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.9/32", r5_path=False, r6_path=True, r8_path=True + ) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + local_nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=local_nhg_id_1, r5_path=False, r6_path=True, r8_path=True + ) + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.20/32", r5_path=False, r6_path=True, r8_path=True + ) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + local_nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=local_nhg_id_2, r5_path=False, r6_path=True, r8_path=True + ) + + logger.info( + f"Get the nhg_id used for 192.0.2.9/32: {local_nhg_id_1}, and 192.0.2.20/32: {local_nhg_id_2}" + ) + step("Check that previous NHG used by 192.0.2.9/32 unicast routes is same as now") + assert local_nhg_id_1 == nhg_id_1, ( + "The same NHG %d is not used after R5 shutdown, EDGE implementation missing" + % nhg_id_1 + ) + + +def test_bgp_ipv4_simulate_r5_direct_peering_going_down_one_path_remain(): + """ + On R8, we shutdown the r8-eth0 interface + Check that R6 only is selected + Check that the NHG change did not change the NHG (EDGE implementation needed) + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Shutdown r8-eth0 interface") + tgen.gears["r8"].vtysh_cmd( + """ + configure terminal\n + interface r8-eth0\n + shutdown\n + """, + isjson=False, + ) + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.9/32", r5_path=False, r6_path=True, r8_path=False + ) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + local_nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=local_nhg_id_1, r5_path=False, r6_path=True, r8_path=False + ) + + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.20/32", r5_path=False, r6_path=True, r8_path=False + ) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + local_nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=local_nhg_id_2, r5_path=False, r6_path=True, r8_path=False + ) + + logger.info( + f"Get the nhg_id used for 192.0.2.9/32: {local_nhg_id_1}, and 192.0.2.20/32: {local_nhg_id_2}" + ) + step("Check that previous NHG used by 192.0.2.9/32 unicast routes is same as now") + assert local_nhg_id_1 == nhg_id_1, ( + "The same NHG %d is not used after R5 shutdown, EDGE implementation missing" + % nhg_id_1 + ) + step("UnShutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + interface {ifname}\n + no shutdown\n + """, + isjson=False, + ) + + +def test_bgp_ipv4_three_paths_again(): + """ + On R8, we remove the route-map + On R5, we unshutdown the interface + Check that R6, R8 and R5 are selected again + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("UnShutdown r8-eth0 interface") + tgen.gears["r8"].vtysh_cmd( + """ + configure terminal\n + interface r8-eth0\n + no shutdown\n + """, + isjson=False, + ) + + check_ipv4_prefix_with_multiple_nexthops("192.0.2.9/32", r8_path=True) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + local_nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=local_nhg_id_1, r8_path=True + ) + + check_ipv4_prefix_with_multiple_nexthops("192.0.2.20/32", r8_path=True) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + local_nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", nhg_id=local_nhg_id_2, r8_path=True + ) + + +def test_bgp_ipv4_r8_uses_nh_from_r5(): + """ + On R8, we use a route-map to change NH of 192.0.2.20 to RT5 + On R5, we unshutdown the interface + Check that R6, R8 and R5 are selected again + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + global nhg_id_1 + global nhg_id_2 + + step("Reconfigure R8 to advertise nexthops with R5 nexthop") + tgen.gears["r8"].vtysh_cmd( + """ + configure terminal\n + ip prefix-list plist1 seq 10 permit 192.0.2.20/32 + """, + isjson=False, + ) + tgen.gears["r8"].vtysh_cmd( + """ + configure terminal\n + ip prefix-list plist2 seq 10 permit 192.0.2.9/32 + """, + isjson=False, + ) + tgen.gears["r8"].vtysh_cmd( + """ + configure terminal\n + route-map rmap_nh permit 1\n + match ip address prefix-list plist1\n + set ip next-hop 192.0.2.5 + """, + isjson=False, + ) + tgen.gears["r8"].vtysh_cmd( + """ + configure terminal\n + route-map rmap_nh permit 2\n + match ip address prefix-list plist2 + """, + isjson=False, + ) + tgen.gears["r8"].vtysh_cmd( + f""" + configure terminal\n + router bgp 64500\n + address-family ipv4 unicast\n + neighbor rrserver route-map rmap_nh out\n + """, + isjson=False, + ) + + # R5, R6 and R8 are selected + check_ipv4_prefix_with_multiple_nexthops("192.0.2.9/32", r8_path=True) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=nhg_id_1, r8_path=True + ) + + # R5, R6 are selected + check_ipv4_prefix_with_multiple_nexthops("192.0.2.20/32") + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux("192.0.2.20", nhg_id=nhg_id_2) + + logger.info( + f"Get the nhg_id used for 192.0.2.9/32: {nhg_id_1}, and 192.0.2.20/32: {nhg_id_2}" + ) + + +def test_bgp_ipv4_simulate_r5_direct_peering_going_down_and_r8_announce_r5_two_path_remain(): + """ + On R5, we shutdown the interface + Check that R8 and R6 are selected for 192.0.2.9 + Check that R8 and R6 are selected for 192.0.2.20 + Check that R5 failure did not change the NHG (EDGE implementation needed) + """ + global nhg_id_1 + global nhg_id_2 + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Shutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + interface {ifname}\n + shutdown\n + """, + isjson=False, + ) + # R6 and R8 are selected + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.9/32", r5_path=False, r6_path=True, r8_path=True + ) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + local_nhg_id_1 = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.9", nhg_id=local_nhg_id_1, r5_path=False, r6_path=True, r8_path=True + ) + + # R6 are selected + check_ipv4_prefix_with_multiple_nexthops( + "192.0.2.20/32", r5_path=False, r6_path=True, r8_path=False + ) + + step("Check that 192.0.2.20/32 unicast entry uses a BGP NHG") + local_nhg_id_2 = route_check_nhg_id_is_protocol("192.0.2.20/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "192.0.2.20", + nhg_id=local_nhg_id_2, + r5_path=False, + r6_path=True, + r8_path=False, + ) + + logger.info( + f"Get the nhg_id used for 192.0.2.9/32: {local_nhg_id_1}, and 192.0.2.20/32: {local_nhg_id_2}" + ) + step("Check that previous NHG used by 192.0.2.9/32 unicast routes is same as now") + if local_nhg_id_1 != nhg_id_1: + logger.warning( + f"The same NHG {nhg_id_1} is not used after R5 shutdown, EDGE implementation missing" + ) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_nhg_topo2/__init__.py b/tests/topotests/bgp_nhg_topo2/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/topotests/bgp_nhg_topo2/r1/bgp_ipv4.json b/tests/topotests/bgp_nhg_topo2/r1/bgp_ipv4.json new file mode 100644 index 000000000000..85f02489b899 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r1/bgp_ipv4.json @@ -0,0 +1,141 @@ +{ + "vrfId": 0, + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "0.0.0.0/0": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix":"0.0.0.0", + "prefixLen": 0, + "network": "0.0.0.0/0", + "metric": 0, + "weight": 0, + "peerId": "172.31.0.2", + "path": "65501", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.31.0.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.1.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "172.31.1.0", + "prefixLen": 24, + "network": "172.31.1.0/24", + "metric": 0, + "weight": 0, + "peerId": "172.31.0.2", + "path": "65501", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.31.0.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "192.168.1.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "192.168.1.0", + "prefixLen": 24, + "network": "192.168.1.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "172.31.0.3", + "path": "", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.31.2.4", + "afi": "ipv4", + "used": true + } + ] + } + ], + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "internal", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "172.31.0.3", + "path": "", + "origin": "IGP", + "nexthops": [ + { + "ip":"172.31.3.4", + "afi": "ipv4", + "used": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "internal", + "prefix": "192.168.3.0", + "prefixLen": 24, + "network": "192.168.3.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "172.31.0.3", + "path": "", + "origin": "IGP", + "nexthops": [ + { + "ip": "192.168.3.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 32768, + "path": "", + "announceNexthopSelf": true, + "nhVrfName": "vrf1", + "nexthops": [ + { + "ip": "0.0.0.0", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_nhg_topo2/r1/bgpd.conf b/tests/topotests/bgp_nhg_topo2/r1/bgpd.conf new file mode 100644 index 000000000000..b01d651764f3 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r1/bgpd.conf @@ -0,0 +1,42 @@ +bgp nexthop-group +route-map rmap_ipv6 permit 1 + set ipv6 next-hop prefer-global +exit +router bgp 65500 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + bgp labeled-unicast ipv4-explicit-null + neighbor 172.31.0.2 remote-as 65501 + neighbor 172.31.0.3 remote-as 65500 + neighbor 172.31.0.4 remote-as 65500 + neighbor 192.0.2.4 remote-as 65500 + neighbor 192.0.2.4 update-source lo + neighbor 192.0.2.4 capability extended-nexthop + neighbor 192.0.2.4 timers connect 1 + address-family ipv4 unicast + import vrf vrf1 + import vpn + no neighbor 192.0.2.4 activate + no neighbor 172.31.0.4 activate + network 192.0.2.1/32 + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 172.31.0.4 activate + exit-address-family + ! + address-family ipv6 unicast + neighbor 192.0.2.4 activate + neighbor 192.0.2.4 route-map rmap_ipv6 in + exit-address-family + ! +exit +router bgp 65500 vrf vrf1 + address-family ipv4 unicast + redistribute connected + export vpn + exit-address-family + ! +exit + + diff --git a/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_1_0.json b/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_1_0.json new file mode 100644 index 000000000000..60f52033f511 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_1_0.json @@ -0,0 +1,37 @@ +{ + "192.168.1.0/24":[ + { + "prefix":"192.168.1.0/24", + "prefixLen":24, + "protocol":"bgp", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "internalNextHopNum":2, + "internalNextHopActiveNum":2, + "nexthops":[ + { + "ip":"172.31.2.4", + "afi":"ipv4", + "active":true, + "recursive":true, + "weight":1 + }, + { + "fib":true, + "ip":"172.31.0.2", + "afi":"ipv4", + "interfaceName":"r1-eth0", + "resolver":true, + "active":true, + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_2_0.json b/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_2_0.json new file mode 100644 index 000000000000..0260003e6808 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_2_0.json @@ -0,0 +1,34 @@ +{ + "192.168.2.0/24":[ + { + "prefix":"192.168.2.0/24", + "prefixLen":24, + "protocol":"bgp", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "nexthops":[ + { + "ip":"172.31.3.4", + "afi":"ipv4", + "active":true, + "recursive":true, + "weight":1 + }, + { + "fib":true, + "unreachable":true, + "blackhole":true, + "resolver":true, + "active":true, + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_3_0.json b/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_3_0.json new file mode 100644 index 000000000000..102a18da32ce --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_3_0.json @@ -0,0 +1,44 @@ +{ + "192.168.3.0/24":[ + { + "prefix":"192.168.3.0/24", + "prefixLen":24, + "protocol":"bgp", + "vrfId":0, + "vrfName":"default", + "distance":200, + "metric":0, + "table":254, + "nexthops":[ + { + "ip":"192.168.3.3", + "afi":"ipv4", + "weight":1 + } + ] + }, + { + "prefix":"192.168.3.0/24", + "prefixLen":24, + "protocol":"static", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":1, + "metric":0, + "installed":true, + "table":254, + "nexthops":[ + { + "fib":true, + "ip":"192.168.5.10", + "afi":"ipv4", + "interfaceName":"r1-eth2", + "active":true, + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_4_0.json b/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_4_0.json new file mode 100644 index 000000000000..b8fcb7e478ca --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r1/ip_route_192_168_4_0.json @@ -0,0 +1,24 @@ +{ + "192.168.4.0/24":[ + { + "prefix":"192.168.4.0/24", + "prefixLen":24, + "protocol":"bgp", + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":254, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"vrf1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_nhg_topo2/r1/ipv6_route_1001_64.json b/tests/topotests/bgp_nhg_topo2/r1/ipv6_route_1001_64.json new file mode 100644 index 000000000000..dd06084dcd03 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r1/ipv6_route_1001_64.json @@ -0,0 +1,25 @@ +{ + "1001::/64":[ + { + "prefix":"1001::/64", + "prefixLen":64, + "protocol":"bgp", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "table":254, + "nexthops":[ + { + "ip":"::ffff:192.0.2.4", + "afi":"ipv6", + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_nhg_topo2/r1/zebra.conf b/tests/topotests/bgp_nhg_topo2/r1/zebra.conf new file mode 100644 index 000000000000..0dda9d1b0ef4 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r1/zebra.conf @@ -0,0 +1,20 @@ +ip route 172.31.3.4/32 blackhole +ip route 192.168.3.0/24 192.168.5.10 +ipv6 route 1010::/64 1003::10 +interface r1-eth0 + ip address 172.31.0.1/24 + ipv6 address ::ffff:172.31.0.1/24 +! +interface r1-eth1 + ip address 192.168.0.1/24 +! +interface r1-eth2 + ip address 192.168.5.1/24 + ipv6 address 1003::1/64 +! +interface r1-eth3 vrf vrf1 + ip address 192.168.4.1/24 +! +interface lo + ip address 192.0.2.1/32 +! diff --git a/tests/topotests/bgp_nhg_topo2/r2/bgpd.conf b/tests/topotests/bgp_nhg_topo2/r2/bgpd.conf new file mode 100644 index 000000000000..6e12f0c3d94d --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r2/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 65501 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + neighbor 172.31.0.1 remote-as 65500 + address-family ipv4 unicast + neighbor 172.31.0.1 activate + neighbor 172.31.0.1 default-originate + network 172.31.1.0/24 + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_topo2/r2/zebra.conf b/tests/topotests/bgp_nhg_topo2/r2/zebra.conf new file mode 100644 index 000000000000..7a29412b392d --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r2/zebra.conf @@ -0,0 +1,8 @@ +log stdout +interface r2-eth0 + ip address 172.31.0.2/24 +! +interface r2-eth1 + ip address 172.31.1.2/24 +! + diff --git a/tests/topotests/bgp_nhg_topo2/r3/bgpd.conf b/tests/topotests/bgp_nhg_topo2/r3/bgpd.conf new file mode 100644 index 000000000000..38d6d509107b --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r3/bgpd.conf @@ -0,0 +1,23 @@ +access-list acl1 seq 1 permit 192.168.1.0/24 +access-list acl2 seq 1 permit 192.168.2.0/24 +access-list acl3 seq 1 permit 192.168.3.0/24 +route-map rmap permit 1 + match ip address acl1 + set ip next-hop 172.31.2.4 +exit +route-map rmap permit 2 + match ip address acl2 + set ip next-hop 172.31.3.4 +exit +route-map rmap permit 3 + match ip address acl3 + set ip next-hop 192.168.3.3 +exit +router bgp 65500 + bgp router-id 192.0.2.3 + neighbor 172.31.0.1 remote-as 65500 + address-family ipv4 unicast + network 192.168.1.0/24 route-map rmap + network 192.168.2.0/24 route-map rmap + network 192.168.3.0/24 route-map rmap + exit-address-family diff --git a/tests/topotests/bgp_nhg_topo2/r3/zebra.conf b/tests/topotests/bgp_nhg_topo2/r3/zebra.conf new file mode 100644 index 000000000000..db1e9b6f3ba4 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r3/zebra.conf @@ -0,0 +1,11 @@ +log stdout +interface r3-eth0 + ip address 172.31.0.3/24 +! +interface r3-eth1 + ip address 192.168.1.3/24 + ip address 192.168.2.3/24 + ip address 192.168.3.3/24 + ip address 192.168.4.3/24 + ipv6 address 1001::3/64 +! diff --git a/tests/topotests/bgp_nhg_topo2/r4/bgpd.conf b/tests/topotests/bgp_nhg_topo2/r4/bgpd.conf new file mode 100644 index 000000000000..f4508be8a4e4 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r4/bgpd.conf @@ -0,0 +1,38 @@ +route-map rmap_ipv6 permit 1 + set ipv6 next-hop prefer-global +exit +ipv6 access-list acl1 seq 1 permit 1002::/64 +ipv6 access-list acl2 seq 1 permit 1001::/64 +route-map rmap_out permit 1 + match ipv6 address acl1 + set ipv6 next-hop global 1010::10 +exit +route-map rmap_out permit 2 + match ipv6 address acl2 +exit +router bgp 65500 + bgp router-id 192.0.2.4 + bgp labeled-unicast ipv4-explicit-null + neighbor 172.31.0.1 remote-as 65500 + neighbor 192.0.2.1 remote-as 65500 + neighbor 192.0.2.1 capability extended-nexthop + neighbor 192.0.2.1 update-source lo + neighbor 192.0.2.1 timers connect 1 + address-family ipv4 unicast + network 192.0.2.4/32 + no neighbor 192.0.2.1 activate + no neighbor 172.31.0.1 activate + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 172.31.0.1 activate + exit-address-family + ! + address-family ipv6 unicast + neighbor 192.0.2.1 activate + neighbor 192.0.2.1 route-map rmap_ipv6 in + neighbor 192.0.2.1 route-map rmap_out out + network 1001::/64 + network 1002::/64 + exit-address-family + ! diff --git a/tests/topotests/bgp_nhg_topo2/r4/zebra.conf b/tests/topotests/bgp_nhg_topo2/r4/zebra.conf new file mode 100644 index 000000000000..b37689a40fed --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/r4/zebra.conf @@ -0,0 +1,12 @@ +log stdout +interface r4-eth0 + ip address 172.31.0.4/24 + ipv6 address ::ffff:172.31.0.4/120 +! +interface r4-eth1 + ipv6 address 1001::4/64 + ipv6 address 1002::4/64 +! +interface lo + ip address 192.0.2.4/32 +! diff --git a/tests/topotests/bgp_nhg_topo2/test_bgp_nhg_topo2.py b/tests/topotests/bgp_nhg_topo2/test_bgp_nhg_topo2.py new file mode 100644 index 000000000000..fab48a77a807 --- /dev/null +++ b/tests/topotests/bgp_nhg_topo2/test_bgp_nhg_topo2.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_nhg_topo2.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2024 by 6WIND +# + +""" + test_bgp_nhg_topo2.py: Test the FRR BGP daemon with EBGP direct connection +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.bgpcheck import ( + check_show_bgp_vpn_prefix_found, + check_show_bgp_vpn_prefix_not_found, +) +from lib.common_config import step +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + # Create 2 routers. + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r3") + tgen.add_router("r4") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s7") + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s8") + switch.add_link(tgen.gears["r1"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list_r1 = [ + "ip link add vrf1 type vrf table 10", + "echo 100000 > /proc/sys/net/mpls/platform_labels", + "ip link set dev vrf1 up", + "ip link set dev {0}-eth3 master vrf1", + "echo 1 > /proc/sys/net/mpls/conf/{0}-eth3/input", + "echo 1 > /proc/sys/net/mpls/conf/{0}-eth0/input", + ] + + for cmd in cmds_list_r1: + input = cmd.format("r1") + logger.info("input: " + cmd.format("r1")) + output = tgen.net["r1"].cmd(cmd.format("r1")) + logger.info("output: " + output) + + cmds_list_r4 = [ + "echo 1 > /proc/sys/net/mpls/conf/{0}-eth0/input", + ] + for cmd in cmds_list_r4: + input = cmd.format("r4") + logger.info("input: " + cmd.format("r4")) + output = tgen.net["r4"].cmd(cmd.format("r4")) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + _populate_iface() + router_list = tgen.routers() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def test_protocols_convergence_ipv4(): + """ + Assert that BGP R1 has converged with IPv4 prefixes + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Check that BGP R1 has converged with IPv4") + router = tgen.gears["r1"] + expected = json.loads(open("{}/r1/bgp_ipv4.json".format(CWD)).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp ipv4 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertmsg = ( + '"{}" JSON output mismatches, BGP R1 has not converged with IPv4'.format( + router.name + ) + ) + assert result is None, assertmsg + + +def check_bgp_nexthop_group_disabled_for_prefix(prefix, router_name): + """ + Assert that the prefix does not use BGP NHG + because it is resolved over blackhole + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + donna = tgen.gears["r1"].vtysh_cmd( + "show bgp nexthop-group detail json", isjson=True + ) + nhg_found = False + nhg_id = 0 + nb_paths = 0 + for nhg_ctx in donna: + if "paths" not in nhg_ctx.keys(): + continue + for path_ctx in nhg_ctx["paths"]: + if "prefix" not in path_ctx.keys(): + continue + nb_paths = nb_paths + 1 + if path_ctx["prefix"] == "192.168.2.0/24": + nhg_found = True + nhg_id = nhg_ctx["nhgId"] + + assertmsg = '"{}" no NHG paths found'.format(router_name) + assert nb_paths != 0, assertmsg + + assertmsg = '"{}" 192.168.2.0 prefix found used with NHG {}'.format( + router_name, nhg_id + ) + assert nhg_found == False, assertmsg + + +def test_bgp_nexthop_group_disabled_for_blackhole(): + """ + Assert that the 192.168.2.0/24 prefix does not use BGP NHG + because it is resolved over blackhole + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Assert that the 192.168.2.0/24 is resolved over blackhole route") + router = tgen.gears["r1"] + expected = json.loads(open("{}/r1/ip_route_192_168_2_0.json".format(CWD)).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ip route 192.168.2.0/24 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = '"{}" JSON output mismatches, 192.168.2.0/24 is not resolved over blackhole route'.format( + router.name + ) + assert result is None, assertmsg + + step("Assert that the 192.168.2.0/24 prefix does not use BGP NHG") + check_bgp_nexthop_group_disabled_for_prefix("192.168.2.0/24", "r1") + + +def test_bgp_nexthop_group_disabled_for_routes_resolving_over_default_route(): + """ + Assert that the 192.168.1.0/24 prefix does not use BGP NHG + because it is resolved over default route + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Assert that the 192.168.1.0/24 is resolved over 172.31.0.2 (default route)") + router = tgen.gears["r1"] + expected = json.loads(open("{}/r1/ip_route_192_168_1_0.json".format(CWD)).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ip route 192.168.1.0/24 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = '"{}" JSON output mismatches, 192.168.1.0/24 is not resolved over 172.31.0.2 (default route)'.format( + router.name + ) + assert result is None, assertmsg + + step("Assert that the 192.168.1.0/24 prefix does not use BGP NHG") + check_bgp_nexthop_group_disabled_for_prefix("192.168.1.0/24", "r1") + + +def test_bgp_nexthop_group_disabled_for_routes_resolving_over_same_prefix(): + """ + Assert that the 192.168.3.0/24 prefix does not use BGP NHG + because it is resolved over an already present exact same prefix + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Assert that the 192.168.3.0/24 has 2 entries in ZEBRA") + router = tgen.gears["r1"] + expected = json.loads(open("{}/r1/ip_route_192_168_3_0.json".format(CWD)).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ip route 192.168.3.0/24 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = ( + '"{}" JSON output mismatches, 192.168.3.0/24 has not 2 entries in ZEBRA'.format( + router.name + ) + ) + assert result is None, assertmsg + + step("Assert that the 192.168.3.0/24 has a nexthop") + donna = router.vtysh_cmd("show bgp ipv4 192.168.3.0/24 json", isjson=True) + # look for first available nexthop + nexthop_to_check = "" + for path in donna["paths"]: + if "nexthops" not in path.keys(): + continue + for nh in path["nexthops"]: + if "ip" in nh.keys(): + nexthop_to_check = nh["ip"] + break + + assert ( + nexthop_to_check != "" + ), '"{}", 192.168.3.0/24 prefix has no valid nexthop'.format(router.name) + + step( + f"Check that nexthop {nexthop_to_check} is resolved over 192.168.3.0/24 prefix" + ) + donna = router.vtysh_cmd(f"show bgp nexthop {nexthop_to_check} json", isjson=True) + if "ipv4" not in donna.keys() or "resolvedPrefix" not in donna["ipv4"].keys(): + assert 0, '"{}", {} nexthop is invalid'.format(router.name, nexthop_to_check) + + assertmsg = '"{}", 192.168.3.0/24 prefix is not resolving over itself'.format( + router.name + ) + assert donna["ipv4"]["resolvedPrefix"] == "192.168.3.0/24", assertmsg + + step("Assert that the 192.168.3.0/24 prefix does not use BGP NHG") + check_bgp_nexthop_group_disabled_for_prefix("192.168.3.0/24", "r1") + + +def test_bgp_nexthop_group_disabled_for_imported_routes(): + """ + Assert that the 192.168.4.0/24 prefix does not use BGP NHG + because it is directly imported, connected over an interface + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Assert that the 192.168.4.0/24 is installed") + router = tgen.gears["r1"] + expected = json.loads(open("{}/r1/ip_route_192_168_4_0.json".format(CWD)).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ip route 192.168.4.0/24 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = '"{}" JSON output mismatches, 192.168.4.0/24 is not installed'.format( + router.name + ) + assert result is None, assertmsg + + step("Assert that the 192.168.4.0/24 prefix does not use BGP NHG") + check_bgp_nexthop_group_disabled_for_prefix("192.168.4.0/24", "r1") + + +def test_bgp_nexthop_group_disabled_for_6pe_routes(): + """ + Assert that the 1001::/64 prefix does not use BGP NHG + because its nexthop is an IPv4 mapped IPv6 address + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Assert that the 1001::/64 is present on ZEBRA") + router = tgen.gears["r1"] + expected = json.loads(open("{}/r1/ipv6_route_1001_64.json".format(CWD)).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ipv6 route 1001::/64 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = '"{}" JSON output mismatches, 192.168.4.0/24 is not installed'.format( + router.name + ) + assert result is None, assertmsg + + step("Assert that the 1001::/64 prefix does not use BGP NHG") + check_bgp_nexthop_group_disabled_for_prefix("1001::/64", "r1") + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_nhg_zapi_scalability/__init__.py b/tests/topotests/bgp_nhg_zapi_scalability/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r1/bgpd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r1/bgpd.conf new file mode 100644 index 000000000000..86ec0b5ae628 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r1/bgpd.conf @@ -0,0 +1,17 @@ +bgp nexthop-group +bgp suppress-fib-pending +bgp nexthop-group +router bgp 64500 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + neighbor rrserver peer-group + neighbor rrserver remote-as 64500 + neighbor rrserver update-source lo + neighbor rrserver timers connect 2 + neighbor 192.0.2.3 peer-group rrserver + neighbor 192.0.2.3 bfd + address-family ipv4 unicast + neighbor rrserver next-hop-self + neighbor rrserver activate + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r1/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r1/isisd.conf new file mode 100644 index 000000000000..9660577f4e8f --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r1/isisd.conf @@ -0,0 +1,26 @@ +hostname r1 +interface lo + ip router isis 1 + isis passive +! +interface r1-eth1 + ip router isis 1 + isis network point-to-point +! +interface r1-eth2 + ip router isis 1 + isis network point-to-point +! +interface r1-eth4 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0001.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.1/32 index 1 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r1/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability/r1/zebra.conf new file mode 100644 index 000000000000..2e3549a57401 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r1/zebra.conf @@ -0,0 +1,24 @@ +log stdout +interface lo + ip address 192.0.2.1/32 +! +interface r1-eth0 + ip address 172.31.10.1/24 +! +interface r1-eth1 + ip address 172.31.0.1/24 + mpls enable +! +interface r1-eth2 + ip address 172.31.2.1/24 + mpls enable +! +interface r1-eth3 + ip address 172.31.11.1/24 + mpls enable +! +interface r1-eth4 + ip address 172.31.8.1/24 + mpls enable +! + diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r3/bgpd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r3/bgpd.conf new file mode 100644 index 000000000000..4eb38658ef5b --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r3/bgpd.conf @@ -0,0 +1,19 @@ +router bgp 64500 view one + bgp router-id 192.0.2.3 + neighbor rr peer-group + neighbor rr remote-as 64500 + neighbor rr update-source lo + neighbor 192.0.2.1 peer-group rr + neighbor 192.0.2.5 peer-group rr + neighbor 192.0.2.6 peer-group rr + neighbor 192.0.2.8 peer-group rr + neighbor 192.0.2.1 bfd check-control-plane-failure + neighbor 192.0.2.6 bfd check-control-plane-failure + neighbor 192.0.2.8 bfd check-control-plane-failure + ! + address-family ipv4 unicast + neighbor rr activate + neighbor rr route-reflector-client + neighbor rr addpath-tx-all-paths + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r3/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r3/isisd.conf new file mode 100644 index 000000000000..a11cd06eb57d --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r3/isisd.conf @@ -0,0 +1,39 @@ +hostname r3 +interface lo + ip router isis 1 + isis passive +! +interface r3-eth0 + ip router isis 1 + isis network point-to-point +! +interface r3-eth1 + ip router isis 1 + isis network point-to-point + isis bfd +! +interface r3-eth2 + ip router isis 1 + isis network point-to-point +! +interface r3-eth3 + ip router isis 1 + isis network point-to-point +! +interface r3-eth4 + ip router isis 1 + isis network point-to-point +! +interface r3-eth5 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0003.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.3/32 index 3 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r3/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability/r3/zebra.conf new file mode 100644 index 000000000000..05b3769fb8bf --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r3/zebra.conf @@ -0,0 +1,16 @@ +log stdout +interface lo + ip address 192.0.2.3/32 +! +interface r3-eth0 + ip address 172.31.0.3/24 + mpls enable +! +interface r3-eth1 + ip address 172.31.4.3/24 + mpls enable +! +interface r3-eth2 + ip address 172.31.5.3/24 + mpls enable +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r4/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r4/isisd.conf new file mode 100644 index 000000000000..c33fbd08d34b --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r4/isisd.conf @@ -0,0 +1,31 @@ +hostname r4 +interface lo + ip router isis 1 + isis passive +! +interface r4-eth0 + ip router isis 1 + isis network point-to-point +! +interface r4-eth1 + ip router isis 1 + isis network point-to-point +! +interface r4-eth2 + ip router isis 1 + isis network point-to-point + isis bfd +! +interface r4-eth3 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0004.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.4/32 index 4 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r4/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability/r4/zebra.conf new file mode 100644 index 000000000000..9ea1b7ec4314 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r4/zebra.conf @@ -0,0 +1,20 @@ +log stdout +interface lo + ip address 192.0.2.4/32 +! +interface r4-eth0 + ip address 172.31.2.4/24 + mpls enable +! +interface r4-eth1 + ip address 172.31.6.4/24 + mpls enable +! +interface r4-eth2 + ip address 172.31.7.4/24 + mpls enable +! +interface r4-eth3 + mpls enable +! + diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r5/bgpd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r5/bgpd.conf new file mode 100644 index 000000000000..5b2e2133f786 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r5/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 64500 + bgp router-id 192.0.2.5 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor rrserver peer-group + neighbor rrserver remote-as 64500 + neighbor rrserver update-source lo + neighbor rrserver timers connect 2 + neighbor 192.0.2.3 peer-group rrserver + address-family ipv4 unicast + network 192.0.2.9/32 + neighbor rrserver activate + redistribute sharp + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r5/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r5/isisd.conf new file mode 100644 index 000000000000..b968ce11db88 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r5/isisd.conf @@ -0,0 +1,27 @@ +hostname r5 +interface lo + ip router isis 1 + isis passive +! +interface r5-eth1 + ip router isis 1 + isis network point-to-point + isis bfd +! +interface r5-eth2 + ip router isis 1 + isis network point-to-point +! +interface r5-eth3 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0005.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.5/32 index 55 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r5/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability/r5/zebra.conf new file mode 100644 index 000000000000..6f326561e733 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r5/zebra.conf @@ -0,0 +1,19 @@ +log stdout +mpls label dynamic-block 5000 5999 +interface lo + ip address 192.0.2.5/32 +! +interface r5-eth0 + ip address 172.31.12.5/24 +! +interface r5-eth1 + ip address 172.31.4.5/24 + mpls enable +! +interface r5-eth2 + ip address 172.31.7.5/24 + mpls enable +! +interface r5-eth3 + ip address 172.31.21.5/24 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r6/bgpd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r6/bgpd.conf new file mode 100644 index 000000000000..ae0ff9aff2a6 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r6/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 64500 + bgp router-id 192.0.2.6 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor rrserver peer-group + neighbor rrserver remote-as 64500 + neighbor rrserver update-source lo + neighbor rrserver bfd + neighbor 192.0.2.3 peer-group rrserver + address-family ipv4 unicast + network 192.0.2.9/32 + neighbor rrserver activate + redistribute sharp + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r6/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r6/isisd.conf new file mode 100644 index 000000000000..5126a6485846 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r6/isisd.conf @@ -0,0 +1,22 @@ +hostname r6 +interface lo + ip router isis 1 + isis passive +! +interface r6-eth1 + ip router isis 1 + isis network point-to-point +! +interface r6-eth2 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0006.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.6/32 index 6 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r6/sharpd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r6/sharpd.conf new file mode 100644 index 000000000000..9d9a6e2b0678 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r6/sharpd.conf @@ -0,0 +1 @@ +sharp install routes 193.0.0.1 nexthop 192.0.2.6 2000 diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r6/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability/r6/zebra.conf new file mode 100644 index 000000000000..cda62d7e87a0 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r6/zebra.conf @@ -0,0 +1,20 @@ +log stdout +mpls label dynamic-block 6000 6999 +interface lo + ip address 192.0.2.6/32 +! +interface r6-eth0 + ip address 172.31.13.6/24 +! +interface r6-eth1 + ip address 172.31.5.6/24 + mpls enable +! +interface r6-eth2 + ip address 172.31.6.6/24 + mpls enable +! +interface r6-eth3 + ip address 172.31.22.6/24 +! + diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r7/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r7/isisd.conf new file mode 100644 index 000000000000..b6e78749d4fd --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r7/isisd.conf @@ -0,0 +1,23 @@ +hostname r7 +interface lo + ip router isis 1 + isis passive +! +interface r7-eth0 + ip router isis 1 + isis network point-to-point +! +interface r7-eth1 + ip router isis 1 + isis network point-to-point + isis bfd +! +router isis 1 + net 49.0123.6452.0007.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.7/32 index 7 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r7/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability/r7/zebra.conf new file mode 100644 index 000000000000..281922a6cd4b --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r7/zebra.conf @@ -0,0 +1,14 @@ +log stdout +interface lo + ip address 192.0.2.7/32 +! +interface r7-eth0 + ip address 172.31.8.7/24 + mpls enable +! shutdown +! +interface r7-eth1 + ip address 172.31.9.7/24 + mpls enable +! + diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r8/bgpd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r8/bgpd.conf new file mode 100644 index 000000000000..5fc51c349dcd --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r8/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 64500 + bgp router-id 192.0.2.8 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor rrserver peer-group + neighbor rrserver remote-as 64500 + neighbor rrserver update-source lo + neighbor rrserver bfd + neighbor 192.0.2.3 peer-group rrserver + address-family ipv4 unicast + network 192.0.2.9/32 + neighbor rrserver activate + redistribute sharp + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r8/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r8/isisd.conf new file mode 100644 index 000000000000..8f62ff5fc9a4 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r8/isisd.conf @@ -0,0 +1,20 @@ +hostname r8 +interface lo + ip router isis 1 + isis passive + isis metric 10 +! +interface r8-eth0 + ip router isis 1 + isis network point-to-point + isis bfd +! +router isis 1 + net 49.0123.6452.0008.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.8/32 index 8 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r8/sharpd.conf b/tests/topotests/bgp_nhg_zapi_scalability/r8/sharpd.conf new file mode 100644 index 000000000000..ebd81338e9a8 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r8/sharpd.conf @@ -0,0 +1 @@ +sharp install routes 193.0.0.1 nexthop 192.0.2.8 2000 diff --git a/tests/topotests/bgp_nhg_zapi_scalability/r8/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability/r8/zebra.conf new file mode 100644 index 000000000000..b94a6ba0b37a --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/r8/zebra.conf @@ -0,0 +1,16 @@ +log stdout +mpls label dynamic-block 6000 6999 +interface lo + ip address 192.0.2.8/32 +! +interface r8-eth0 + ip address 172.31.9.8/24 + mpls enable +! +interface r8-eth1 + ip address 172.31.14.8/24 +! +interface r8-eth2 + ip address 172.31.23.8/24 +! + diff --git a/tests/topotests/bgp_nhg_zapi_scalability/test_bgp_nhg_zapi_scalability.py b/tests/topotests/bgp_nhg_zapi_scalability/test_bgp_nhg_zapi_scalability.py new file mode 100644 index 000000000000..259190d4dfc8 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability/test_bgp_nhg_zapi_scalability.py @@ -0,0 +1,1477 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_nhg_zapi_scalability.py +# +# Copyright 2024 6WIND S.A. +# + +""" + test_bgp_nhg_zapi_scalability.py: + Check that the FRR BGP daemon reduces the number of route_add messages + by using bgp nexthop group facility. + + ++---+----+ +---+----+ +--------+ +| | | + | | +| r1 +----------+ r3 +----------+ r5 + +| | | rr + +-----+ | ++++-+----+ +--------+\ / +--------+ + | | \/ + | | /\ + | | +--------+/ \ +--------+ + | | | + +-----+ + + | +---------------+ r4 +----------+ r6 + + | | | | | + | +--------+ +--------+ + | + | +--------+ +--------+ + | | | | + + +-----------------+ r7 +----------+ r8 + + | | | | + +--------+ +--------+ +""" + +import os +import sys +import json +from functools import partial +import pytest +import functools + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.common_check import ip_check_path_selection, iproute2_check_path_selection +from lib.common_config import step +from lib.nexthopgroup import route_check_nhg_id_is_protocol +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + +nhg_id = 0 +nhg_id_18 = 0 +nhg_id_22 = 0 +route_count = 0 +route_exact_number = 6001 + + +def build_topo(tgen): + "Build function" + + # Create 7 PE routers. + tgen.add_router("r1") + tgen.add_router("r3") + tgen.add_router("r4") + tgen.add_router("r5") + tgen.add_router("r6") + tgen.add_router("r7") + tgen.add_router("r8") + + # switch + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s7") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s8") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s9") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s10") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s11") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s12") + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s13") + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s14") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s15") + switch.add_link(tgen.gears["r7"]) + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s16") + switch.add_link(tgen.gears["r7"]) + switch.add_link(tgen.gears["r8"]) + + switch = tgen.add_switch("s17") + switch.add_link(tgen.gears["r8"]) + + switch = tgen.add_switch("s18") + switch.add_link(tgen.gears["r8"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list = [ + "ip link add loop2 type dummy", + "ip link set dev loop2 up", + ] + + cmds_list = [ + "modprobe mpls_router", + "echo 100000 > /proc/sys/net/mpls/platform_labels", + ] + + for name in ("r1", "r3", "r4", "r5", "r6", "r7", "r8"): + for cmd in cmds_list: + logger.info("input: " + cmd) + output = tgen.net[name].cmd(cmd) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + _populate_iface() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + if rname in ("r1", "r3", "r4", "r5", "r6", "r7", "r8"): + router.load_config( + TopoRouter.RD_ISIS, os.path.join(CWD, "{}/isisd.conf".format(rname)) + ) + if rname in ("r1", "r3", "r5", "r6", "r7", "r8"): + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + if rname in ("r1", "r3", "r5", "r6", "r8"): + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + if rname in ("r5", "r6", "r8"): + router.load_config( + TopoRouter.RD_SHARP, os.path.join(CWD, "{}/sharpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def ip_check_ibgp_prefix_count_in_rib(router, count): + output = json.loads(router.vtysh_cmd(f"show ip route summary json")) + for entry in output["routes"]: + if entry["type"] == "ibgp": + if count == int(entry["rib"]): + return None + return f'ibgp ipv4 route count differs from expected: {entry["rib"]}, expected {count}' + return f"ibgp ipv4 route count not found" + + +def check_ipv4_prefix_with_multiple_nexthops( + prefix, r5_path=True, r6_path=True, r8_path=False +): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info( + f"Check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path}" + ) + + r5_nh = [ + { + "ip": "192.0.2.5", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16055, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16055, + ], + }, + ] + + r6_nh = [ + { + "ip": "192.0.2.6", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16006, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16006, + ], + }, + ] + + r8_nh = [ + { + "ip": "192.0.2.8", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.8.7", + "interfaceName": "r1-eth4", + "active": True, + "labels": [ + 16008, + ], + }, + ] + + expected = { + prefix: [ + { + "prefix": prefix, + "protocol": "bgp", + "metric": 0, + "table": 254, + "nexthops": [], + } + ] + } + if r5_path: + for nh in r5_nh: + expected[prefix][0]["nexthops"].append(nh) + if r6_path: + for nh in r6_nh: + expected[prefix][0]["nexthops"].append(nh) + if r8_path: + for nh in r8_nh: + expected[prefix][0]["nexthops"].append(nh) + + test_func = functools.partial( + ip_check_path_selection, tgen.gears["r1"], prefix, expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, f"Failed to check that {prefix} uses the IGP label 16055" + + +def check_ipv4_prefix_recursive_with_multiple_nexthops( + prefix, recursive_nexthop, r5_path=True, r6_path=True, r8_path=False +): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info( + f"Check that {prefix} unicast entry is correctly recursive via {recursive_nexthop} with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path}" + ) + + r5_nh = [ + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16055, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16055, + ], + }, + ] + + r6_nh = [ + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16006, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16006, + ], + }, + ] + + r8_nh = [ + { + "ip": "172.31.8.7", + "interfaceName": "r1-eth4", + "active": True, + "labels": [ + 16008, + ], + }, + ] + + expected = { + prefix: [ + { + "prefix": prefix, + "protocol": "bgp", + "metric": 0, + "table": 254, + "nexthops": [], + } + ] + } + + recursive_nh = [ + { + "ip": recursive_nexthop, + "active": True, + "recursive": True, + }, + ] + for nh in recursive_nh: + expected[prefix][0]["nexthops"].append(nh) + + if r5_path: + for nh in r5_nh: + expected[prefix][0]["nexthops"].append(nh) + if r6_path: + for nh in r6_nh: + expected[prefix][0]["nexthops"].append(nh) + if r8_path: + for nh in r8_nh: + expected[prefix][0]["nexthops"].append(nh) + + test_func = functools.partial( + ip_check_path_selection, + tgen.gears["r1"], + prefix, + expected, + ignore_duplicate_nh=True, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), f"Failed to check that {prefix} is correctly recursive via {recursive_nexthop}" + + +def check_ipv4_prefix_with_multiple_nexthops_linux( + prefix, nhg_id, r5_path=True, r6_path=True, r8_path=False +): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + f"Check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path} with NHID {nhg_id} on Linux" + ) + + r5_nh = [ + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ] + + r6_nh = [ + { + "encap": "mpls", + "dst": "16006", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16006", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ] + + r8_nh = [ + { + "encap": "mpls", + "dst": "16008", + "gateway": "172.31.8.7", + "dev": "r1-eth4", + } + ] + + expected_r8_nh_only = [ + { + "dst": prefix, + "protocol": "bgp", + "metric": 20, + "encap": "mpls", + "dst": "16008", + "gateway": "172.31.8.7", + "dev": "r1-eth4", + } + ] + expected = [ + { + "dst": prefix, + "protocol": "bgp", + "metric": 20, + "nexthops": [], + } + ] + + # only one path + if r8_path and not r5_path and not r6_path: + expected = expected_r8_nh_only + else: + if r5_path: + for nh in r5_nh: + expected[0]["nexthops"].append(nh) + if r6_path: + for nh in r6_nh: + expected[0]["nexthops"].append(nh) + if r8_path: + for nh in r8_nh: + expected[0]["nexthops"].append(nh) + + test_func = functools.partial( + iproute2_check_path_selection, + tgen.routers()["r1"], + prefix, + expected, + nhg_id=nhg_id, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), f"Failed to check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path} on Linux with BGP ID" + + +def _get_bgp_route_count(router, add_routes=True): + """ + Dump 'show zebra client' for BGP and extract the number of ipv4 route add messages + if add_routes is not True, then it returns the number of ipv4 route_delete messages + """ + if add_routes: + return int( + router.cmd( + "vtysh -c 'show zebra client' | grep -e 'Client: bgp$' -A 40 | grep IPv4 | awk -F ' ' '{print $2}' | awk -F\, '$1 > 6000'" + ) + ) + # IS-IS may have counter to update, lets filter only on BGP clients + return int( + router.cmd( + "vtysh -c 'show zebra client' | grep -e 'Client: bgp$' -A 40 | grep IPv4 | awk -F ' ' '{print $4}' | uniq" + ) + ) + + +def test_bgp_ipv4_convergence(): + """ + Check that R1 has received the 192.0.2.9/32 prefix from R5, and R8 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Ensure that the 192.0.2.9/32 route is available") + check_ipv4_prefix_with_multiple_nexthops("192.0.2.9/32", r8_path=True) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux("192.0.2.9", nhg_id=local_nhg_id) + + +def test_bgp_ipv4_multiple_routes(): + """ + Configure 2000 routes on R5, R6, and R8, and redistribute routes in BGP. + Check that R1 has received 2 of those routesprefix from R5, and R8 + Check that the number of RIB routes in ZEBRA is 2001 + """ + global nhg_id, nhg_id_18, nhg_id_22 + global route_count, route_exact_number + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r5"].vtysh_cmd( + "sharp install routes 172.16.0.100 nexthop 192.0.2.5 2000\n" + ) + tgen.gears["r6"].vtysh_cmd( + "sharp install routes 172.16.0.100 nexthop 192.0.2.6 2000\n" + ) + tgen.gears["r8"].vtysh_cmd( + "sharp install routes 172.16.0.100 nexthop 192.0.2.8 2000\n" + ) + tgen.gears["r5"].vtysh_cmd( + "sharp install routes 172.18.1.100 nexthop 172.16.0.200 2000\n" + ) + tgen.gears["r6"].vtysh_cmd( + "sharp install routes 172.18.1.100 nexthop 172.16.0.200 2000\n" + ) + tgen.gears["r8"].vtysh_cmd( + "sharp install routes 172.18.1.100 nexthop 172.16.0.200 2000\n" + ) + tgen.gears["r5"].vtysh_cmd( + "sharp install routes 172.22.1.100 nexthop 172.18.1.200 2000\n" + ) + tgen.gears["r6"].vtysh_cmd( + "sharp install routes 172.22.1.100 nexthop 172.18.1.200 2000\n" + ) + tgen.gears["r8"].vtysh_cmd( + "sharp install routes 172.22.1.100 nexthop 172.18.1.200 2000\n" + ) + + check_ipv4_prefix_with_multiple_nexthops("172.16.5.150/32", r8_path=True) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.200", r8_path=True + ) + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.200", r8_path=True + ) + + step("Check that 192.0.2.9/32 unicast entry has 1 BGP NHG") + nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux("172.16.5.150", nhg_id=nhg_id) + + step("Check that 172.18.1.100/32 unicast entry has 1 BGP NHG") + nhg_id_18 = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux("172.18.1.100/32", nhg_id=nhg_id_18) + + step("Check that 172.22.1.100/32 unicast entry has 1 BGP NHG") + nhg_id_22 = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux("172.22.1.100/32", nhg_id=nhg_id_22) + + step(f"Check that the ipv4 zebra RIB count reaches {route_exact_number}") + test_func = functools.partial( + ip_check_ibgp_prefix_count_in_rib, tgen.gears["r1"], route_exact_number + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), f"Failed to check that the ipv4 zebra RIB count reaches {route_exact_number} :{result}" + + +def test_bgp_ipv4_simulate_r5_machine_going_down(): + """ + On R5, we shutdown the interface + Check that only R8 is selected + Check that R5 failure did not change the NHG (EDGE implementation needed) + Check that the number of zclient messages did not move + """ + global nhg_id, nhg_id_18, nhg_id_22 + global route_count + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # debug + tgen.gears["r1"].vtysh_cmd("show zebra client") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop-group") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop") + + # take the reference number of zebra route add/update messages + local_route_count_add = _get_bgp_route_count(tgen.net["r1"]) + logger.info( + f"Number of route messages between BGP and ZEBRA, before shutdown: {local_route_count_add}" + ) + + step("Shutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + interface {ifname}\n + shutdown\n + """, + isjson=False, + ) + check_ipv4_prefix_with_multiple_nexthops( + "172.16.5.150/32", r5_path=False, r8_path=True + ) + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.200", r5_path=False, r8_path=True + ) + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.200", r5_path=False, r8_path=True + ) + + step("Check that 172.16.5.150/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.5.150", nhg_id=local_nhg_id, r5_path=False, r8_path=True + ) + + step("Check that other NHG is used by 172.16.5.150/32 unicast routes") + assert local_nhg_id == nhg_id, ( + "The same NHG %d is not used after R5 shutdown, EDGE implementation missing" + % nhg_id + ) + + step("Check that 172.18.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r5_path=False, r8_path=True + ) + step("Check that other NHG is used by 172.18.1.100/32 unicast routes") + assert local_nhg_id == nhg_id_18, ( + "The same NHG %d is not used after R5 shutdown, EDGE implementation missing" + % nhg_id_18 + ) + + step("Check that 172.22.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=local_nhg_id, r5_path=False, r8_path=True + ) + step("Check that other NHG is used by 172.22.1.150/32 unicast routes") + assert local_nhg_id == nhg_id_22, ( + "The same NHG %d is not used after R5 shutdown, EDGE implementation missing" + % nhg_id_22 + ) + + step( + "Check that the number of route ADD messages between BGP and ZEBRA did not move" + ) + route_count = _get_bgp_route_count(tgen.net["r1"]) + + logger.info(f"Number of route messages ADD: {route_count}") + assert route_count == local_route_count_add, ( + "The number of route messages increased when r5 machine goes down : %d, expected %d" + % (route_count, local_route_count_add) + ) + + step("Check that the number of route DEL messages between BGP and ZEBRA is zero") + local_route_count_del = _get_bgp_route_count(tgen.net["r1"], add_routes=False) + + logger.info(f"Get the route count messages between BGP and ZEBRA: {route_count}") + assert local_route_count_del == 0, ( + "The number of route messages delete increased when r5 machine goes down : %d, expected 0" + % local_route_count_del + ) + # debug + tgen.gears["r1"].vtysh_cmd("show zebra client") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop-group") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop") + + +def test_bgp_ipv4_simulate_r5_machine_going_up(): + """ + On R5, we unshutdown the interface + Check that R5 is re-selected + Check that the number of zclient messages has not been multiplied per 2 + """ + global nhg_id + global route_count + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Unshutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + interface {ifname}\n + no shutdown\n + """, + isjson=False, + ) + + logger.info("Check that routes from R5 are back again") + check_ipv4_prefix_with_multiple_nexthops("172.16.5.150/32", r8_path=True) + + step("Check that 172.16.5.150/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.5.150", nhg_id=local_nhg_id, r8_path=True + ) + + step("Check that other NHG is used by 172.16.5.150/32 unicast routes") + assert ( + local_nhg_id != nhg_id + ), "The same NHG %d is used after R5 recovers. The NHG_ID should be different" % ( + local_nhg_id + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.200", r8_path=True + ) + step("Check that 172.22.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=local_nhg_id, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.200", r8_path=True + ) + step("Check that 172.18.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r8_path=True + ) + + step("Check that the number of route ADD messages between BGP and ZEBRA did move") + local_route_count_add = _get_bgp_route_count(tgen.net["r1"]) + logger.info( + f"Get the route count messages between BGP and ZEBRA: {local_route_count_add}" + ) + assert route_count != local_route_count_add, ( + "The number of route messages should have increased after r5 machine goes up : %d" + % (local_route_count_add) + ) + route_count = local_route_count_add + + +def test_bgp_ipv4_unpeering_with_r5(): + """ + On R5, we unconfigure R3 peering + Check that, on R1, routes from R5 are removed + """ + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("R5, unpeer with R3") + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + router bgp 64500\n + no neighbor 192.0.2.3 peer-group rrserver\n + """, + isjson=False, + ) + + logger.info("Check that routes from R5 are removed") + check_ipv4_prefix_with_multiple_nexthops( + "172.16.5.150/32", r5_path=False, r6_path=True, r8_path=True + ) + + step("Check that 172.16.5.150/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.5.150", nhg_id=local_nhg_id, r5_path=False, r6_path=True, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.200", r5_path=False, r6_path=True, r8_path=True + ) + step("Check that 172.18.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r5_path=False, r6_path=True, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.200", r5_path=False, r6_path=True, r8_path=True + ) + step("Check that 172.22.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=local_nhg_id, r5_path=False, r6_path=True, r8_path=True + ) + + # debug + tgen.gears["r1"].vtysh_cmd("show zebra client") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop-group") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop") + + +def test_bgp_ipv4_direct_peering_with_r5(): + """ + On R5, we configure a peering with R1 + On R1, we configure a peering with R5 + Check that routes from R5 are re-added + """ + global nhg_id, nhg_id_18, nhg_id_22 + global route_count + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("R4, disable IS-IS bfd") + tgen.gears["r4"].vtysh_cmd( + """ + configure terminal\n + interface r4-eth2\n + no isis bfd\n + """, + isjson=False, + ) + logger.info("R3, disable IS-IS bfd") + tgen.gears["r3"].vtysh_cmd( + """ + configure terminal\n + interface r3-eth1\n + no isis bfd\n + """, + isjson=False, + ) + logger.info("R5, disable IS-IS bfd") + tgen.gears["r5"].vtysh_cmd( + """ + configure terminal\n + interface r5-eth1\n + no isis bfd\n + """, + isjson=False, + ) + logger.info("R5, peer with R1") + tgen.gears["r5"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + no neighbor 192.0.2.3 peer-group rrserver + neighbor 192.0.2.1 peer-group rrserver\n", + neighbor 192.0.2.1 bfd\n + neighbor 192.0.2.1 bfd check-control-plane-failure\n + address-family ipv4 unicast\n + neighbor 192.0.2.1 addpath-tx-all-paths\n + """, + isjson=False, + ) + + logger.info("R1, peer with R5") + tgen.gears["r1"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + neighbor 192.0.2.5 peer-group rrserver\n + neighbor 192.0.2.5 bfd\n + neighbor 192.0.2.5 bfd check-control-plane-failure\n + """, + isjson=False, + ) + + logger.info("Check that routes from R5 are readded") + check_ipv4_prefix_with_multiple_nexthops("172.16.5.150/32", r8_path=True) + + step("Check that 172.16.5.150/32 unicast entry has 1 BGP NHG") + nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.5.150", nhg_id=nhg_id, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.200", r8_path=True + ) + step("Check that 172.18.1.100/32 unicast entry has 1 BGP NHG") + nhg_id_18 = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=nhg_id, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.200", r8_path=True + ) + step("Check that 172.22.1.100/32 unicast entry has 1 BGP NHG") + nhg_id_22 = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=nhg_id, r8_path=True + ) + + route_count = _get_bgp_route_count(tgen.net["r1"]) + logger.info(f"Get the route count messages between BGP and ZEBRA: {route_count}") + + +def test_bgp_ipv4_simulate_r5_peering_going_down(): + """ + On R5, we shutdown the interface + Check that R8 is selected + Check that R5 failure did not change the NHG (EDGE implementation needed) + """ + global nhg_id, nhg_id_18, nhg_id_22 + global route_count + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # debug + tgen.gears["r1"].vtysh_cmd("show zebra client") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop-group") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop") + + logger.info("Shutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + interface {ifname}\n + shutdown\n + """, + isjson=False, + ) + check_ipv4_prefix_with_multiple_nexthops( + "172.16.5.150/32", r5_path=False, r6_path=True, r8_path=True + ) + + step("Check that 172.16.5.150/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.5.150", nhg_id=local_nhg_id, r5_path=False, r6_path=True, r8_path=True + ) + + step("Check that same NHG is used by 172.16.5.150/32 unicast routes") + assert local_nhg_id == nhg_id, ( + "The same NHG %d is not used after R5 shutdown, EDGE implementation missing" + % nhg_id + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.200", r5_path=False, r6_path=True, r8_path=True + ) + + step("Check that 172.18.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r5_path=False, r6_path=True, r8_path=True + ) + + recursive_nh_changed = False + step("Check that same NHG is used by 172.18.1.100/32 unicast routes") + assert ( + local_nhg_id == nhg_id_18 + ), "BFD event, recursive nexthop 172.18.1.100/32, NHG_ID changed : %d -> %d" % ( + nhg_id_18, + local_nhg_id, + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.200", r5_path=False, r6_path=True, r8_path=True + ) + + step("Check that 172.22.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=local_nhg_id, r5_path=False, r6_path=True, r8_path=True + ) + + step("Check that same NHG is used by 172.22.1.100/32 unicast routes") + assert ( + local_nhg_id == nhg_id_22 + ), "BFD event, recursive nexthop 172.22.1.100/32, NHG_ID changed : %d -> %d" % ( + nhg_id_22, + local_nhg_id, + ) + + step( + "Check that the number of route ADD messages between BGP and ZEBRA did not move" + ) + local_route_count_add = _get_bgp_route_count(tgen.net["r1"]) + logger.info(f"Number of route messages ADD: {local_route_count_add}") + assert route_count == local_route_count_add, ( + "The number of route messages increased when r5 machine goes down : %d, expected %d" + % (local_route_count_add, route_count) + ) + + step("Check that the number of route DEL messages between BGP and ZEBRA is zero") + local_route_count_del = _get_bgp_route_count(tgen.net["r1"], add_routes=False) + logger.info( + f"Get the route DELETE count messages between BGP and ZEBRA: {local_route_count_del}" + ) + assert local_route_count_del == 0, ( + "The number of route messages delete increased when r5 machine goes down : %d, expected 0" + % local_route_count_del + ) + + # debug + tgen.gears["r1"].vtysh_cmd("show zebra client") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop-group") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop") + + +def test_bgp_ipv4_simulate_r5_peering_going_up_again(): + """ + On R5, we un-shutdown the interface + Check that R5 routes are re-added + """ + global nhg_id, nhg_id_18, nhg_id_22 + global route_count + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Un-Shutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f""" + configure terminal\n + interface {ifname}\n + no shutdown\n + """, + isjson=False, + ) + check_ipv4_prefix_with_multiple_nexthops( + "172.16.5.150/32", r5_path=True, r6_path=True, r8_path=True + ) + + step("Check that 172.16.5.150/32 unicast entry has 1 BGP NHG") + nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.5.150", nhg_id=nhg_id, r5_path=True, r6_path=True, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.200", r5_path=True, r6_path=True, r8_path=True + ) + step("Check that 172.18.1.100/32 unicast entry has 1 BGP NHG") + nhg_id_18 = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=nhg_id_18, r5_path=True, r6_path=True, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.200", r5_path=True, r6_path=True, r8_path=True + ) + step("Check that 172.22.1.100/32 unicast entry has 1 BGP NHG") + nhg_id_22 = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=nhg_id_22, r5_path=True, r6_path=True, r8_path=True + ) + + step("Check that the number of route ADD messages between BGP and ZEBRA did move") + local_route_count_add = _get_bgp_route_count(tgen.net["r1"]) + logger.info(f"Number of route messages ADD: {local_route_count_add}") + assert route_count != local_route_count_add, ( + "The number of route messages did not increase since r5 machine went up : %d" + % (route_count) + ) + route_count = local_route_count_add + + +def test_bgp_ipv4_lower_preference_value_on_r5_and_r8_configured(): + """ + On R5, and R8, we add a route-map to lower local-preference + Check that only R6 is selected + """ + global nhg_id, nhg_id_18, nhg_id_22 + global route_count + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Reconfigure R5 and R8 to lower the preference value of advertised unicast networks" + ) + for rname in ("r5", "r8"): + tgen.gears[rname].vtysh_cmd( + """ + configure terminal\n + route-map rmap permit 1\n + set local-preference 50\n + """, + isjson=False, + ) + tgen.gears[rname].vtysh_cmd( + f""" + configure terminal\n + router bgp 64500\n + address-family ipv4 unicast\n + redistribute sharp route-map rmap + """, + isjson=False, + ) + + step("Check that 172.16.5.150/32 unicast entry is installed with one endpoints") + check_ipv4_prefix_with_multiple_nexthops("172.16.5.150/32", r5_path=False) + + step("Check that 172.16.5.150/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.5.150", nhg_id=local_nhg_id, r5_path=False + ) + + step("Check that other NHG is used by 172.16.5.150/32 unicast routes") + assert local_nhg_id != nhg_id, ( + "The same NHG %d is used after R5 and R8 updates use a different preference value. The NHG_ID should be different" + % (local_nhg_id) + ) + + step("Check that 172.18.1.100/32 unicast entry is installed with one endpoints") + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", + "172.16.0.200", + r5_path=False, + ) + + step("Check that 172.18.1.100/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r5_path=False + ) + + step("Check that other NHG is used by 172.18.1.100/32 unicast routes") + assert local_nhg_id != nhg_id_18, ( + "The same NHG %d is used after R5 and R8 updates use a different preference value. The NHG_ID should be different" + % (local_nhg_id) + ) + + step("Check that 172.22.1.100/32 unicast entry is installed with one endpoints") + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", + "172.18.1.200", + r5_path=False, + ) + + step("Check that 172.22.1.100/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=local_nhg_id, r5_path=False + ) + + step("Check that other NHG is used by 172.22.0.100/32 unicast routes") + assert local_nhg_id != nhg_id_22, ( + "The same NHG %d is used after R5 and R8 updates use a different preference value. The NHG_ID should be different" + % (local_nhg_id) + ) + + step( + "Check that the number of route ADD messages between BGP and ZEBRA did not move" + ) + local_route_count_add = _get_bgp_route_count(tgen.net["r1"]) + logger.info( + f"Get the route count messages between BGP and ZEBRA: {local_route_count_add}" + ) + assert route_count != local_route_count_add, ( + "The number of route messages should have increased after r5 machine goes up : %d" + % (local_route_count_add) + ) + route_count = local_route_count_add + + +def test_bgp_ipv4_reset_preference_value(): + """ + On R5 and R8, we reset the route-map preference value + Check that R5 routes are re-added + """ + global nhg_id, nhg_id_18, nhg_id_22 + global route_count + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Reconfigure R5 and R8 to reset the preference value of advertised unicast networks" + ) + for rname in ("r5", "r8"): + tgen.gears[rname].vtysh_cmd( + """ + configure terminal\n + route-map rmap permit 1\n + no set local-preference 50\n + """, + isjson=False, + ) + + check_ipv4_prefix_with_multiple_nexthops( + "172.16.5.150/32", r5_path=True, r6_path=True, r8_path=True + ) + + step("Check that 172.16.5.150/32 unicast entry has 1 BGP NHG") + nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.5.150", nhg_id=nhg_id, r5_path=True, r6_path=True, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.200", r5_path=True, r6_path=True, r8_path=True + ) + step("Check that 172.18.1.100/32 unicast entry has 1 BGP NHG") + nhg_id_18 = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=nhg_id_18, r5_path=True, r6_path=True, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.200", r5_path=True, r6_path=True, r8_path=True + ) + step("Check that 172.22.1.100/32 unicast entry has 1 BGP NHG") + nhg_id_22 = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=nhg_id_22, r5_path=True, r6_path=True, r8_path=True + ) + + step("Check that the number of route ADD messages between BGP and ZEBRA did move") + local_route_count_add = _get_bgp_route_count(tgen.net["r1"]) + logger.info(f"Number of route messages ADD: {local_route_count_add}") + assert ( + route_count != local_route_count_add + ), "The number of route messages did not increas since r5 machine went up : %d" % ( + route_count + ) + route_count = local_route_count_add + + +def test_bgp_ipv4_change_igp_on_r8_removed(): + """ + On R8, we remove the lo interface from the IGP + Consequently, BGP NHT will tell the path to R8 lo interface is invalid + Check that only R5, and R6 are selected, and use the same NHG_ID + """ + global nhg_id, nhg_id_18, nhg_id_22 + global route_count + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Reconfigure R3 to remove BGP BFD with R8") + tgen.gears["r3"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500 view one\n + no neighbor 192.0.2.8 bfd check-control-plane-failure\n + no neighbor 192.0.2.8 bfd\n + """, + isjson=False, + ) + step("Reconfigure R8 to remove BGP BFD with R3") + tgen.gears["r8"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + no neighbor rrserver bfd\n + """, + isjson=False, + ) + + check_ipv4_prefix_with_multiple_nexthops( + "172.16.5.150/32", r5_path=True, r6_path=True, r8_path=True + ) + nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.200", r5_path=True, r6_path=True, r8_path=True + ) + nhg_id_18 = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.200", r5_path=True, r6_path=True, r8_path=True + ) + nhg_id_22 = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + step("Get the number of route ADD messages between BGP and ZEBRA") + route_count = _get_bgp_route_count(tgen.net["r1"]) + + step("Reconfigure R8 to remove the lo interface from the IGP") + tgen.gears["r8"].vtysh_cmd( + """ + configure terminal\n + no router isis 1\n + """, + isjson=False, + ) + + check_ipv4_prefix_with_multiple_nexthops( + "172.16.5.150/32", r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that 172.16.5.150/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.5.150", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that same NHG is used by 172.16.5.150/32 unicast routes") + assert local_nhg_id == nhg_id, ( + "A different NHG %d is used after IGP on R7 changed. The NHG_ID should be same (expected %d)" + % (local_nhg_id, nhg_id) + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.200", r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that 172.18.1.100/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that same NHG is used by 172.18.1.100/32 unicast routes") + assert local_nhg_id == nhg_id_18, ( + "A different NHG %d is used after IGP on R7 changed. The NHG_ID should be same (expected %d)" + % (local_nhg_id, nhg_id_18) + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.200", r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that 172.22.1.100/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that same NHG is used by 172.22.1.100/32 unicast routes") + assert local_nhg_id == nhg_id_22, ( + "A different NHG %d is used after IGP on R7 changed. The NHG_ID should be same (expected %d)" + % (local_nhg_id, nhg_id_22) + ) + + step( + "Check that the number of route ADD messages between BGP and ZEBRA did not move" + ) + local_route_count_add = _get_bgp_route_count(tgen.net["r1"]) + logger.info( + f"Get the route count messages between BGP and ZEBRA: {local_route_count_add}" + ) + assert route_count == local_route_count_add, ( + "The number of route messages should have not moved after r8 IGP metric changed: expected %d" + % (route_count) + ) + route_count = local_route_count_add + + +def test_bgp_ipv4_change_igp_on_r8_readded(): + """ + On R8, we restore the lo interface in the IGP + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Readd the IGP to loopback interface of R8") + conf_file = os.path.join(CWD, "r8/isisd.conf") + tgen.net["r8"].cmd("vtysh -f {}".format(conf_file)) + + check_ipv4_prefix_with_multiple_nexthops( + "172.16.5.150/32", r5_path=True, r6_path=True, r8_path=True + ) + + step("Check that 172.16.5.150/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.16.5.150/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.5.150", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.200", r5_path=True, r6_path=True, r8_path=True + ) + step("Check that 172.18.1.100/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=False + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.200", r5_path=True, r6_path=True, r8_path=True + ) + step("Check that 172.22.1.100/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=False + ) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/__init__.py b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r1/bgpd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r1/bgpd.conf new file mode 100644 index 000000000000..2e948dc37c6b --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r1/bgpd.conf @@ -0,0 +1,26 @@ +debug bgp bfd +debug bgp nexthop-group detail +debug bgp zebra +debug bgp nht +bgp suppress-fib-pending +route-map rmap permit 1 + set as-path exclude all +exit +router bgp 64500 + no bgp ebgp-requires-policy + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + neighbor 192.0.2.3 remote-as 64501 + neighbor 192.0.2.3 disable-connected-check + neighbor 192.0.2.3 update-source lo + neighbor 192.0.2.3 timers connect 2 + neighbor 192.0.2.3 peer-group rrserver + neighbor 192.0.2.3 bfd + no neighbor 192.0.2.3 enforce-first-as + address-family ipv4 unicast + neighbor 192.0.2.3 next-hop-self + neighbor 192.0.2.3 allowas-in + neighbor 192.0.2.3 route-map rmap in + neighbor 192.0.2.3 activate + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r1/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r1/isisd.conf new file mode 100644 index 000000000000..9660577f4e8f --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r1/isisd.conf @@ -0,0 +1,26 @@ +hostname r1 +interface lo + ip router isis 1 + isis passive +! +interface r1-eth1 + ip router isis 1 + isis network point-to-point +! +interface r1-eth2 + ip router isis 1 + isis network point-to-point +! +interface r1-eth4 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0001.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.1/32 index 1 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r1/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r1/zebra.conf new file mode 100644 index 000000000000..2e3549a57401 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r1/zebra.conf @@ -0,0 +1,24 @@ +log stdout +interface lo + ip address 192.0.2.1/32 +! +interface r1-eth0 + ip address 172.31.10.1/24 +! +interface r1-eth1 + ip address 172.31.0.1/24 + mpls enable +! +interface r1-eth2 + ip address 172.31.2.1/24 + mpls enable +! +interface r1-eth3 + ip address 172.31.11.1/24 + mpls enable +! +interface r1-eth4 + ip address 172.31.8.1/24 + mpls enable +! + diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r3/bgpd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r3/bgpd.conf new file mode 100644 index 000000000000..498dddefdfb0 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r3/bgpd.conf @@ -0,0 +1,32 @@ +router bgp 64501 view one + no bgp ebgp-requires-policy + bgp router-id 192.0.2.3 + neighbor rr peer-group + neighbor rr remote-as 64500 + neighbor rr disable-connected-check + neighbor rr update-source lo + neighbor 192.0.2.1 peer-group rr + neighbor 192.0.2.6 peer-group rr + neighbor 192.0.2.8 peer-group rr + neighbor 192.0.2.1 bfd check-control-plane-failure + neighbor 192.0.2.6 bfd check-control-plane-failure + neighbor 192.0.2.8 bfd check-control-plane-failure + neighbor 192.0.2.8 ebgp-multihop + neighbor 192.0.2.5 remote-as 64502 + neighbor 192.0.2.5 disable-connected-check + neighbor 192.0.2.5 update-source lo + ! + address-family ipv4 unicast + neighbor rr activate + neighbor rr route-server-client + neighbor rr addpath-tx-all-paths + neighbor 192.0.2.1 attribute-unchanged next-hop + neighbor 192.0.2.5 attribute-unchanged next-hop + neighbor 192.0.2.6 attribute-unchanged next-hop + neighbor 192.0.2.8 attribute-unchanged next-hop + neighbor 192.0.2.5 activate + neighbor 192.0.2.5 route-server-client + neighbor 192.0.2.5 addpath-tx-all-paths + neighbor 192.0.2.5 attribute-unchanged next-hop + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r3/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r3/isisd.conf new file mode 100644 index 000000000000..a11cd06eb57d --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r3/isisd.conf @@ -0,0 +1,39 @@ +hostname r3 +interface lo + ip router isis 1 + isis passive +! +interface r3-eth0 + ip router isis 1 + isis network point-to-point +! +interface r3-eth1 + ip router isis 1 + isis network point-to-point + isis bfd +! +interface r3-eth2 + ip router isis 1 + isis network point-to-point +! +interface r3-eth3 + ip router isis 1 + isis network point-to-point +! +interface r3-eth4 + ip router isis 1 + isis network point-to-point +! +interface r3-eth5 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0003.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.3/32 index 3 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r3/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r3/zebra.conf new file mode 100644 index 000000000000..05b3769fb8bf --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r3/zebra.conf @@ -0,0 +1,16 @@ +log stdout +interface lo + ip address 192.0.2.3/32 +! +interface r3-eth0 + ip address 172.31.0.3/24 + mpls enable +! +interface r3-eth1 + ip address 172.31.4.3/24 + mpls enable +! +interface r3-eth2 + ip address 172.31.5.3/24 + mpls enable +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r4/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r4/isisd.conf new file mode 100644 index 000000000000..c33fbd08d34b --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r4/isisd.conf @@ -0,0 +1,31 @@ +hostname r4 +interface lo + ip router isis 1 + isis passive +! +interface r4-eth0 + ip router isis 1 + isis network point-to-point +! +interface r4-eth1 + ip router isis 1 + isis network point-to-point +! +interface r4-eth2 + ip router isis 1 + isis network point-to-point + isis bfd +! +interface r4-eth3 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0004.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.4/32 index 4 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r4/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r4/zebra.conf new file mode 100644 index 000000000000..9ea1b7ec4314 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r4/zebra.conf @@ -0,0 +1,20 @@ +log stdout +interface lo + ip address 192.0.2.4/32 +! +interface r4-eth0 + ip address 172.31.2.4/24 + mpls enable +! +interface r4-eth1 + ip address 172.31.6.4/24 + mpls enable +! +interface r4-eth2 + ip address 172.31.7.4/24 + mpls enable +! +interface r4-eth3 + mpls enable +! + diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r5/bgpd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r5/bgpd.conf new file mode 100644 index 000000000000..cf29ba8d3bdc --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r5/bgpd.conf @@ -0,0 +1,17 @@ +router bgp 64502 + bgp router-id 192.192.192.192 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor rrserver peer-group + neighbor rrserver remote-as 64501 + neighbor rrserver disable-connected-check + neighbor rrserver update-source lo + neighbor rrserver timers connect 2 + neighbor 192.0.2.3 peer-group rrserver + address-family ipv4 unicast + network 192.0.2.9/32 + neighbor rrserver activate + neighbor rrserver attribute-unchanged next-hop + redistribute sharp + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r5/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r5/isisd.conf new file mode 100644 index 000000000000..b968ce11db88 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r5/isisd.conf @@ -0,0 +1,27 @@ +hostname r5 +interface lo + ip router isis 1 + isis passive +! +interface r5-eth1 + ip router isis 1 + isis network point-to-point + isis bfd +! +interface r5-eth2 + ip router isis 1 + isis network point-to-point +! +interface r5-eth3 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0005.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.5/32 index 55 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r5/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r5/zebra.conf new file mode 100644 index 000000000000..6f326561e733 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r5/zebra.conf @@ -0,0 +1,19 @@ +log stdout +mpls label dynamic-block 5000 5999 +interface lo + ip address 192.0.2.5/32 +! +interface r5-eth0 + ip address 172.31.12.5/24 +! +interface r5-eth1 + ip address 172.31.4.5/24 + mpls enable +! +interface r5-eth2 + ip address 172.31.7.5/24 + mpls enable +! +interface r5-eth3 + ip address 172.31.21.5/24 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/bgpd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/bgpd.conf new file mode 100644 index 000000000000..83d5e5e43a5a --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/bgpd.conf @@ -0,0 +1,17 @@ +router bgp 64500 + bgp router-id 192.192.192.192 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor rrserver peer-group + neighbor rrserver remote-as 64501 + neighbor rrserver disable-connected-check + neighbor rrserver update-source lo + neighbor rrserver bfd + neighbor 192.0.2.3 peer-group rrserver + address-family ipv4 unicast + network 192.0.2.9/32 + neighbor rrserver activate + neighbor rrserver attribute-unchanged next-hop + redistribute sharp + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/isisd.conf new file mode 100644 index 000000000000..5126a6485846 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/isisd.conf @@ -0,0 +1,22 @@ +hostname r6 +interface lo + ip router isis 1 + isis passive +! +interface r6-eth1 + ip router isis 1 + isis network point-to-point +! +interface r6-eth2 + ip router isis 1 + isis network point-to-point +! +router isis 1 + net 49.0123.6452.0006.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.6/32 index 6 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/sharpd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/sharpd.conf new file mode 100644 index 000000000000..9d9a6e2b0678 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/sharpd.conf @@ -0,0 +1 @@ +sharp install routes 193.0.0.1 nexthop 192.0.2.6 2000 diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/zebra.conf new file mode 100644 index 000000000000..cda62d7e87a0 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r6/zebra.conf @@ -0,0 +1,20 @@ +log stdout +mpls label dynamic-block 6000 6999 +interface lo + ip address 192.0.2.6/32 +! +interface r6-eth0 + ip address 172.31.13.6/24 +! +interface r6-eth1 + ip address 172.31.5.6/24 + mpls enable +! +interface r6-eth2 + ip address 172.31.6.6/24 + mpls enable +! +interface r6-eth3 + ip address 172.31.22.6/24 +! + diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r7/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r7/isisd.conf new file mode 100644 index 000000000000..b6e78749d4fd --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r7/isisd.conf @@ -0,0 +1,23 @@ +hostname r7 +interface lo + ip router isis 1 + isis passive +! +interface r7-eth0 + ip router isis 1 + isis network point-to-point +! +interface r7-eth1 + ip router isis 1 + isis network point-to-point + isis bfd +! +router isis 1 + net 49.0123.6452.0007.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.7/32 index 7 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r7/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r7/zebra.conf new file mode 100644 index 000000000000..281922a6cd4b --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r7/zebra.conf @@ -0,0 +1,14 @@ +log stdout +interface lo + ip address 192.0.2.7/32 +! +interface r7-eth0 + ip address 172.31.8.7/24 + mpls enable +! shutdown +! +interface r7-eth1 + ip address 172.31.9.7/24 + mpls enable +! + diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/bgpd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/bgpd.conf new file mode 100644 index 000000000000..0ef18e2d98a7 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 64500 + bgp router-id 192.192.192.192 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor rrserver peer-group + neighbor rrserver remote-as 64501 + neighbor rrserver disable-connected-check + neighbor rrserver update-source lo + neighbor rrserver ebgp-multihop + neighbor rrserver bfd + neighbor 192.0.2.3 peer-group rrserver + address-family ipv4 unicast + network 192.0.2.9/32 + neighbor rrserver activate + neighbor rrserver attribute-unchanged next-hop + redistribute sharp + exit-address-family +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/isisd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/isisd.conf new file mode 100644 index 000000000000..8f62ff5fc9a4 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/isisd.conf @@ -0,0 +1,20 @@ +hostname r8 +interface lo + ip router isis 1 + isis passive + isis metric 10 +! +interface r8-eth0 + ip router isis 1 + isis network point-to-point + isis bfd +! +router isis 1 + net 49.0123.6452.0008.00 + is-type level-2-only + mpls-te on + segment-routing on + segment-routing global-block 16000 17000 + segment-routing node-msd 10 + segment-routing prefix 192.0.2.8/32 index 8 +! diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/sharpd.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/sharpd.conf new file mode 100644 index 000000000000..ebd81338e9a8 --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/sharpd.conf @@ -0,0 +1 @@ +sharp install routes 193.0.0.1 nexthop 192.0.2.8 2000 diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/zebra.conf b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/zebra.conf new file mode 100644 index 000000000000..b94a6ba0b37a --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/r8/zebra.conf @@ -0,0 +1,16 @@ +log stdout +mpls label dynamic-block 6000 6999 +interface lo + ip address 192.0.2.8/32 +! +interface r8-eth0 + ip address 172.31.9.8/24 + mpls enable +! +interface r8-eth1 + ip address 172.31.14.8/24 +! +interface r8-eth2 + ip address 172.31.23.8/24 +! + diff --git a/tests/topotests/bgp_nhg_zapi_scalability_ebgp/test_bgp_nhg_zapi_scalability_ebgp.py b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/test_bgp_nhg_zapi_scalability_ebgp.py new file mode 100644 index 000000000000..047ac5d9ec9d --- /dev/null +++ b/tests/topotests/bgp_nhg_zapi_scalability_ebgp/test_bgp_nhg_zapi_scalability_ebgp.py @@ -0,0 +1,963 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_nhg_zapi_scalability_ebgp.py +# +# Copyright 2024 6WIND S.A. +# + +""" + test_bgp_nhg_zapi_scalability_ebgp.py: + Check that the FRR BGP daemon reduces the number of route_add messages + by using bgp nexthop group facility. + + ++---+----+ +---+----+ +--------+ +| | | + | | +| r1 +----------+ r3 +----------+ r5 + +| | | rs + +-----+ | ++++-+----+ +--------+\ / +--------+ + | | \/ + | | /\ + | | +--------+/ \ +--------+ + | | | + +-----+ + + | +---------------+ r4 +----------+ r6 + + | | | | | + | +--------+ +--------+ + | + | +--------+ +--------+ + | | | | + + +-----------------+ r7 +----------+ r8 + + | | | | + +--------+ +--------+ +""" + +import os +import sys +import json +from functools import partial +import pytest +import functools + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.common_check import ip_check_path_selection, iproute2_check_path_selection +from lib.common_config import step +from lib.nexthopgroup import route_check_nhg_id_is_protocol +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + +nhg_id = 0 +nhg_id_18 = 0 +nhg_id_22 = 0 +route_count = 0 +route_exact_number = 7 + + +def build_topo(tgen): + "Build function" + + # Create 7 PE routers. + tgen.add_router("r1") + tgen.add_router("r3") + tgen.add_router("r4") + tgen.add_router("r5") + tgen.add_router("r6") + tgen.add_router("r7") + tgen.add_router("r8") + + # switch + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s7") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s8") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s9") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s10") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s11") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s12") + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s13") + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s14") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s15") + switch.add_link(tgen.gears["r7"]) + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s16") + switch.add_link(tgen.gears["r7"]) + switch.add_link(tgen.gears["r8"]) + + switch = tgen.add_switch("s17") + switch.add_link(tgen.gears["r8"]) + + switch = tgen.add_switch("s18") + switch.add_link(tgen.gears["r8"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list = [ + "ip link add loop2 type dummy", + "ip link set dev loop2 up", + ] + + cmds_list = [ + "modprobe mpls_router", + "echo 100000 > /proc/sys/net/mpls/platform_labels", + ] + + for name in ("r1", "r3", "r4", "r5", "r6", "r7", "r8"): + for cmd in cmds_list: + logger.info("input: " + cmd) + output = tgen.net[name].cmd(cmd) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + _populate_iface() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + if rname in ("r1", "r3", "r4", "r5", "r6", "r7", "r8"): + router.load_config( + TopoRouter.RD_ISIS, os.path.join(CWD, "{}/isisd.conf".format(rname)) + ) + if rname in ("r1", "r3", "r5", "r6", "r7", "r8"): + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + if rname in ("r1", "r3", "r5", "r6", "r8"): + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + if rname in ("r5", "r6", "r8"): + router.load_config( + TopoRouter.RD_SHARP, os.path.join(CWD, "{}/sharpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def ip_check_ibgp_prefix_count_in_rib(router, count): + output = json.loads(router.vtysh_cmd(f"show ip route summary json")) + for entry in output["routes"]: + if entry["type"] == "ebgp": + if count == int(entry["rib"]): + return None + return f'ibgp ipv4 route count differs from expected: {entry["rib"]}, expected {count}' + return f"ibgp ipv4 route count not found" + + +def check_ipv4_prefix_with_multiple_nexthops( + prefix, r5_path=True, r6_path=True, r8_path=False +): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info( + f"Check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path}" + ) + + r5_nh = [ + { + "ip": "192.0.2.5", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16055, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16055, + ], + }, + ] + + r6_nh = [ + { + "ip": "192.0.2.6", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16006, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16006, + ], + }, + ] + + r8_nh = [ + { + "ip": "192.0.2.8", + "active": True, + "recursive": True, + }, + { + "ip": "172.31.8.7", + "interfaceName": "r1-eth4", + "active": True, + "labels": [ + 16008, + ], + }, + ] + + expected = { + prefix: [ + { + "prefix": prefix, + "protocol": "bgp", + "metric": 0, + "table": 254, + "nexthops": [], + } + ] + } + if r5_path: + for nh in r5_nh: + expected[prefix][0]["nexthops"].append(nh) + if r6_path: + for nh in r6_nh: + expected[prefix][0]["nexthops"].append(nh) + if r8_path: + for nh in r8_nh: + expected[prefix][0]["nexthops"].append(nh) + + test_func = functools.partial( + ip_check_path_selection, tgen.gears["r1"], prefix, expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, f"Failed to check that {prefix} uses the IGP label 16055" + + +def check_ipv4_prefix_recursive_with_multiple_nexthops( + prefix, recursive_nexthop, r5_path=True, r6_path=True, r8_path=False +): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info( + f"Check that {prefix} unicast entry is correctly recursive via {recursive_nexthop} with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path}" + ) + + r5_nh = [ + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16055, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16055, + ], + }, + ] + + r6_nh = [ + { + "ip": "172.31.0.3", + "interfaceName": "r1-eth1", + "active": True, + "labels": [ + 16006, + ], + }, + { + "ip": "172.31.2.4", + "interfaceName": "r1-eth2", + "active": True, + "labels": [ + 16006, + ], + }, + ] + + r8_nh = [ + { + "ip": "172.31.8.7", + "interfaceName": "r1-eth4", + "active": True, + "labels": [ + 16008, + ], + }, + ] + + expected = { + prefix: [ + { + "prefix": prefix, + "protocol": "bgp", + "metric": 0, + "table": 254, + "nexthops": [], + } + ] + } + + recursive_nh = [ + { + "ip": recursive_nexthop, + "active": True, + "recursive": True, + }, + ] + for nh in recursive_nh: + expected[prefix][0]["nexthops"].append(nh) + + if r5_path: + for nh in r5_nh: + expected[prefix][0]["nexthops"].append(nh) + if r6_path: + for nh in r6_nh: + expected[prefix][0]["nexthops"].append(nh) + if r8_path: + for nh in r8_nh: + expected[prefix][0]["nexthops"].append(nh) + + test_func = functools.partial( + ip_check_path_selection, + tgen.gears["r1"], + prefix, + expected, + ignore_duplicate_nh=True, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), f"Failed to check that {prefix} is correctly recursive via {recursive_nexthop}" + + +def check_ipv4_prefix_with_multiple_nexthops_linux( + prefix, nhg_id, r5_path=True, r6_path=True, r8_path=False +): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + f"Check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path} with NHID {nhg_id} on Linux" + ) + + r5_nh = [ + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16055", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ] + + r6_nh = [ + { + "encap": "mpls", + "dst": "16006", + "gateway": "172.31.0.3", + "dev": "r1-eth1", + }, + { + "encap": "mpls", + "dst": "16006", + "gateway": "172.31.2.4", + "dev": "r1-eth2", + }, + ] + + r8_nh = [ + { + "encap": "mpls", + "dst": "16008", + "gateway": "172.31.8.7", + "dev": "r1-eth4", + } + ] + + expected_r8_nh_only = [ + { + "dst": prefix, + "protocol": "bgp", + "metric": 20, + "encap": "mpls", + "dst": "16008", + "gateway": "172.31.8.7", + "dev": "r1-eth4", + } + ] + expected = [ + { + "dst": prefix, + "protocol": "bgp", + "metric": 20, + "nexthops": [], + } + ] + + # only one path + if r8_path and not r5_path and not r6_path: + expected = expected_r8_nh_only + else: + if r5_path: + for nh in r5_nh: + expected[0]["nexthops"].append(nh) + if r6_path: + for nh in r6_nh: + expected[0]["nexthops"].append(nh) + if r8_path: + for nh in r8_nh: + expected[0]["nexthops"].append(nh) + + test_func = functools.partial( + iproute2_check_path_selection, + tgen.routers()["r1"], + prefix, + expected, + nhg_id=nhg_id, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), f"Failed to check that {prefix} unicast entry is installed with paths for r5 {r5_path}, r6 {r6_path}, r8 {r8_path} on Linux with BGP ID" + + +def _get_bgp_route_count(router, add_routes=True): + """ + Dump 'show zebra client' for BGP and extract the number of ipv4 route add messages + if add_routes is not True, then it returns the number of ipv4 route_delete messages + """ + if add_routes: + return int( + router.cmd( + "vtysh -c 'show zebra client' | grep -e 'Client: bgp$' -A 40 | grep IPv4 | awk -F ' ' '{print $2}' | awk -F\, '$1 > 6'" + ) + ) + # IS-IS may have counter to update, lets filter only on BGP clients + return int( + router.cmd( + "vtysh -c 'show zebra client' | grep -e 'Client: bgp$' -A 40 | grep IPv4 | awk -F ' ' '{print $4}' | uniq" + ) + ) + + +def test_bgp_ipv4_convergence(): + """ + Check that R1 has received the 192.0.2.9/32 prefix from R5, and R8 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Ensure that the 192.0.2.9/32 route is available") + check_ipv4_prefix_with_multiple_nexthops("192.0.2.9/32", r8_path=True) + + step("Check that 192.0.2.9/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("192.0.2.9/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux("192.0.2.9", nhg_id=local_nhg_id) + + +def test_bgp_ipv4_multiple_routes(): + """ + Configure 2000 routes on R5, R6, and R8, and redistribute routes in BGP. + Check that R1 has received 2 of those routesprefix from R5, and R8 + Check that the number of RIB routes in ZEBRA is 2001 + """ + global nhg_id, nhg_id_18, nhg_id_22 + global route_count, route_exact_number + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r5"].vtysh_cmd( + "sharp install routes 172.16.0.100 nexthop 192.0.2.5 2\n" + ) + tgen.gears["r6"].vtysh_cmd( + "sharp install routes 172.16.0.100 nexthop 192.0.2.6 2\n" + ) + tgen.gears["r8"].vtysh_cmd( + "sharp install routes 172.16.0.100 nexthop 192.0.2.8 2\n" + ) + tgen.gears["r5"].vtysh_cmd( + "sharp install routes 172.18.1.100 nexthop 172.16.0.100 2\n" + ) + tgen.gears["r6"].vtysh_cmd( + "sharp install routes 172.18.1.100 nexthop 172.16.0.100 2\n" + ) + tgen.gears["r8"].vtysh_cmd( + "sharp install routes 172.18.1.100 nexthop 172.16.0.100 2\n" + ) + tgen.gears["r5"].vtysh_cmd( + "sharp install routes 172.22.1.100 nexthop 172.18.1.100 2\n" + ) + tgen.gears["r6"].vtysh_cmd( + "sharp install routes 172.22.1.100 nexthop 172.18.1.100 2\n" + ) + tgen.gears["r8"].vtysh_cmd( + "sharp install routes 172.22.1.100 nexthop 172.18.1.100 2\n" + ) + + check_ipv4_prefix_with_multiple_nexthops("172.16.0.101/32", r8_path=True) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.100", r8_path=True + ) + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.100", r8_path=True + ) + + step("Check that 192.0.2.9/32 unicast entry has 1 BGP NHG") + nhg_id = route_check_nhg_id_is_protocol("172.16.0.101/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux("172.16.0.101", nhg_id=nhg_id) + + step("Check that 172.18.1.100/32 unicast entry has 1 BGP NHG") + nhg_id_18 = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux("172.18.1.100/32", nhg_id=nhg_id_18) + + step("Check that 172.22.1.100/32 unicast entry has 1 BGP NHG") + nhg_id_22 = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux("172.22.1.100/32", nhg_id=nhg_id_22) + + step(f"Check that the ipv4 zebra RIB count reaches {route_exact_number}") + test_func = functools.partial( + ip_check_ibgp_prefix_count_in_rib, tgen.gears["r1"], route_exact_number + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), f"Failed to check that the ipv4 zebra RIB count reaches {route_exact_number} :{result}" + + +def test_bgp_ipv4_simulate_r5_machine_going_down(): + """ + On R5, we shutdown the interface + Check that only R8 is selected + Check that R5 failure did not change the NHG (EDGE implementation needed) + Check that the number of zclient messages did not move + """ + global nhg_id, nhg_id_18, nhg_id_22 + global route_count + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # debug + tgen.gears["r1"].vtysh_cmd("show zebra client") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop-group") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop") + + # take the reference number of zebra route add/update messages + local_route_count_add = _get_bgp_route_count(tgen.net["r1"]) + logger.info( + f"Number of route messages between BGP and ZEBRA, before shutdown: {local_route_count_add}" + ) + + step("Shutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f"configure terminal\ninterface {ifname}\nshutdown\n", + isjson=False, + ) + check_ipv4_prefix_with_multiple_nexthops( + "172.16.0.101/32", r5_path=False, r8_path=True + ) + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.100", r5_path=False, r8_path=True + ) + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.100", r5_path=False, r8_path=True + ) + + step("Check that 172.16.0.101/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.16.0.101/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.0.101", nhg_id=local_nhg_id, r5_path=False, r8_path=True + ) + + step("Check that other NHG is used by 172.16.0.101/32 unicast routes") + assert local_nhg_id == nhg_id, ( + "The same NHG %d is not used after R5 shutdown, EDGE implementation missing" + % nhg_id + ) + + step("Check that 172.18.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r5_path=False, r8_path=True + ) + step("Check that other NHG is used by 172.18.1.100/32 unicast routes") + assert local_nhg_id == nhg_id_18, ( + "The same NHG %d is not used after R5 shutdown, EDGE implementation missing" + % nhg_id_18 + ) + + step("Check that 172.22.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=local_nhg_id, r5_path=False, r8_path=True + ) + step("Check that other NHG is used by 172.22.1.150/32 unicast routes") + assert local_nhg_id == nhg_id_22, ( + "The same NHG %d is not used after R5 shutdown, EDGE implementation missing" + % nhg_id_22 + ) + + step( + "Check that the number of route ADD messages between BGP and ZEBRA did not move" + ) + route_count = _get_bgp_route_count(tgen.net["r1"]) + + logger.info(f"Number of route messages ADD: {route_count}") + assert route_count == local_route_count_add, ( + "The number of route messages increased when r5 machine goes down : %d, expected %d" + % (route_count, local_route_count_add) + ) + + step("Check that the number of route DEL messages between BGP and ZEBRA is zero") + local_route_count_del = _get_bgp_route_count(tgen.net["r1"], add_routes=False) + + logger.info(f"Get the route count messages between BGP and ZEBRA: {route_count}") + assert local_route_count_del == 0, ( + "The number of route messages delete increased when r5 machine goes down : %d, expected 0" + % local_route_count_del + ) + # debug + tgen.gears["r1"].vtysh_cmd("show zebra client") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop-group") + tgen.gears["r1"].vtysh_cmd("show bgp nexthop") + + +def test_bgp_ipv4_simulate_r5_machine_going_up(): + """ + On R5, we unshutdown the interface + Check that R5 is re-selected + Check that the number of zclient messages has not been multiplied per 2 + """ + global nhg_id + global route_count + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Unshutdown R5 interface") + for ifname in ("r5-eth1", "r5-eth2"): + tgen.gears["r5"].vtysh_cmd( + f"configure terminal\ninterface {ifname}\nno shutdown\n", + isjson=False, + ) + + logger.info("Check that routes from R5 are back again") + check_ipv4_prefix_with_multiple_nexthops("172.16.0.101/32", r8_path=True) + + step("Check that 172.16.0.101/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.16.0.101/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.0.101", nhg_id=local_nhg_id, r8_path=True + ) + + step("Check that other NHG is used by 172.16.0.101/32 unicast routes") + assert ( + local_nhg_id != nhg_id + ), "The same NHG %d is used after R5 recovers. The NHG_ID should be different" % ( + local_nhg_id + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.100", r8_path=True + ) + step("Check that 172.22.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=local_nhg_id, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.100", r8_path=True + ) + step("Check that 172.18.1.100/32 unicast entry has 1 BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r8_path=True + ) + + step("Check that the number of route ADD messages between BGP and ZEBRA did move") + local_route_count_add = _get_bgp_route_count(tgen.net["r1"]) + logger.info( + f"Get the route count messages between BGP and ZEBRA: {local_route_count_add}" + ) + assert route_count != local_route_count_add, ( + "The number of route messages should have increased after r5 machine goes up : %d" + % (local_route_count_add) + ) + route_count = local_route_count_add + + +def test_bgp_ipv4_change_igp_on_r8_removed(): + """ + On R8, we remove the lo interface from the IGP + Consequently, BGP NHT will tell the path to R8 lo interface is invalid + Check that only R5, and R6 are selected, and use the same NHG_ID + """ + global nhg_id, nhg_id_18, nhg_id_22 + global route_count + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Reconfigure R3 to remove BGP BFD with R8") + tgen.gears["r3"].vtysh_cmd( + """ + configure terminal\n + router bgp 64501 view one\n + no neighbor 192.0.2.8 bfd check-control-plane-failure\n + no neighbor 192.0.2.8 bfd\n + """, + isjson=False, + ) + step("Reconfigure R8 to remove BGP BFD with R3") + tgen.gears["r8"].vtysh_cmd( + """ + configure terminal\n + router bgp 64500\n + no neighbor rrserver bfd\n + """, + isjson=False, + ) + + check_ipv4_prefix_with_multiple_nexthops( + "172.16.0.101/32", r5_path=True, r6_path=True, r8_path=True + ) + nhg_id = route_check_nhg_id_is_protocol("172.16.0.101/32", "r1") + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.100", r5_path=True, r6_path=True, r8_path=True + ) + nhg_id_18 = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.100", r5_path=True, r6_path=True, r8_path=True + ) + nhg_id_22 = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + step("Get the number of route ADD messages between BGP and ZEBRA") + route_count = _get_bgp_route_count(tgen.net["r1"]) + + step("Reconfigure R8 to remove the lo interface from the IGP") + tgen.gears["r8"].vtysh_cmd( + "configure terminal\nno router isis 1\n", + isjson=False, + ) + + check_ipv4_prefix_with_multiple_nexthops( + "172.16.0.101/32", r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that 172.16.0.101/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.16.0.101/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.0.101", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that same NHG is used by 172.16.0.101/32 unicast routes") + assert local_nhg_id == nhg_id, ( + "A different NHG %d is used after IGP on R7 changed. The NHG_ID should be same (expected %d)" + % (local_nhg_id, nhg_id) + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.100", r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that 172.18.1.100/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that same NHG is used by 172.18.1.100/32 unicast routes") + assert local_nhg_id == nhg_id_18, ( + "A different NHG %d is used after IGP on R7 changed. The NHG_ID should be same (expected %d)" + % (local_nhg_id, nhg_id_18) + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.100", r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that 172.22.1.100/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.22.1.100", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=False + ) + + step("Check that same NHG is used by 172.22.1.100/32 unicast routes") + assert local_nhg_id == nhg_id_22, ( + "A different NHG %d is used after IGP on R7 changed. The NHG_ID should be same (expected %d)" + % (local_nhg_id, nhg_id_22) + ) + + step( + "Check that the number of route ADD messages between BGP and ZEBRA did not move" + ) + local_route_count_add = _get_bgp_route_count(tgen.net["r1"]) + logger.info( + f"Get the route count messages between BGP and ZEBRA: {local_route_count_add}" + ) + assert route_count == local_route_count_add, ( + "The number of route messages should have not moved after r8 IGP metric changed: expected %d" + % (route_count) + ) + route_count = local_route_count_add + + +def test_bgp_ipv4_change_igp_on_r8_readded(): + """ + On R8, we restore the lo interface in the IGP + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Readd the IGP to loopback interface of R8") + conf_file = os.path.join(CWD, "r8/isisd.conf") + tgen.net["r8"].cmd("vtysh -f {}".format(conf_file)) + + check_ipv4_prefix_with_multiple_nexthops( + "172.16.0.101/32", r5_path=True, r6_path=True, r8_path=True + ) + + step("Check that 172.16.0.101/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.16.0.101/32", "r1") + + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.16.0.101", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=True + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.18.1.100/32", "172.16.0.100", r5_path=True, r6_path=True, r8_path=True + ) + step("Check that 172.18.1.100/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.18.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=False + ) + + check_ipv4_prefix_recursive_with_multiple_nexthops( + "172.22.1.100/32", "172.18.1.100", r5_path=True, r6_path=True, r8_path=True + ) + step("Check that 172.22.1.100/32 unicast entry uses a BGP NHG") + local_nhg_id = route_check_nhg_id_is_protocol("172.22.1.100/32", "r1") + check_ipv4_prefix_with_multiple_nexthops_linux( + "172.18.1.100", nhg_id=local_nhg_id, r5_path=True, r6_path=True, r8_path=False + ) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-ip-route.json b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-ip-route.json index 1bf38efcc54c..946333399571 100644 --- a/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-ip-route.json +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-ip-route.json @@ -4,18 +4,12 @@ "prefix":"203.0.113.8\/30", "protocol":"bgp", "installed":true, - "internalNextHopNum":2, "internalNextHopActiveNum":1, "nexthops":[ { "fib":true, "ip":"10.0.3.2", "active":true - }, - { - "fib":null, - "ip":"198.51.100.10", - "active":null } ] } diff --git a/tests/topotests/lib/bgpcheck.py b/tests/topotests/lib/bgpcheck.py index 5ca35a50a46b..5a3209c04c9b 100644 --- a/tests/topotests/lib/bgpcheck.py +++ b/tests/topotests/lib/bgpcheck.py @@ -61,3 +61,13 @@ def check_show_bgp_vpn_prefix_found( else: expected = {rd: {"prefix": prefix}} return topotest.json_cmp(output, expected) + + +def bgp_check_path_selection_unicast(router, expected): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast 192.0.2.9/32 json")) + return topotest.json_cmp(output, expected) + + +def bgp_check_path_selection_vpn(router, prefix, expected, vrf_name="vrf1"): + output = json.loads(router.vtysh_cmd(f"show bgp vrf {vrf_name} ipv4 {prefix} json")) + return topotest.json_cmp(output, expected) diff --git a/tests/topotests/lib/common_check.py b/tests/topotests/lib/common_check.py index b04b9de44e89..6803b5074621 100644 --- a/tests/topotests/lib/common_check.py +++ b/tests/topotests/lib/common_check.py @@ -11,7 +11,12 @@ def ip_check_path_selection( - router, ipaddr_str, expected, vrf_name=None, check_fib=False + router, + ipaddr_str, + expected, + vrf_name=None, + check_fib=False, + ignore_duplicate_nh=False, ): if vrf_name: cmdstr = f"show ip route vrf {vrf_name} {ipaddr_str} json" @@ -25,7 +30,15 @@ def ip_check_path_selection( ret = topotest.json_cmp(output, expected) if ret is None: num_nh_expected = len(expected[ipaddr_str][0]["nexthops"]) - num_nh_observed = len(output[ipaddr_str][0]["nexthops"]) + if ignore_duplicate_nh: + num_nh_observed = 0 + for nh in output[ipaddr_str][0]["nexthops"]: + if "duplicate" in nh.keys() and nh["duplicate"]: + continue + num_nh_observed += 1 + else: + num_nh_observed = len(output[ipaddr_str][0]["nexthops"]) + if num_nh_expected == num_nh_observed: if check_fib: # special case: when fib flag is unset, @@ -49,7 +62,9 @@ def ip_check_path_selection( return ret -def iproute2_check_path_selection(router, ipaddr_str, expected, vrf_name=None): +def iproute2_check_path_selection( + router, ipaddr_str, expected, vrf_name=None, nhg_id=None +): if not topotest.iproute2_is_json_capable(): return None @@ -62,4 +77,32 @@ def iproute2_check_path_selection(router, ipaddr_str, expected, vrf_name=None): except: output = [] + if nhg_id is None: + return topotest.json_cmp(output, expected) + + for entry in output: + if "nhid" not in entry.keys(): + return "problem. nhid not found" + if entry["nhid"] != nhg_id: + return f"problem: invalid nhid {entry['nhid']}, expected {nhg_id}" + return topotest.json_cmp(output, expected) + + +def ip_check_path_not_present(router, ipaddr_str): + output = json.loads(router.vtysh_cmd(f"show ip route {ipaddr_str} json")) + if ipaddr_str in output.keys(): + return "Not Good" + return None + + +def iproute2_check_path_not_present(router, ipaddr_str): + if not topotest.iproute2_is_json_capable(): + return None + + output = json.loads(router.run(f"ip -json route show {ipaddr_str}")) + for entry in output: + for nhid_entry in entry: + return f"The following entry is found: {nhid_entry['dst']}." + + return None diff --git a/tests/topotests/lib/nexthopgroup.py b/tests/topotests/lib/nexthopgroup.py new file mode 100644 index 000000000000..1d77decea0ac --- /dev/null +++ b/tests/topotests/lib/nexthopgroup.py @@ -0,0 +1,151 @@ +# SPDX-License-Identifier: ISC +# +# Copyright (C) 2023 NVIDIA Corporation +# Copyright (C) 2023 6WIND +# +import json +import re +from time import sleep + +from lib.topogen import get_topogen, topotest +import functools + + +def route_get_nhg_id(route_str, rname, vrf_name=None): + global fatal_error + + def get_func(route_str, rname, vrf_name=None): + net = get_topogen().net + if vrf_name: + output = net[rname].cmd( + 'vtysh -c "show ip route vrf {} {} nexthop-group"'.format( + vrf_name, route_str + ) + ) + else: + output = net[rname].cmd( + 'vtysh -c "show ip route {} nexthop-group"'.format(route_str) + ) + match = re.search(r"Nexthop Group ID: (\d+)", output) + if match is not None: + nhg_id = int(match.group(1)) + return nhg_id + else: + return None + + test_func = functools.partial(get_func, route_str, rname, vrf_name) + _, nhg_id = topotest.run_and_expect_type(test_func, int, count=30, wait=1) + if nhg_id == None: + fatal_error = "Nexthop Group ID not found for route {}".format(route_str) + assert nhg_id != None, fatal_error + else: + return nhg_id + + +def verify_nexthop_group(nhg_id, rname, recursive=False, ecmp=0): + net = get_topogen().net + count = 0 + valid = None + ecmpcount = None + depends = None + resolved_id = None + installed = None + found = False + + while not found and count < 10: + count += 1 + # Verify NHG is valid/installed + output = net[rname].cmd('vtysh -c "show nexthop-group rib {}"'.format(nhg_id)) + valid = re.search(r"Valid", output) + if valid is None: + found = False + sleep(1) + continue + + if ecmp or recursive: + ecmpcount = re.search(r"Depends:.*\n", output) + if ecmpcount is None: + found = False + sleep(1) + continue + + # list of IDs in group + depends = re.findall(r"\((\d+)\)", ecmpcount.group(0)) + + if ecmp: + if len(depends) != ecmp: + found = False + sleep(1) + continue + else: + # If recursive, we need to look at its resolved group + if len(depends) != 1: + found = False + sleep(1) + continue + + resolved_id = int(depends[0]) + verify_nexthop_group(resolved_id, rname, False) + else: + installed = re.search(r"Installed", output) + if installed is None: + found = False + sleep(1) + continue + found = True + + assert valid is not None, "Nexthop Group ID={} not marked Valid".format(nhg_id) + if ecmp or recursive: + assert ecmpcount is not None, "Nexthop Group ID={} has no depends".format( + nhg_id + ) + if ecmp: + assert ( + len(depends) == ecmp + ), "Nexthop Group ID={} doesn't match ecmp size".format(nhg_id) + else: + assert ( + len(depends) == 1 + ), "Nexthop Group ID={} should only have one recursive depend".format( + nhg_id + ) + else: + assert installed is not None, "Nexthop Group ID={} not marked Installed".format( + nhg_id + ) + + +def verify_route_nexthop_group(route_str, rname, recursive=False, ecmp=0): + # Verify route and that zebra created NHGs for and they are valid/installed + nhg_id = route_get_nhg_id(route_str, rname) + verify_nexthop_group(nhg_id, rname, recursive, ecmp) + + +def verify_nexthop_group_has_nexthop(router, nexthop, client="sharp"): + net = get_topogen().net + cmd_str = "show nexthop-group rib {} json".format(client) + + output = router.vtysh_cmd(cmd_str) + joutput = json.loads(output) + for nhgid in joutput: + n = joutput[nhgid] + if "nexthops" not in n: + continue + if "ip" not in n["nexthops"][0].keys(): + continue + if n["nexthops"][0]["ip"] == nexthop: + return None + return f"nexthop {nexthop} not found in show nexthop-group rib" + + +def route_check_nhg_id_is_protocol(ipaddr_str, rname, vrf_name=None, protocol="bgp"): + tgen = get_topogen() + nhg_id = route_get_nhg_id(ipaddr_str, rname, vrf_name=vrf_name) + output = tgen.gears["r1"].vtysh_cmd( + "show nexthop-group rib %d" % nhg_id, + ) + assert f"ID: {nhg_id} ({protocol})" in output, ( + "NHG %d not found in 'show nexthop-group rib ID json" % nhg_id + ) + + return nhg_id diff --git a/tests/topotests/sharp_recursive_nexthop/r1/route_recursive.json b/tests/topotests/sharp_recursive_nexthop/r1/route_recursive.json new file mode 100644 index 000000000000..34f547b24402 --- /dev/null +++ b/tests/topotests/sharp_recursive_nexthop/r1/route_recursive.json @@ -0,0 +1,31 @@ +[{ + "prefix":"192.168.0.1/32", + "prefixLen":32, + "protocol":"sharp", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "nexthops":[ + { + "ip":"172.31.0.55", + "afi":"ipv4", + "active":true, + "recursive":true, + "weight":1 + }, + { + "fib":true, + "ip":"192.0.2.100", + "afi":"ipv4", + "interfaceName":"dum0", + "resolver":true, + "active":true, + "weight":1 + } + ] +}] diff --git a/tests/topotests/sharp_recursive_nexthop/r1/route_recursive_2.json b/tests/topotests/sharp_recursive_nexthop/r1/route_recursive_2.json new file mode 100644 index 000000000000..488abc611c48 --- /dev/null +++ b/tests/topotests/sharp_recursive_nexthop/r1/route_recursive_2.json @@ -0,0 +1,40 @@ +[{ + "prefix":"192.168.0.1/32", + "prefixLen":32, + "protocol":"sharp", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "nexthops":[ + { + "ip":"172.31.0.55", + "afi":"ipv4", + "active":true, + "recursive":true, + "weight":1 + }, + { + "fib":true, + "ip":"192.0.2.100", + "afi":"ipv4", + "interfaceName":"dum0", + "resolver":true, + "active":true, + "weight":1 + }, + { + "fib":true, + "ip":"192.0.2.102", + "afi":"ipv4", + "interfaceName":"dum0", + "resolver":true, + "active":true, + "weight":1 + } + ] +}] diff --git a/tests/topotests/sharp_recursive_nexthop/r1/route_recursive_3.json b/tests/topotests/sharp_recursive_nexthop/r1/route_recursive_3.json new file mode 100644 index 000000000000..273e53dc7dcd --- /dev/null +++ b/tests/topotests/sharp_recursive_nexthop/r1/route_recursive_3.json @@ -0,0 +1,31 @@ +[{ + "prefix":"192.168.0.1/32", + "prefixLen":32, + "protocol":"sharp", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "nexthops":[ + { + "ip":"172.31.0.55", + "afi":"ipv4", + "active":true, + "recursive":true, + "weight":1 + }, + { + "fib":true, + "ip":"192.0.2.102", + "afi":"ipv4", + "interfaceName":"dum0", + "resolver":true, + "active":true, + "weight":1 + } + ] +}] diff --git a/tests/topotests/sharp_recursive_nexthop/r1/route_static.json b/tests/topotests/sharp_recursive_nexthop/r1/route_static.json new file mode 100644 index 000000000000..cff9e6a035ad --- /dev/null +++ b/tests/topotests/sharp_recursive_nexthop/r1/route_static.json @@ -0,0 +1,23 @@ +[{ + "prefix":"172.31.0.0/24", + "prefixLen":24, + "protocol":"static", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":1, + "metric":0, + "installed":true, + "table":254, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[{ + "fib":true, + "ip":"192.0.2.100", + "afi":"ipv4", + "interfaceName":"dum0", + "active":true, + "weight":1 + }] +}] diff --git a/tests/topotests/sharp_recursive_nexthop/r1/route_static_2.json b/tests/topotests/sharp_recursive_nexthop/r1/route_static_2.json new file mode 100644 index 000000000000..704f97c6f500 --- /dev/null +++ b/tests/topotests/sharp_recursive_nexthop/r1/route_static_2.json @@ -0,0 +1,31 @@ +[{ + "prefix":"172.31.0.0/24", + "prefixLen":24, + "protocol":"static", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":1, + "metric":0, + "installed":true, + "table":254, + "nexthops":[ + { + "fib":true, + "ip":"192.0.2.100", + "afi":"ipv4", + "interfaceName":"dum0", + "active":true, + "weight":1 + }, + { + "fib":true, + "ip":"192.0.2.102", + "afi":"ipv4", + "interfaceName":"dum0", + "active":true, + "weight":1 + } + ] +}] diff --git a/tests/topotests/sharp_recursive_nexthop/r1/route_static_3.json b/tests/topotests/sharp_recursive_nexthop/r1/route_static_3.json new file mode 100644 index 000000000000..c333b1158fd2 --- /dev/null +++ b/tests/topotests/sharp_recursive_nexthop/r1/route_static_3.json @@ -0,0 +1,21 @@ +[{ + "prefix":"172.31.0.0/24", + "prefixLen":24, + "protocol":"static", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":1, + "metric":0, + "installed":true, + "table":254, + "nexthops":[{ + "fib":true, + "ip":"192.0.2.102", + "afi":"ipv4", + "interfaceName":"dum0", + "active":true, + "weight":1 + }] +}] diff --git a/tests/topotests/sharp_recursive_nexthop/r1/setup.sh b/tests/topotests/sharp_recursive_nexthop/r1/setup.sh new file mode 100644 index 000000000000..43be196f66b6 --- /dev/null +++ b/tests/topotests/sharp_recursive_nexthop/r1/setup.sh @@ -0,0 +1,2 @@ +ip link add dum0 type dummy +ip link set dum0 up diff --git a/tests/topotests/sharp_recursive_nexthop/r1/zebra.conf b/tests/topotests/sharp_recursive_nexthop/r1/zebra.conf new file mode 100644 index 000000000000..1fd735efd37f --- /dev/null +++ b/tests/topotests/sharp_recursive_nexthop/r1/zebra.conf @@ -0,0 +1,7 @@ +debug zebra nexthop detail +debug zebra rib detail +interface dum0 + ip address 192.0.2.1/24 +exit +ip route 172.31.0.0/24 192.0.2.100 + diff --git a/tests/topotests/sharp_recursive_nexthop/test_sharp_recursive_nexthop.py b/tests/topotests/sharp_recursive_nexthop/test_sharp_recursive_nexthop.py new file mode 100755 index 000000000000..ebf75eeda3d0 --- /dev/null +++ b/tests/topotests/sharp_recursive_nexthop/test_sharp_recursive_nexthop.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_sharp_recursive_nexthop.py +# +# Copyright (c) 2020 by 6WIND +# + +""" +test_sharp_recursive_nexthop.py +""" + +import os +import sys +import pytest +import json +from functools import partial + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from lib import topotest +from lib.topogen import Topogen, get_topogen +from lib.common_config import step +from lib.nexthopgroup import route_get_nhg_id, verify_nexthop_group_has_nexthop + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.sharpd] + + +def open_json_file(filename): + try: + with open(filename, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(filename) + + +def setup_module(mod): + tgen = Topogen({None: "r1"}, mod.__name__) + tgen.start_topology() + router_list = tgen.routers() + for rname, router in tgen.routers().items(): + router.run( + "/bin/bash {}".format(os.path.join(CWD, "{}/setup.sh".format(rname))) + ) + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_SHARP, os.path.join(CWD, "{}/sharpd.conf".format(rname)) + ) + tgen.start_router() + + +def teardown_module(_mod): + tgen = get_topogen() + tgen.stop_topology() + + +def check(router, dest, expected): + output = json.loads(router.vtysh_cmd("show ip route {0} json".format(dest))) + output = output.get(f"{dest}") + if output is None: + return False + if "nexthops" not in output[0].keys(): + return False + if len(expected[0]["nexthops"]) != len(output[0]["nexthops"]): + return False + return topotest.json_cmp(output, expected) + + +def nocheck(router, dest): + output = json.loads(router.vtysh_cmd("show ip route {0} json".format(dest))) + return output.get(f"{dest}") + + +def test_sharp_create_nexthop_and_route(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + r1 = tgen.gears["r1"] + + step("Check that static route is installed.") + expected = open_json_file(os.path.join(CWD, "r1/route_static.json")) + test_func = partial(check, r1, "172.31.0.0/24", expected) + success, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Failed" + + step("From sharpd, creation of a recursive nexthop with route. Check install is ok") + r1.vtysh_cmd( + """ + configure terminal\n + nexthop-group A\n + allow-recursion\n + nexthop 172.31.0.55\n + exit\n + exit\n + """ + ) + + test_func = partial(verify_nexthop_group_has_nexthop, r1, "172.31.0.55") + success, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + + step( + "From sharpd, install route to 192.168.0.1 using nexthop-group A. Check install is ok" + ) + r1.vtysh_cmd( + """ + sharp install routes 192.168.0.1 nexthop-group A 1\n + """ + ) + expected = open_json_file(os.path.join(CWD, "r1/route_recursive.json")) + test_func = partial(check, r1, "192.168.0.1/32", expected) + success, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + + +def test_sharp_change_static_route_to_ecmp(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + step("Change static route to ECMP. Check route is ECMP") + r1.vtysh_cmd( + """ + configure terminal\n + ip route 172.31.0.0/24 192.0.2.102\n + """ + ) + expected = open_json_file(os.path.join(CWD, "r1/route_static_2.json")) + test_func = partial(check, r1, "172.31.0.0/24", expected) + success, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + + step("Update nexthop-group. Check recursive route is refreshed.") + r1.vtysh_cmd( + """ + sharp reinstall nexthop-group A\n + """ + ) + expected = open_json_file(os.path.join(CWD, "r1/route_recursive_2.json")) + test_func = partial(check, r1, "192.168.0.1/32", expected) + success, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + + +def test_sharp_change_static_route_to_single_nh(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + step("Change static route to a single nexthop. Check route is installed.") + r1.vtysh_cmd( + """ + configure terminal\n + no ip route 172.31.0.0/24 192.0.2.100\n + """ + ) + expected = open_json_file(os.path.join(CWD, "r1/route_static_3.json")) + test_func = partial(check, r1, "172.31.0.0/24", expected) + success, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + + step("Update nexthop-group. Check recursive route is refreshed.") + r1.vtysh_cmd( + """ + sharp reinstall nexthop-group A\n + """ + ) + expected = open_json_file(os.path.join(CWD, "r1/route_recursive_3.json")) + test_func = partial(check, r1, "192.168.0.1/32", expected) + success, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + +def test_sharp_remove_static_route(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + r1 = tgen.gears["r1"] + + step("Remove static route. Check route is removed.") + r1.vtysh_cmd( + """ + configure terminal\n + no ip route 172.31.0.0/24 192.0.2.102\n + """ + ) + test_func = partial(nocheck, r1, "172.31.0.0/24") + success, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + + step("Update nexthop-group. Check recursive route is inactive.") + r1.vtysh_cmd( + """ + sharp reinstall nexthop-group A\n + """ + ) + expected = open_json_file(os.path.join(CWD, "r1/route_recursive_4.json")) + test_func = partial(check, r1, "192.168.0.1/32", expected) + success, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + r1.vtysh_cmd( + """ + show nexthop-group rib sharp detail\n + """ + ) + r1.vtysh_cmd( + """ + show nexthop-group rib sharp detail json\n + """ + ) + + +def test_sharp_readded_static_route(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + r1 = tgen.gears["r1"] + + step("Readd static route, but undefined. Check route is still inactive.") + r1.vtysh_cmd( + """ + configure terminal\n + ip route 172.31.0.0/24 192.0.2.202\n + """ + ) + expected = open_json_file(os.path.join(CWD, "r1/route_static_4.json")) + test_func = partial(check, r1, "172.31.0.0/24", expected) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=1) + + step("Update nexthop-group. Check recursive route is active.") + r1.vtysh_cmd( + """ + sharp reinstall nexthop-group A\n + """ + ) + expected = open_json_file(os.path.join(CWD, "r1/route_recursive_5.json")) + test_func = partial(check, r1, "192.168.0.1/32", expected) + success, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c index 10acee9be435..7acf67e70702 100644 --- a/zebra/zapi_msg.c +++ b/zebra/zapi_msg.c @@ -1740,6 +1740,118 @@ static struct nexthop *nexthop_from_zapi(const struct zapi_nexthop *api_nh, return nexthop; } +static struct nhg_backup_info * +zapi_read_nexthop_group_backup(struct zserv *client, + struct zapi_nhg_group *api_nhg_group, bool *err) +{ + uint16_t nexthopgroup_num; + struct nhg_backup_info *bnhg = NULL; + uint16_t i; + uint32_t nhgid; + struct nhg_hash_entry *nhe; + + assert(api_nhg_group); + assert(err); + + *err = false; + + nexthopgroup_num = api_nhg_group->backup_child_group_num; + if (nexthopgroup_num == 0) + return NULL; + + if (IS_ZEBRA_DEBUG_RECV) + zlog_debug("%s: adding %d backup nexthop groups", __func__, + nexthopgroup_num); + + bnhg = zebra_nhg_backup_alloc(); + + for (i = 0; i < nexthopgroup_num; i++) { + nhgid = api_nhg_group->backup_child_group_id[i]; + if (!nhgid) { + flog_warn(EC_ZEBRA_NEXTHOP_CREATION_FAILED, + "%s: Nexthops Groups Specified: %u(%u) but we failed to properly create one", + __func__, nexthopgroup_num, i); + zebra_nhg_backup_free(&bnhg); + *err = true; + return NULL; + } + if (IS_ZEBRA_DEBUG_RECV) + zlog_debug("%s: nhgroup=%u)", __func__, nhgid); + + /* find nexthop associated to id */ + nhe = zebra_nhg_lookup_id(nhgid); + if (!nhe) { + flog_warn(EC_ZEBRA_NEXTHOP_CREATION_FAILED, + "%s: Nexthops Groups Specified: %u(%u) not found", + __func__, nexthopgroup_num, nhgid); + zebra_nhg_backup_free(&bnhg); + *err = true; + return NULL; + } + + /* Note that the order of the backup groups is + * significant, so we don't sort this list as we do the + * primary group, we just append. + */ + nexthop_group_append_nexthops(&bnhg->nhe->nhg, &nhe->nhg); + } + + return bnhg; +} + +static struct nexthop_group * +zapi_read_nexthop_group(struct zserv *client, + struct zapi_nhg_group *api_nhg_group) +{ + uint16_t nexthopgroup_num; + struct nexthop_group *nhg = NULL, nhg_tmp = { 0 }; + uint16_t i; + uint32_t nhgid; + struct nhg_hash_entry *nhe; + struct nexthop *nh, *nh_next; + + assert(api_nhg_group); + + nexthopgroup_num = api_nhg_group->child_group_num; + for (i = 0; i < nexthopgroup_num; i++) { + nhgid = api_nhg_group->child_group_id[i]; + if (!nhgid) { + flog_warn(EC_ZEBRA_NEXTHOP_CREATION_FAILED, + "%s: Nexthops Groups Specified: %u(%u) but we failed to properly create one", + __func__, nexthopgroup_num, i); + nexthops_free(nhg_tmp.nexthop); + return NULL; + } + if (IS_ZEBRA_DEBUG_RECV) + zlog_debug("%s: nhgroup=%u)", __func__, nhgid); + + /* find nexthop associated to id */ + nhe = zebra_nhg_lookup_id(nhgid); + if (!nhe) { + flog_warn(EC_ZEBRA_NEXTHOP_CREATION_FAILED, + "%s: Nexthops Groups Specified: %u(%u) not found", + __func__, nexthopgroup_num, nhgid); + nexthops_free(nhg_tmp.nexthop); + return NULL; + } + + nexthop_group_append_nexthops(&nhg_tmp, &nhe->nhg); + } + + nhg = nexthop_group_new(); + + /* The list of nexthops must be sorted. */ + nh = nhg_tmp.nexthop; + while (nh) { + nh_next = nh->next; + nh->next = NULL; + nexthop_group_add_sorted(nhg, nh); + nh = nh_next; + } + + return nhg; +} + static bool zapi_read_nexthops(struct zserv *client, struct prefix *p, struct zapi_nexthop *nhops, uint32_t flags, uint32_t message, uint16_t nexthop_num, @@ -1954,6 +2066,9 @@ static int zapi_nhg_decode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) STREAM_GETL(s, api_nhg->resilience.idle_timer); STREAM_GETL(s, api_nhg->resilience.unbalanced_timer); + STREAM_GETL(s, api_nhg->message); + STREAM_GETC(s, api_nhg->flags); + /* Nexthops */ STREAM_GETW(s, api_nhg->nexthop_num); @@ -1969,7 +2084,7 @@ static int zapi_nhg_decode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) for (i = 0; i < api_nhg->nexthop_num; i++) { znh = &((api_nhg->nexthops)[i]); - if (zapi_nexthop_decode(s, znh, 0, 0) != 0) { + if (zapi_nexthop_decode(s, znh, 0, api_nhg->message) != 0) { flog_warn(EC_ZEBRA_NEXTHOP_CREATION_FAILED, "%s: Nexthop creation failed", __func__); return -1; @@ -1985,7 +2100,7 @@ static int zapi_nhg_decode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) for (i = 0; i < api_nhg->backup_nexthop_num; i++) { znh = &((api_nhg->backup_nexthops)[i]); - if (zapi_nexthop_decode(s, znh, 0, 0) != 0) { + if (zapi_nexthop_decode(s, znh, 0, api_nhg->message) != 0) { flog_warn(EC_ZEBRA_NEXTHOP_CREATION_FAILED, "%s: Backup Nexthop creation failed", __func__); @@ -2004,6 +2119,47 @@ static int zapi_nhg_decode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) return -1; } +static int zapi_nhg_child_decode(struct stream *s, + struct zapi_nhg_group *api_nhg_group) +{ + uint16_t i; + + STREAM_GETW(s, api_nhg_group->proto); + STREAM_GETL(s, api_nhg_group->id); + + STREAM_GETW(s, api_nhg_group->child_group_num); + + if (api_nhg_group->child_group_num == 0) { + flog_warn(EC_ZEBRA_NEXTHOP_CREATION_FAILED, + "%s: grp count should not be 0 when NHG_CHILD_ADD is used", + __func__); + return -1; + } + + for (i = 0; i < api_nhg_group->child_group_num; i++) + STREAM_GETL(s, api_nhg_group->child_group_id[i]); + + STREAM_GETW(s, api_nhg_group->backup_child_group_num); + + for (i = 0; i < api_nhg_group->backup_child_group_num; i++) + STREAM_GETL(s, api_nhg_group->backup_child_group_id[i]); + + STREAM_GETW(s, api_nhg_group->resilience.buckets); + STREAM_GETL(s, api_nhg_group->resilience.idle_timer); + STREAM_GETL(s, api_nhg_group->resilience.unbalanced_timer); + + STREAM_GETC(s, api_nhg_group->flags); + STREAM_GETC(s, api_nhg_group->message); + + return 0; + +stream_failure: + flog_warn(EC_ZEBRA_NEXTHOP_CREATION_FAILED, + "%s: Nexthop Group decode failed with some sort of stream read failure", + __func__); + return -1; +} + static void zread_nhg_del(ZAPI_HANDLER_ARGS) { struct stream *s; @@ -2051,13 +2207,12 @@ static void zread_nhg_add(ZAPI_HANDLER_ARGS) return; } - if ((!zapi_read_nexthops(client, NULL, api_nhg.nexthops, 0, 0, - api_nhg.nexthop_num, - api_nhg.backup_nexthop_num, &nhg, NULL)) - || (!zapi_read_nexthops(client, NULL, api_nhg.backup_nexthops, 0, 0, - api_nhg.backup_nexthop_num, - api_nhg.backup_nexthop_num, NULL, &bnhg))) { - + if ((!zapi_read_nexthops(client, NULL, api_nhg.nexthops, 0, + api_nhg.message, api_nhg.nexthop_num, + api_nhg.backup_nexthop_num, &nhg, NULL)) || + (!zapi_read_nexthops(client, NULL, api_nhg.backup_nexthops, 0, 0, + api_nhg.backup_nexthop_num, + api_nhg.backup_nexthop_num, NULL, &bnhg))) { flog_warn(EC_ZEBRA_NEXTHOP_CREATION_FAILED, "%s: Nexthop Group Creation failed", __func__); @@ -2072,6 +2227,9 @@ static void zread_nhg_add(ZAPI_HANDLER_ARGS) nhe = zebra_nhg_alloc(); nhe->id = api_nhg.id; nhe->type = api_nhg.proto; + nhe->nhg.flags = api_nhg.flags; + if (CHECK_FLAG(api_nhg.message, ZAPI_MESSAGE_SRTE)) + SET_FLAG(nhe->nhg.message, NEXTHOP_GROUP_MESSAGE_SRTE); nhe->zapi_instance = client->instance; nhe->zapi_session = client->session_id; @@ -2081,10 +2239,8 @@ static void zread_nhg_add(ZAPI_HANDLER_ARGS) nhe->nhg.nhgr = api_nhg.resilience; - if (bnhg) { - nhe->backup_info = bnhg; - bnhg = NULL; - } + nhe->backup_info = bnhg; + bnhg = NULL; /* * TODO: @@ -2107,6 +2263,69 @@ static void zread_nhg_add(ZAPI_HANDLER_ARGS) client->nhg_add_cnt++; } +static void zread_nhg_child_add(ZAPI_HANDLER_ARGS) +{ + struct stream *s; + struct zapi_nhg_group api_nhg_group = {}; + struct nexthop_group *nhg = NULL; + struct nhg_backup_info *bnhg = NULL; + struct nhg_hash_entry *nhe; + bool err = false; + + s = msg; + if (zapi_nhg_child_decode(s, &api_nhg_group) < 0) { + if (IS_ZEBRA_DEBUG_RECV) + zlog_debug("%s: unable to decode the received zapi_nhg_group", + __func__); + return; + } + + if (IS_ZEBRA_DEBUG_RECV) + zlog_debug("%s: nhid=%u)", __func__, api_nhg_group.id); + + nhg = zapi_read_nexthop_group(client, &api_nhg_group); + if (!nhg) + return; + + bnhg = zapi_read_nexthop_group_backup(client, &api_nhg_group, &err); + if (!bnhg && err) + return; + + /* Create a temporary nhe */ + nhe = zebra_nhg_alloc(); + nhe->id = api_nhg_group.id; + nhe->type = api_nhg_group.proto; + nhe->nhg.flags = api_nhg_group.flags; + nhe->zapi_instance = client->instance; + nhe->zapi_session = client->session_id; + + /* Take over the list(s) of nexthops */ + nhe->nhg.nexthop = nhg->nexthop; + nhg->nexthop = NULL; + + nhe->nhg.nhgr = api_nhg_group.resilience; + + nhe->backup_info = bnhg; + + /* + * TODO: + * Assume fully resolved for now and install. + * Resolution is going to need some more work. + */ + + /* Enqueue to workqueue for processing */ + rib_queue_nhe_add(nhe); + + /* Free any local allocations */ + nexthop_group_delete(&nhg); + + /* Stats */ + if (zebra_nhg_lookup_id(api_nhg_group.id)) + client->nhg_upd8_cnt++; + else + client->nhg_add_cnt++; +} + static void zread_route_add(ZAPI_HANDLER_ARGS) { struct stream *s; @@ -4097,6 +4316,7 @@ void (*const zserv_handlers[])(ZAPI_HANDLER_ARGS) = { [ZEBRA_NEIGH_DISCOVER] = zread_neigh_discover, [ZEBRA_NHG_ADD] = zread_nhg_add, [ZEBRA_NHG_DEL] = zread_nhg_del, + [ZEBRA_NHG_CHILD_ADD] = zread_nhg_child_add, [ZEBRA_ROUTE_NOTIFY_REQUEST] = zread_route_notify_request, [ZEBRA_EVPN_REMOTE_NH_ADD] = zebra_evpn_proc_remote_nh, [ZEBRA_EVPN_REMOTE_NH_DEL] = zebra_evpn_proc_remote_nh, diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c index 88c1a049382b..9054a73a6a7f 100644 --- a/zebra/zebra_dplane.c +++ b/zebra/zebra_dplane.c @@ -4232,6 +4232,7 @@ dplane_route_update_internal(struct route_node *rn, enum zebra_dplane_result result = ZEBRA_DPLANE_REQUEST_FAILURE; int ret = EINVAL; struct zebra_dplane_ctx *ctx = NULL; + struct nhg_hash_entry *nhe; /* Obtain context block */ ctx = dplane_ctx_alloc(); @@ -4294,8 +4295,10 @@ dplane_route_update_internal(struct route_node *rn, "%s: Ignoring Route exactly the same", __func__); - for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), - nexthop)) { + nhe = zebra_nhg_lookup_id(dplane_ctx_get_nhe_id(ctx)); + if (PROTO_OWNED(nhe)) + nexthop_group_mark_duplicates(&nhe->nhg); + for (ALL_NEXTHOPS_PTR(&nhe->nhg, nexthop)) { if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) continue; diff --git a/zebra/zebra_nhg.c b/zebra/zebra_nhg.c index 1519246c179e..ec73b9a5812e 100644 --- a/zebra/zebra_nhg.c +++ b/zebra/zebra_nhg.c @@ -394,6 +394,99 @@ struct nhg_hash_entry *zebra_nhg_alloc(void) return nhe; } +static uint32_t zebra_nhg_prefix_proto_nhgs_hash_key(const void *arg) +{ + const struct nhg_prefix_proto_nhgs *nhg_p = arg; + uint32_t key, key2; + + key = prefix_hash_key(&(nhg_p->prefix)); + key2 = prefix_hash_key(&(nhg_p->src_prefix)); + key = jhash_1word(key, key2); + key = jhash_2words(nhg_p->afi, nhg_p->safi, key); + key = jhash_2words(nhg_p->table_id, nhg_p->vrf_id, key); + + return key; +} + +static bool zebra_nhg_prefix_proto_nhgs_hash_equal(const void *arg1, + const void *arg2) +{ + const struct nhg_prefix_proto_nhgs *r1, *r2; + + r1 = (const struct nhg_prefix_proto_nhgs *)arg1; + r2 = (const struct nhg_prefix_proto_nhgs *)arg2; + + if (r1->afi != r2->afi) + return false; + + if (r1->safi != r2->safi) + return false; + + if (r1->vrf_id != r2->vrf_id) + return false; + + if (r1->table_id != r2->table_id) + return false; + + if (prefix_cmp(&r1->prefix, &r2->prefix)) + return false; + + if (r1->src_prefix.family != r2->src_prefix.family) + return false; + + if (r1->src_prefix.family && + prefix_cmp(&r1->src_prefix, &r2->src_prefix)) + return false; + + return true; +} + +void zebra_nhg_prefix_proto_nhgs_hash_free(void *p) +{ + struct nhg_prefix_proto_nhgs *nhg_p = p; + + if (IS_ZEBRA_DEBUG_NHG_DETAIL) + zlog_debug("%s: => detach prefix %pFX from NHG (%pNG)", + __func__, &nhg_p->prefix, nhg_p->nhe); + + XFREE(MTYPE_NHG, nhg_p); +} + +void *zebra_nhg_prefix_proto_nhgs_alloc(void *arg) +{ + struct nhg_prefix_proto_nhgs *nhg_p, *orig; + + orig = (struct nhg_prefix_proto_nhgs *)arg; + + nhg_p = XCALLOC(MTYPE_NHG, sizeof(struct nhg_prefix_proto_nhgs)); + + memcpy(nhg_p, orig, sizeof(struct nhg_prefix_proto_nhgs)); + + return nhg_p; +} + +static void zebra_nhg_prefix_copy_entry(struct hash_bucket *b, void *data) +{ + struct nhg_prefix_proto_nhgs *nhg_p = b->data, *new_nhg_p; + struct nhg_hash_entry *nhe = data; + + new_nhg_p = hash_get(nhe->prefix_proto_nhgs, nhg_p, + zebra_nhg_prefix_proto_nhgs_alloc); + new_nhg_p->nhe = nhe; + if (IS_ZEBRA_DEBUG_NHG_DETAIL) + zlog_debug("%s: => copy prefix %pFX to NHG (%pNG)", __func__, + &nhg_p->prefix, nhe); +} + +void zebra_nhg_prefix_copy(struct nhg_hash_entry *new_entry, + struct nhg_hash_entry *old_entry) +{ + if (new_entry && old_entry && old_entry->prefix_proto_nhgs && + new_entry->prefix_proto_nhgs) + hash_iterate(old_entry->prefix_proto_nhgs, + zebra_nhg_prefix_copy_entry, new_entry); +} + /* * Allocate new nhe and make shallow copy of 'orig'; no * recursive info is copied. @@ -407,6 +500,12 @@ struct nhg_hash_entry *zebra_nhe_copy(const struct nhg_hash_entry *orig, nhe->id = id; + if (id >= ZEBRA_NHG_PROTO_LOWER) + nhe->prefix_proto_nhgs = + hash_create_size(8, zebra_nhg_prefix_proto_nhgs_hash_key, + zebra_nhg_prefix_proto_nhgs_hash_equal, + "Zebra Nexthop groups Prefixes hash list index"); + nexthop_group_copy(&(nhe->nhg), &(orig->nhg)); nhe->vrf_id = orig->vrf_id; @@ -1099,15 +1198,11 @@ void zebra_nhg_check_valid(struct nhg_hash_entry *nhe) bool valid = false; /* - * If I have other nhe's depending on me, or I have nothing - * I am depending on then this is a + * If I have other nhe's depending on me, then this is a * singleton nhe so set this nexthops flag as appropriate. */ - if (nhg_connected_tree_count(&nhe->nhg_depends) || - nhg_connected_tree_count(&nhe->nhg_dependents) == 0) { - UNSET_FLAG(nhe->nhg.nexthop->flags, NEXTHOP_FLAG_FIB); + if (nhg_connected_tree_count(&nhe->nhg_depends)) UNSET_FLAG(nhe->nhg.nexthop->flags, NEXTHOP_FLAG_ACTIVE); - } /* If anthing else in the group is valid, the group is valid */ frr_each(nhg_connected_tree, &nhe->nhg_depends, rb_node_dep) { @@ -1169,8 +1264,7 @@ static void zebra_nhg_handle_install(struct nhg_hash_entry *nhe, bool install) "%s nh id %u (flags 0x%x) associated dependent NHG %pNG install", __func__, nhe->id, nhe->flags, rb_node_dep->nhe); - zebra_nhg_install_kernel(rb_node_dep->nhe, - ZEBRA_ROUTE_MAX); + zebra_nhg_install_kernel(rb_node_dep->nhe, true); } } } @@ -1674,6 +1768,13 @@ void zebra_nhg_free(struct nhg_hash_entry *nhe) zebra_nhg_free_members(nhe); + if (nhe->prefix_proto_nhgs) { + if (IS_ZEBRA_DEBUG_NHG_DETAIL) + zlog_debug("%s: => suppressing prefixes from NHG (%pNG)", + __func__, nhe); + hash_clean_and_free(&nhe->prefix_proto_nhgs, + zebra_nhg_prefix_proto_nhgs_hash_free); + } XFREE(MTYPE_NHG, nhe); } @@ -2387,8 +2488,11 @@ static int nexthop_active(struct nexthop *nexthop, struct nhg_hash_entry *nhe, * if specified) - i.e., we cannot have a nexthop NH1 is * resolved by a route NH1. The exception is if the route is a * host route. + * A test on 'top' is done, because the 'top' prefix is not known + * when 'nexthop_active()' is called from 'zebra_nhg_proto_add()'. + * The 'match againts oursleves' test will have to be done at protocol level. */ - if (prefix_same(&rn->p, top)) + if (top && prefix_same(&rn->p, top)) if (((afi == AFI_IP) && (rn->p.prefixlen != IPV4_MAX_BITLEN)) || ((afi == AFI_IP6) @@ -2923,189 +3027,15 @@ static uint32_t proto_nhg_nexthop_active_update(struct nexthop_group *nhg) return curr_active; } -/* - * This function takes the start of two comparable nexthops from two different - * nexthop groups and walks them to see if they can be considered the same - * or not. This is being used to determine if zebra should reuse a nhg - * from the old_re to the new_re, when an interface goes down and the - * new nhg sent down from the upper level protocol would resolve to it - */ -static bool zebra_nhg_nexthop_compare(const struct nexthop *nhop, - const struct nexthop *old_nhop, - const struct route_node *rn) +static uint32_t nexthop_active_update_common(struct route_node *rn, + struct route_entry *re, + struct nhg_hash_entry *curr_nhe) { - bool same = true; - - while (nhop && old_nhop) { - if (IS_ZEBRA_DEBUG_NHG_DETAIL) - zlog_debug("%s: %pRN Comparing %pNHvv(%u) to old: %pNHvv(%u)", - __func__, rn, nhop, nhop->flags, old_nhop, - old_nhop->flags); - if (!CHECK_FLAG(old_nhop->flags, NEXTHOP_FLAG_ACTIVE)) { - if (IS_ZEBRA_DEBUG_NHG_DETAIL) - zlog_debug("%s: %pRN Old is not active going to the next one", - __func__, rn); - old_nhop = old_nhop->next; - continue; - } - - if (nexthop_same(nhop, old_nhop)) { - struct nexthop *new_recursive, *old_recursive; - - if (IS_ZEBRA_DEBUG_NHG_DETAIL) - zlog_debug("%s: %pRN New and old are same, continuing search", - __func__, rn); - - new_recursive = nhop->resolved; - old_recursive = old_nhop->resolved; - - while (new_recursive && old_recursive) { - if (!nexthop_same(new_recursive, old_recursive)) { - same = false; - break; - } - - new_recursive = new_recursive->next; - old_recursive = old_recursive->next; - } - - if (new_recursive) - same = false; - else if (old_recursive) { - while (old_recursive) { - if (CHECK_FLAG(old_recursive->flags, - NEXTHOP_FLAG_ACTIVE)) - break; - old_recursive = old_recursive->next; - } - - if (old_recursive) - same = false; - } - - if (!same) - break; - - nhop = nhop->next; - old_nhop = old_nhop->next; - continue; - } else { - if (IS_ZEBRA_DEBUG_NHG_DETAIL) - zlog_debug("%s:%pRN They are not the same, stopping using new nexthop entry", - __func__, rn); - same = false; - break; - } - } - - if (nhop) - same = false; - else if (old_nhop) { - while (old_nhop) { - if (CHECK_FLAG(old_nhop->flags, NEXTHOP_FLAG_ACTIVE)) - break; - old_nhop = old_nhop->next; - } - - if (old_nhop) - same = false; - } - - return same; -} - -static struct nhg_hash_entry *zebra_nhg_rib_compare_old_nhe( - const struct route_node *rn, const struct route_entry *re, - struct nhg_hash_entry *new_nhe, struct nhg_hash_entry *old_nhe) -{ - struct nexthop *nhop, *old_nhop; - bool same = true; - struct vrf *vrf = vrf_lookup_by_id(re->vrf_id); - - if (IS_ZEBRA_DEBUG_NHG_DETAIL) { - char straddr[PREFIX_STRLEN]; - - prefix2str(&rn->p, straddr, sizeof(straddr)); - zlog_debug("%s: %pRN new id: %u old id: %u", __func__, rn, - new_nhe->id, old_nhe->id); - zlog_debug("%s: %pRN NEW", __func__, rn); - for (ALL_NEXTHOPS(new_nhe->nhg, nhop)) - route_entry_dump_nh(re, straddr, vrf, nhop); - - zlog_debug("%s: %pRN OLD", __func__, rn); - for (ALL_NEXTHOPS(old_nhe->nhg, nhop)) - route_entry_dump_nh(re, straddr, vrf, nhop); - } - - nhop = new_nhe->nhg.nexthop; - old_nhop = old_nhe->nhg.nexthop; - - same = zebra_nhg_nexthop_compare(nhop, old_nhop, rn); - - if (same) { - struct nexthop_group *bnhg, *old_bnhg; - - bnhg = zebra_nhg_get_backup_nhg(new_nhe); - old_bnhg = zebra_nhg_get_backup_nhg(old_nhe); - - if (bnhg || old_bnhg) { - if (bnhg && !old_bnhg) - same = false; - else if (!bnhg && old_bnhg) - same = false; - else - same = zebra_nhg_nexthop_compare(bnhg->nexthop, - old_bnhg->nexthop, - rn); - } - } - - if (IS_ZEBRA_DEBUG_NHG_DETAIL) - zlog_debug("%s:%pRN They are %sthe same, using the %s nhg entry", - __func__, rn, same ? "" : "not ", - same ? "old" : "new"); - - if (same) - return old_nhe; - else - return new_nhe; -} - -/* - * Iterate over all nexthops of the given RIB entry and refresh their - * ACTIVE flag. If any nexthop is found to toggle the ACTIVE flag, - * the whole re structure is flagged with ROUTE_ENTRY_CHANGED. - * - * Return value is the new number of active nexthops. - */ -int nexthop_active_update(struct route_node *rn, struct route_entry *re, - struct route_entry *old_re) -{ - struct nhg_hash_entry *curr_nhe; uint32_t curr_active = 0, backup_active = 0; - - if (PROTO_OWNED(re->nhe)) - return proto_nhg_nexthop_active_update(&re->nhe->nhg); - afi_t rt_afi = family2afi(rn->p.family); UNSET_FLAG(re->status, ROUTE_ENTRY_CHANGED); - /* Make a local copy of the existing nhe, so we don't work on/modify - * the shared nhe. - */ - curr_nhe = zebra_nhe_copy(re->nhe, re->nhe->id); - - if (IS_ZEBRA_DEBUG_NHG_DETAIL) - zlog_debug("%s: re %p nhe %p (%pNG), curr_nhe %p", __func__, re, - re->nhe, re->nhe, curr_nhe); - - /* Clear the existing id, if any: this will avoid any confusion - * if the id exists, and will also force the creation - * of a new nhe reflecting the changes we may make in this local copy. - */ - curr_nhe->id = 0; - /* Process nexthops */ curr_active = nexthop_list_active_update(rn, re, curr_nhe, false); @@ -3135,11 +3065,6 @@ int nexthop_active_update(struct route_node *rn, struct route_entry *re, new_nhe = zebra_nhg_rib_find_nhe(curr_nhe, rt_afi); - if (old_re && old_re->type == re->type && - old_re->instance == re->instance) - new_nhe = zebra_nhg_rib_compare_old_nhe(rn, re, new_nhe, - old_re->nhe); - if (IS_ZEBRA_DEBUG_NHG_DETAIL) zlog_debug( "%s: re %p CHANGED: nhe %p (%pNG) => new_nhe %p (%pNG)", @@ -3156,6 +3081,41 @@ int nexthop_active_update(struct route_node *rn, struct route_entry *re, if (curr_active) zebra_nhg_set_valid_if_active(re->nhe); + return curr_active; +} + +/* + * Iterate over all nexthops of the given RIB entry and refresh their + * ACTIVE flag. If any nexthop is found to toggle the ACTIVE flag, + * the whole re structure is flagged with ROUTE_ENTRY_CHANGED. + * + * Return value is the new number of active nexthops. + */ +int nexthop_active_update(struct route_node *rn, struct route_entry *re) +{ + struct nhg_hash_entry *curr_nhe; + uint32_t curr_active; + + if (PROTO_OWNED(re->nhe)) + return proto_nhg_nexthop_active_update(&re->nhe->nhg); + + /* Make a local copy of the existing nhe, so we don't work on/modify + * the shared nhe. + */ + curr_nhe = zebra_nhe_copy(re->nhe, re->nhe->id); + + if (IS_ZEBRA_DEBUG_NHG_DETAIL) + zlog_debug("%s: re %p nhe %p (%pNG), curr_nhe %p", __func__, re, + re->nhe, re->nhe, curr_nhe); + + /* Clear the existing id, if any: this will avoid any confusion + * if the id exists, and will also force the creation + * of a new nhe reflecting the changes we may make in this local copy. + */ + curr_nhe->id = 0; + + curr_active = nexthop_active_update_common(rn, re, curr_nhe); + /* * Do not need the old / copied nhe anymore since it * was either copied over into a new nhe or not @@ -3393,6 +3353,7 @@ void zebra_nhg_dplane_result(struct zebra_dplane_ctx *ctx) enum zebra_dplane_result status; uint32_t id = 0; struct nhg_hash_entry *nhe = NULL; + struct nexthop *nhop; op = dplane_ctx_get_op(ctx); status = dplane_ctx_get_status(ctx); @@ -3429,6 +3390,19 @@ void zebra_nhg_dplane_result(struct zebra_dplane_ctx *ctx) case ZEBRA_DPLANE_REQUEST_SUCCESS: SET_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED); zebra_nhg_handle_install(nhe, true); + /* update FIB flag of nexthop-group */ + for (ALL_NEXTHOPS(nhe->nhg, nhop)) { + if (!CHECK_FLAG(nhop->flags, + NEXTHOP_FLAG_ACTIVE)) + continue; + if (CHECK_FLAG(nhop->flags, + NEXTHOP_FLAG_RECURSIVE)) + continue; + if (CHECK_FLAG(nhop->flags, + NEXTHOP_FLAG_DUPLICATE)) + continue; + SET_FLAG(nhop->flags, NEXTHOP_FLAG_FIB); + } /* If daemon nhg, send it an update */ if (PROTO_OWNED(nhe)) @@ -3589,25 +3563,16 @@ bool zebra_nhg_proto_nexthops_only(void) return proto_nexthops_only; } -/* Add NHE from upper level proto */ -struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type, - uint16_t instance, uint32_t session, - struct nexthop_group *nhg, afi_t afi) +/* mark nexthops of an NHG as valid or not + * return -1 if an error happened, 0 otherwise */ +static int zebra_nhg_update_nhg_list_valid(struct nexthop_group *nhg, struct nhg_hash_entry *nhe) { - struct nhg_hash_entry lookup; - struct nhg_hash_entry *new, *old; - struct nhg_connected *rb_node_dep = NULL; struct nexthop *newhop; - bool replace = false; - int ret = 0; - - if (!nhg->nexthop) { - if (IS_ZEBRA_DEBUG_NHG) - zlog_debug("%s: id %u, no nexthops passed to add", - __func__, id); - return NULL; - } + uint32_t flags = 0, id; + int type; + id = nhe->id; + type = nhe->type; /* Set nexthop list as active, since they wont go through rib * processing. @@ -3622,7 +3587,7 @@ struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type, zlog_debug( "%s: id %u, backup nexthops not supported", __func__, id); - return NULL; + return -1; } if (newhop->type == NEXTHOP_TYPE_BLACKHOLE) { @@ -3630,7 +3595,7 @@ struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type, zlog_debug( "%s: id %u, blackhole nexthop not supported", __func__, id); - return NULL; + return -1; } if (newhop->type == NEXTHOP_TYPE_IFINDEX) { @@ -3638,28 +3603,205 @@ struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type, zlog_debug( "%s: id %u, nexthop without gateway not supported", __func__, id); - return NULL; + return -1; } - if (!newhop->ifindex) { - if (IS_ZEBRA_DEBUG_NHG) - zlog_debug( - "%s: id %u, nexthop without ifindex is not supported", - __func__, id); - return NULL; - } - SET_FLAG(newhop->flags, NEXTHOP_FLAG_ACTIVE); + if (CHECK_FLAG(nhg->flags, NEXTHOP_GROUP_ALLOW_RECURSION)) + /* Tell zebra that the route may be recursively resolved */ + flags = ZEBRA_FLAG_ALLOW_RECURSION; + + if (CHECK_FLAG(nhg->flags, NEXTHOP_GROUP_IBGP)) + /* Tell zebra that the prefix originates from an IBGP peer */ + SET_FLAG(flags, ZEBRA_FLAG_IBGP); + + if (newhop->ifindex || + nexthop_active(newhop, nhe, NULL, type, flags, NULL, + newhop->vrf_id)) + SET_FLAG(newhop->flags, NEXTHOP_FLAG_ACTIVE); + else + UNSET_FLAG(newhop->flags, NEXTHOP_FLAG_ACTIVE); } + return 0; +} + +static struct nhg_hash_entry *zebra_nhg_get_new_nhe(struct nhg_hash_entry *nhe, + struct nexthop_group *nhg, afi_t afi) +{ + struct nhg_hash_entry lookup; + struct nhg_hash_entry *new; zebra_nhe_init(&lookup, afi, nhg->nexthop); lookup.nhg.nexthop = nhg->nexthop; lookup.nhg.nhgr = nhg->nhgr; - lookup.id = id; - lookup.type = type; + lookup.id = nhe->id; + lookup.type = nhe->type; + lookup.nhg.flags = nhg->flags; + + new = zebra_nhg_rib_find_nhe(&lookup, afi); + + zebra_nhg_increment_ref(new); + + /* Capture zapi client info */ + new->zapi_instance = nhe->zapi_instance; + new->zapi_session = nhe->zapi_session; + + zebra_nhg_set_valid_if_active(new); + + zebra_nhg_install_kernel(new, ZEBRA_ROUTE_MAX); + return new; +} + +/* Check if the new nexthop has changed with the previous one + * return true if it is the case. Returns false if: + * - nexthops do not match + * - non recursive nexthops presence + * - duplicate nexthops in a same nexthop group + */ +static bool zebra_nhg_reuse_same_nhe(struct nexthop_group *old, struct nexthop_group *new) +{ + struct nexthop *newhop, *oldhop, *temp; + + oldhop = old->nexthop; + temp = NULL; + newhop = new->nexthop; + do { + if (oldhop && !CHECK_FLAG(oldhop->flags, NEXTHOP_FLAG_RECURSIVE)) + /* updating the same NHG is ok if the nexthops are recursive + */ + break; + if (oldhop == NULL && newhop == NULL) { + /* the NHG to change is exactly the same (same nexthop, + * same number of nexthops) + */ + return true; + } + if (temp && oldhop && !nexthop_cmp(temp, oldhop)) + /* duplicate nexthops may happen with recursive routes and addpath + * lets ignore this for now. + */ + break; + if (oldhop == NULL || newhop == NULL) + break; + if (nexthop_cmp(oldhop, newhop)) + break; + temp = oldhop; + oldhop = oldhop->next; + newhop = newhop->next; + } while (1); + + return false; +} + +/* refresh the nhe with the passed nhg + * - remove the current nexthop_group structure + * - update the nhg_depends dependencies + * - refresh the validity of each nexthop + */ +static void zebra_nhg_update_nhe(struct nhg_hash_entry *nhe, struct nexthop_group *new_nhg, + afi_t afi) +{ + struct nexthop *newhop, *temp; + struct nhg_connected *rb_node_dep = NULL; + struct nhg_hash_entry *nhe_temp; + uint32_t orig_refcount, old_refcount = 0, new_refcount = 0; + + /* nexthops are the same. nhg_depend list must be refreshed + */ + temp = nhe->nhg.nexthop; + nhe->nhg.nexthop = new_nhg->nexthop; + new_nhg->nexthop = NULL; + nexthops_free(temp); + + orig_refcount = nhe->refcnt; + + if (!zebra_nhg_depends_is_empty(nhe)) { + frr_each (nhg_connected_tree, &nhe->nhg_depends, rb_node_dep) { + zebra_nhg_decrement_ref(rb_node_dep->nhe); + old_refcount++; + } + nhg_connected_tree_free(&nhe->nhg_depends); + } + + /* Free all the things */ + zebra_nhg_release_all_deps(nhe); + + /* instead of creating a new nhe, just update the dependencies + * from the new nexthops + */ + for (newhop = nhe->nhg.nexthop; newhop; newhop = newhop->next) { + nhe_temp = depends_find_add(&nhe->nhg_depends, newhop, afi, nhe->type, false); + if (nhe_temp) { + zebra_nhg_increment_ref(nhe_temp); + new_refcount++; + } + } + + /* Attach dependent backpointers to singletons + */ + zebra_nhg_connect_depends(nhe, &nhe->nhg_depends); + + /* removing dependency may have invalidated + * the nexthops of the dependent nhe (see zebra_nhg_check_valid) + * update the validity of the nexthops + */ + zebra_nhg_update_nhg_list_valid(&nhe->nhg, nhe); + + /* Reset time since last update + */ + nhe->uptime = monotime(NULL); + + /* sync refcount */ + nhe->refcnt = orig_refcount - old_refcount + new_refcount; + + /* Reset install flags to force re-install of the NHG + */ + UNSET_FLAG(nhe->flags, NEXTHOP_GROUP_REINSTALL); + UNSET_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED); + UNSET_FLAG(nhe->flags, NEXTHOP_GROUP_VALID); + zebra_nhg_set_valid_if_active(nhe); + zebra_nhg_install_kernel(nhe, ZEBRA_ROUTE_MAX); +} + +/* Add NHE from upper level proto */ +struct nhg_hash_entry *zebra_nhg_proto_add(struct nhg_hash_entry *nhe, struct nexthop_group *nhg, + afi_t afi) +{ + struct nhg_hash_entry *new, *old; + struct nhg_connected *rb_node_dep = NULL; + bool replace = false; + int ret = 0; + uint32_t id; + bool reuse_same_nhe = false; + + id = nhe->id; + + if (!nhg->nexthop) { + if (IS_ZEBRA_DEBUG_NHG) + zlog_debug("%s: id %u, no nexthops passed to add", __func__, id); + return NULL; + } + + if (zebra_nhg_update_nhg_list_valid(nhg, nhe) == -1) + return NULL; old = zebra_nhg_lookup_id(id); - if (old) { + /* The below conditions are arbitrary, in order to reduce the cases where an NHG + * is eligible for update: + * - same resilience settings + * - no other action done with that NHG in ZEBRA (queued, releases, keep_around) + * - no attached to interface + */ + if (old && !CHECK_FLAG(old->flags, NEXTHOP_GROUP_PROTO_RELEASED) && + !CHECK_FLAG(old->flags, NEXTHOP_GROUP_QUEUED) && + !CHECK_FLAG(old->flags, NEXTHOP_GROUP_KEEP_AROUND) && !old->ifp && + old->nhg.flags == nhg->flags && old->nhg.nhgr.buckets == nhg->nhgr.buckets && + old->nhg.nhgr.idle_timer == nhg->nhgr.idle_timer && + old->nhg.nhgr.unbalanced_timer == nhg->nhgr.unbalanced_timer && + old->nhg.nhgr.unbalanced_time == nhg->nhgr.unbalanced_time) + reuse_same_nhe = zebra_nhg_reuse_same_nhe(&old->nhg, nhg); + + if (old && reuse_same_nhe == false) { /* * This is a replace, just release NHE from ID for now, The * depends/dependents may still be used in the replacement so @@ -3673,19 +3815,13 @@ struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type, zebra_nhg_release_all_deps(old); } - new = zebra_nhg_rib_find_nhe(&lookup, afi); - - zebra_nhg_increment_ref(new); - - /* Capture zapi client info */ - new->zapi_instance = instance; - new->zapi_session = session; - - zebra_nhg_set_valid_if_active(new); - - zebra_nhg_install_kernel(new, ZEBRA_ROUTE_MAX); + if (reuse_same_nhe) { + zebra_nhg_update_nhe(old, nhg, afi); + new = old; + } else + new = zebra_nhg_get_new_nhe(nhe, nhg, afi); - if (old) { + if (old && reuse_same_nhe == false) { /* * Check to handle recving DEL while routes still in use then * a replace. @@ -3726,8 +3862,8 @@ struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type, if (IS_ZEBRA_DEBUG_NHG_DETAIL) zlog_debug("%s: %s nhe %p (%u), vrf %d, type %s", __func__, - (replace ? "replaced" : "added"), new, new->id, - new->vrf_id, zebra_route_string(new->type)); + (replace ? "replaced" : (reuse_same_nhe ? "updated" : "added")), new, + new->id, new->vrf_id, zebra_route_string(new->type)); return new; } @@ -3933,17 +4069,6 @@ void zebra_interface_nhg_reinstall(struct interface *ifp) frr_each_safe (nhg_connected_tree, &rb_node_dep->nhe->nhg_dependents, rb_node_dependent) { - struct nexthop *nhop_dependent = - rb_node_dependent->nhe->nhg.nexthop; - - while (nhop_dependent && - !nexthop_same(nhop_dependent, nh)) - nhop_dependent = nhop_dependent->next; - - if (nhop_dependent) - SET_FLAG(nhop_dependent->flags, - NEXTHOP_FLAG_ACTIVE); - if (IS_ZEBRA_DEBUG_NHG) zlog_debug("%s dependent nhe %pNG Setting Reinstall flag", __func__, diff --git a/zebra/zebra_nhg.h b/zebra/zebra_nhg.h index 0f90627a0d15..c2aa69d14d0f 100644 --- a/zebra/zebra_nhg.h +++ b/zebra/zebra_nhg.h @@ -91,6 +91,10 @@ struct nhg_hash_entry { struct event *timer; + /* list of prefixes using that nexthop group + * only populated for protocol nexthop groups + */ + struct hash *prefix_proto_nhgs; /* * Is this nexthop group valid, ie all nexthops are fully resolved. * What is fully resolved? It's a nexthop that is either self contained @@ -171,6 +175,12 @@ struct nhg_hash_entry { * chooses this NHG then we can install it then. */ #define NEXTHOP_GROUP_INITIAL_DELAY_INSTALL (1 << 9) + +/* at NHG_ADD, when an update of an already installed NHG happens, + * the nhg_depends must be refreshed. This flag is used to check + * if the dependency still exists or has been removed. + */ +#define NEXTHOP_GROUP_DEPEND_TO_DETACH (1 << 11) }; /* Upper 4 bits of the NHG are reserved for indicating the NHG type */ @@ -244,6 +254,18 @@ struct nhg_ctx { enum nhg_ctx_status status; }; +struct nhg_prefix_proto_nhgs { + /* key */ + struct prefix prefix; + struct prefix src_prefix; + afi_t afi; + safi_t safi; + uint32_t table_id; + vrf_id_t vrf_id; + + struct nhg_hash_entry *nhe; +}; + /* Global control to disable use of kernel nexthops, if available. We can't * force the kernel to support nexthop ids, of course, but we can disable * zebra's use of them, for testing e.g. By default, if the kernel supports @@ -283,6 +305,13 @@ void zebra_nhe_init(struct nhg_hash_entry *nhe, afi_t afi, struct nhg_hash_entry *zebra_nhe_copy(const struct nhg_hash_entry *orig, uint32_t id); +/* Allocate/Free prefix_proto_nhgs object */ +void *zebra_nhg_prefix_proto_nhgs_alloc(void *arg); +void zebra_nhg_prefix_proto_nhgs_hash_free(void *p); +/* Utility to copy hash list of prefixes in a new nhg */ +void zebra_nhg_prefix_copy(struct nhg_hash_entry *new, + struct nhg_hash_entry *old); + /* Allocate, free backup nexthop info objects */ struct nhg_backup_info *zebra_nhg_backup_alloc(void); void zebra_nhg_backup_free(struct nhg_backup_info **p); @@ -347,10 +376,8 @@ zebra_nhg_rib_find_nhe(struct nhg_hash_entry *rt_nhe, afi_t rt_afi); * * Returns allocated NHE on success, otherwise NULL. */ -struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type, - uint16_t instance, uint32_t session, - struct nexthop_group *nhg, - afi_t afi); +struct nhg_hash_entry *zebra_nhg_proto_add(struct nhg_hash_entry *nhe, + struct nexthop_group *nhg, afi_t afi); /* * Del NHE. @@ -401,8 +428,7 @@ extern void zebra_nhg_mark_keep(void); /* Nexthop resolution processing */ struct route_entry; /* Forward ref to avoid circular includes */ -extern int nexthop_active_update(struct route_node *rn, struct route_entry *re, - struct route_entry *old_re); +extern int nexthop_active_update(struct route_node *rn, struct route_entry *re); #ifdef _FRR_ATTRIBUTE_PRINTFRR #pragma FRR printfrr_ext "%pNG" (const struct nhg_hash_entry *) diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c index 72421dc8ee15..55422501aaad 100644 --- a/zebra/zebra_rib.c +++ b/zebra/zebra_rib.c @@ -424,6 +424,9 @@ int route_entry_update_nhe(struct route_entry *re, int ret = 0; struct nhg_hash_entry *old_nhg = NULL; + if (re->nhe == new_nhghe) + return ret; + if (new_nhghe == NULL) { old_nhg = re->nhe; @@ -468,6 +471,8 @@ int rib_handle_nhg_replace(struct nhg_hash_entry *old_entry, struct route_entry *re, *next; int ret = 0; + if (old_entry == new_entry) + return ret; if (IS_ZEBRA_DEBUG_RIB_DETAILED || IS_ZEBRA_DEBUG_NHG_DETAIL) zlog_debug("%s: replacing routes nhe (%u) OLD %p NEW %p", __func__, new_entry->id, new_entry, old_entry); @@ -484,6 +489,8 @@ int rib_handle_nhg_replace(struct nhg_hash_entry *old_entry, } } + zebra_nhg_prefix_copy(new_entry, old_entry); + /* * if ret > 0, some previous re->nhe has freed the address to which * old_entry is pointing. @@ -1338,7 +1345,7 @@ static void rib_process(struct route_node *rn) */ if (CHECK_FLAG(re->status, ROUTE_ENTRY_CHANGED)) { proto_re_changed = re; - if (!nexthop_active_update(rn, re, old_fib)) { + if (!nexthop_active_update(rn, re)) { const struct prefix *p; struct rib_table_info *info; @@ -1769,6 +1776,7 @@ static bool rib_update_re_from_ctx(struct route_entry *re, bool changed_p = false; /* Change to nexthops? */ rib_dest_t *dest; struct vrf *vrf; + uint32_t ctxnhgid; vrf = vrf_lookup_by_id(re->vrf_id); @@ -1788,6 +1796,7 @@ static bool rib_update_re_from_ctx(struct route_entry *re, */ matched = false; ctxnhg = dplane_ctx_get_ng(ctx); + ctxnhgid = dplane_ctx_get_nhg_id(ctx); /* Check route's fib group and incoming notif group for equivalence. * @@ -1848,6 +1857,9 @@ static bool rib_update_re_from_ctx(struct route_entry *re, goto no_nexthops; } + if (ctxnhgid >= ZEBRA_NHG_PROTO_LOWER) + nexthop_group_mark_duplicates(&(re->nhe->nhg)); + matched = rib_update_nhg_from_ctx(&(re->nhe->nhg), ctxnhg, &changed_p); /* If all nexthops were processed, we're done */ @@ -1999,7 +2011,86 @@ struct route_node *rib_find_rn_from_ctx(const struct zebra_dplane_ctx *ctx) } +static void zebra_rib_evaluate_prefix_nhg(struct hash_bucket *b, void *data) +{ + struct zebra_router_table *zrt; + struct nhg_prefix_proto_nhgs *nhg_p = b->data; + struct zebra_vrf *zvrf = zebra_vrf_lookup_by_id(nhg_p->vrf_id); + struct route_node *rn; + struct route_entry *re, *next; + rib_dest_t *dest; + + if (!zvrf) + return; + + zrt = zebra_router_find_zrt(zvrf, nhg_p->table_id, nhg_p->afi, + nhg_p->safi); + if (!zrt) + return; + + if (nhg_p->src_prefix.family) + rn = srcdest_rnode_get(zrt->table, &nhg_p->prefix, + (const struct prefix_ipv6 *)&nhg_p + ->src_prefix); + else + rn = srcdest_rnode_get(zrt->table, &nhg_p->prefix, NULL); + if (!rn) + return; + + if (IS_ZEBRA_DEBUG_NHG_DETAIL) + zlog_debug("%s: => NHG updated, evaluating prefix %pRN", + __func__, rn); + + RNODE_FOREACH_RE_SAFE (rn, re, next) { + /* re did not change, an, still selected */ + dest = rib_dest_from_rnode(rn); + /* Redistribute if this is the selected re */ + if (dest && re == dest->selected_fib) + redistribute_update(rn, re, re); + zebra_rib_evaluate_rn_nexthops(rn, + zebra_router_get_next_sequence(), + false); + zebra_rib_evaluate_mpls(rn); + } +} +/* + * update results processing after async dataplane update. + */ +static void rib_nhg_process_result(struct zebra_dplane_ctx *ctx) +{ + enum dplane_op_e op; + enum zebra_dplane_result status; + uint32_t id; + struct nhg_hash_entry *nhe; + + op = dplane_ctx_get_op(ctx); + status = dplane_ctx_get_status(ctx); + + id = dplane_ctx_get_nhe_id(ctx); + + if (op == DPLANE_OP_NH_DELETE) + /* We already free'd the data, nothing to do */ + return; + + nhe = zebra_nhg_lookup_id(id); + if (!nhe) + /* nothing to process, as there is no NHG add or update */ + return; + + if (status != ZEBRA_DPLANE_REQUEST_SUCCESS) + /* no sucess, means that the attempt to change did not work */ + return; + + if (IS_ZEBRA_DEBUG_RIB_DETAILED || IS_ZEBRA_DEBUG_NHG_DETAIL) + zlog_debug("%s: Evaluating routes using updated nhe (%u)", + __func__, id); + + /* We have to do them ALL */ + if (nhe->prefix_proto_nhgs) + hash_iterate(nhe->prefix_proto_nhgs, + zebra_rib_evaluate_prefix_nhg, NULL); +} /* * Route-update results processing after async dataplane update. */ @@ -2017,6 +2108,10 @@ static void rib_process_result(struct zebra_dplane_ctx *ctx) bool fib_changed = false; struct rib_table_info *info; bool rt_delete = false; + struct nhg_hash_entry *nhe; + struct nhg_prefix_proto_nhgs nhg_p = {}, *p_nhg_p; + const struct prefix *p; + const struct prefix *src_p = NULL; zvrf = zebra_vrf_lookup_by_id(dplane_ctx_get_vrf(ctx)); vrf = vrf_lookup_by_id(dplane_ctx_get_vrf(ctx)); @@ -2110,6 +2205,25 @@ static void rib_process_result(struct zebra_dplane_ctx *ctx) } if (op == DPLANE_OP_ROUTE_INSTALL || op == DPLANE_OP_ROUTE_UPDATE) { + if (re && re->nhe_id >= ZEBRA_NHG_PROTO_LOWER) { + nhe = zebra_nhg_lookup_id(re->nhe_id); + if (nhe) { + srcdest_rnode_prefixes(rn, &p, &src_p); + prefix_copy(&nhg_p.prefix, p); + if (src_p) + prefix_copy(&nhg_p.src_prefix, src_p); + nhg_p.table_id = dplane_ctx_get_table(ctx); + nhg_p.afi = dplane_ctx_get_afi(ctx); + nhg_p.safi = dplane_ctx_get_safi(ctx); + nhg_p.vrf_id = dplane_ctx_get_vrf(ctx); + nhg_p.nhe = nhe; + hash_get(nhe->prefix_proto_nhgs, &nhg_p, + zebra_nhg_prefix_proto_nhgs_alloc); + if (IS_ZEBRA_DEBUG_NHG_DETAIL) + zlog_debug("%s: => attach prefix %pFX to NHG (%pNG)", + __func__, p, nhe); + } + } if (status == ZEBRA_DPLANE_REQUEST_SUCCESS) { if (re) { UNSET_FLAG(re->status, ROUTE_ENTRY_FAILED); @@ -2213,6 +2327,32 @@ static void rib_process_result(struct zebra_dplane_ctx *ctx) if (re) { UNSET_FLAG(re->status, ROUTE_ENTRY_INSTALLED); UNSET_FLAG(re->status, ROUTE_ENTRY_FAILED); + + if (re->nhe && re->nhe->prefix_proto_nhgs) { + /* remove prefix from nhe_id list */ + srcdest_rnode_prefixes(rn, &p, &src_p); + prefix_copy(&nhg_p.prefix, p); + if (src_p) + prefix_copy(&nhg_p.src_prefix, + src_p); + nhg_p.table_id = + dplane_ctx_get_table(ctx); + nhg_p.afi = dplane_ctx_get_afi(ctx); + nhg_p.safi = dplane_ctx_get_safi(ctx); + nhg_p.vrf_id = dplane_ctx_get_vrf(ctx); + p_nhg_p = + hash_lookup(re->nhe->prefix_proto_nhgs, + &nhg_p); + if (IS_ZEBRA_DEBUG_NHG_DETAIL) + zlog_debug("%s: => detach prefix %pFX from NHG (%pNG)", + __func__, p, re->nhe); + if (p_nhg_p) { + hash_release(re->nhe->prefix_proto_nhgs, + p_nhg_p); + zebra_nhg_prefix_proto_nhgs_hash_free( + p_nhg_p); + } + } } zsend_route_notify_owner_ctx(ctx, ZAPI_ROUTE_REMOVED); @@ -2620,10 +2760,7 @@ static void process_subq_nhg(struct listnode *lnode) ZAPI_NHG_REMOVE_FAIL); } else { - newnhe = zebra_nhg_proto_add(nhe->id, nhe->type, - nhe->zapi_instance, - nhe->zapi_session, - &nhe->nhg, 0); + newnhe = zebra_nhg_proto_add(nhe, &nhe->nhg, 0); /* Report error to daemon via ZAPI */ if (newnhe == NULL) @@ -5001,6 +5138,8 @@ static void rib_process_dplane_results(struct event *thread) case DPLANE_OP_NH_UPDATE: case DPLANE_OP_NH_DELETE: zebra_nhg_dplane_result(ctx); + if (dplane_ctx_get_notif_provider(ctx) == 0) + rib_nhg_process_result(ctx); break; case DPLANE_OP_LSP_INSTALL: diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c index 3bf20ff42e7c..0ca4fb6b717c 100644 --- a/zebra/zebra_vty.c +++ b/zebra/zebra_vty.c @@ -1160,8 +1160,54 @@ DEFPY (show_ip_nht, return CMD_SUCCESS; } +struct show_nhe_prefix_list_context { + struct vty *vty; + json_object *json; +}; + +static void show_nhe_prefix_proto_nhgs_entry(struct hash_bucket *bucket, + void *data) +{ + struct vty *vty; + json_object *json = NULL; + json_object *json_entry = NULL; + struct nhg_prefix_proto_nhgs *entry = bucket->data; + struct show_nhe_prefix_list_context *ctx = data; + struct vrf *vrf; + + vty = ctx->vty; + json = ctx->json; + if (json) { + json_entry = json_object_new_object(); + json_object_string_addf(json_entry, "prefix", "%pFX", + &entry->prefix); + if (entry->prefix.family == AF_INET6 && + entry->src_prefix.family != AF_UNSPEC) + json_object_string_addf(json_entry, "srcPrefix", "%pFX", + &entry->src_prefix); + json_object_string_add(json_entry, "afi", afi2str(entry->afi)); + json_object_string_add(json_entry, "safi", + safi2str(entry->safi)); + json_object_int_add(json_entry, "table", entry->table_id); + json_object_int_add(json_entry, "vrfId", entry->vrf_id); + json_object_string_add(json_entry, "vrfName", + vrf_id_to_name(entry->vrf_id)); + json_object_array_add(json, json_entry); + } else if (vty) { + vrf = vrf_lookup_by_id(entry->vrf_id); + + vty_out(vty, " %pFX", &entry->prefix); + if (entry->prefix.family == AF_INET6 && + entry->src_prefix.family != AF_UNSPEC) + vty_out(vty, ", src %pFX", &entry->src_prefix); + vty_out(vty, ", afi %s, safi %s, table %d (vrf %s)\n", + afi2str(entry->afi), safi2str(entry->safi), + entry->table_id, VRF_LOGNAME(vrf)); + } +} + static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe, - json_object *json_nhe_hdr) + bool detail, json_object *json_nhe_hdr) { struct nexthop *nexthop = NULL; struct nhg_connected *rb_node_dep = NULL; @@ -1175,7 +1221,8 @@ static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe, json_object *json = NULL; json_object *json_backup_nexthop_array = NULL; json_object *json_backup_nexthops = NULL; - + json_object *json_prefix_list = NULL; + struct show_nhe_prefix_list_context ctx; uptime2str(nhe->uptime, up_str, sizeof(up_str)); @@ -1196,7 +1243,6 @@ static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe, json_object_string_add(json, "vrf", vrf_id_to_name(nhe->vrf_id)); json_object_string_add(json, "afi", afi2str(nhe->afi)); - } else { vty_out(vty, "ID: %u (%s)\n", nhe->id, zebra_route_string(nhe->type)); @@ -1411,17 +1457,31 @@ static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe, if (json_nhe_hdr) json_object_object_addf(json_nhe_hdr, json, "%u", nhe->id); + + if (detail && nhe->id >= ZEBRA_NHG_PROTO_LOWER) { + if (json) + json_prefix_list = json_object_new_array(); + else + vty_out(vty, " Prefix list:\n"); + ctx.vty = vty; + ctx.json = json_prefix_list; + hash_iterate(nhe->prefix_proto_nhgs, + show_nhe_prefix_proto_nhgs_entry, &ctx); + if (json) + json_object_object_add(json, "prefixList", + json_prefix_list); + } } static int show_nexthop_group_id_cmd_helper(struct vty *vty, uint32_t id, - json_object *json) + bool detail, json_object *json) { struct nhg_hash_entry *nhe = NULL; nhe = zebra_nhg_lookup_id(id); if (nhe) - show_nexthop_group_out(vty, nhe, json); + show_nexthop_group_out(vty, nhe, detail, json); else { if (json) vty_json(vty, json); @@ -1445,6 +1505,7 @@ struct nhe_show_context { afi_t afi; int type; json_object *json; + bool detail; }; static int nhe_show_walker(struct hash_bucket *bucket, void *arg) @@ -1463,7 +1524,7 @@ static int nhe_show_walker(struct hash_bucket *bucket, void *arg) if (ctx->type && nhe->type != ctx->type) goto done; - show_nexthop_group_out(ctx->vty, nhe, ctx->json); + show_nexthop_group_out(ctx->vty, nhe, ctx->detail, ctx->json); done: return HASHWALK_CONTINUE; @@ -1471,7 +1532,8 @@ static int nhe_show_walker(struct hash_bucket *bucket, void *arg) static void show_nexthop_group_cmd_helper(struct vty *vty, struct zebra_vrf *zvrf, afi_t afi, - int type, json_object *json) + int type, bool detail, + json_object *json) { struct nhe_show_context ctx; @@ -1480,6 +1542,7 @@ static void show_nexthop_group_cmd_helper(struct vty *vty, ctx.vrf_id = zvrf->vrf->vrf_id; ctx.type = type; ctx.json = json; + ctx.detail = detail; hash_walk(zrouter.nhgs_id, nhe_show_walker, &ctx); } @@ -1499,7 +1562,7 @@ static void if_nexthop_group_dump_vty(struct vty *vty, struct interface *ifp) } vty_out(vty, " "); - show_nexthop_group_out(vty, rb_node_dep->nhe, NULL); + show_nexthop_group_out(vty, rb_node_dep->nhe, false, NULL); } } @@ -1539,7 +1602,7 @@ DEFPY (show_interface_nexthop_group, DEFPY(show_nexthop_group, show_nexthop_group_cmd, - "show nexthop-group rib <(0-4294967295)$id|[singleton ] [$type_str] [vrf ]> [json]", + "show nexthop-group rib <(0-4294967295)$id|[singleton ] [$type_str] [vrf ]> [detail$detail] [json]", SHOW_STR "Show Nexthop Groups\n" "RIB information\n" @@ -1552,6 +1615,7 @@ DEFPY(show_nexthop_group, "Border Gateway Protocol (BGP)\n" "Super Happy Advanced Routing Protocol (SHARP)\n" VRF_FULL_CMD_HELP_STR + "Show detailed information\n" JSON_STR) { @@ -1566,7 +1630,7 @@ DEFPY(show_nexthop_group, json = json_object_new_object(); if (id) - return show_nexthop_group_id_cmd_helper(vty, id, json); + return show_nexthop_group_id_cmd_helper(vty, id, !!detail, json); if (v4) afi = AFI_IP; @@ -1605,7 +1669,7 @@ DEFPY(show_nexthop_group, vty_out(vty, "VRF: %s\n", vrf->name); show_nexthop_group_cmd_helper(vty, zvrf, afi, type, - json_vrf); + !!detail, json_vrf); if (uj) json_object_object_add(json, vrf->name, json_vrf); @@ -1631,7 +1695,7 @@ DEFPY(show_nexthop_group, return CMD_WARNING; } - show_nexthop_group_cmd_helper(vty, zvrf, afi, type, json); + show_nexthop_group_cmd_helper(vty, zvrf, afi, type, !!detail, json); if (uj) vty_json(vty, json);