diff options
50 files changed, 7005 insertions, 245 deletions
diff --git a/bgpd/bgp_advertise.h b/bgpd/bgp_advertise.h index f2cf78efde..745a0dffce 100644 --- a/bgpd/bgp_advertise.h +++ b/bgpd/bgp_advertise.h @@ -83,6 +83,9 @@ struct bgp_adj_out { /* Advertisement information. */ struct bgp_advertise *adv; + + /* Attribute hash */ + uint32_t attr_hash; }; RB_HEAD(bgp_adj_out_rb, bgp_adj_out); diff --git a/bgpd/bgp_clist.c b/bgpd/bgp_clist.c index 247d758f8c..6ac6cf56dd 100644 --- a/bgpd/bgp_clist.c +++ b/bgpd/bgp_clist.c @@ -1110,11 +1110,14 @@ struct lcommunity *lcommunity_list_match_delete(struct lcommunity *lcom, } /* Helper to check if every octet do not exceed UINT_MAX */ -static bool lcommunity_list_valid(const char *community) +bool lcommunity_list_valid(const char *community, int style) { int octets; char **splits, **communities; + char *endptr; int num, num_communities; + regex_t *regres; + int invalid = 0; frrstr_split(community, " ", &communities, &num_communities); @@ -1123,25 +1126,43 @@ static bool lcommunity_list_valid(const char *community) frrstr_split(communities[j], ":", &splits, &num); for (int i = 0; i < num; i++) { - if (strtoul(splits[i], NULL, 10) > UINT_MAX) - return false; - if (strlen(splits[i]) == 0) - return false; + /* There is no digit to check */ + invalid++; + + if (style == LARGE_COMMUNITY_LIST_STANDARD) { + if (*splits[i] == '-') + /* Must not be negative */ + invalid++; + else if (strtoul(splits[i], &endptr, 10) + > UINT_MAX) + /* Larger than 4 octets */ + invalid++; + else if (*endptr) + /* Not all characters were digits */ + invalid++; + } else { + regres = bgp_regcomp(communities[j]); + if (!regres) + /* malformed regex */ + invalid++; + else + bgp_regex_free(regres); + } octets++; XFREE(MTYPE_TMP, splits[i]); } XFREE(MTYPE_TMP, splits); - if (octets < 3) - return false; + if (octets != 3) + invalid++; XFREE(MTYPE_TMP, communities[j]); } XFREE(MTYPE_TMP, communities); - return true; + return (invalid > 0) ? false : true; } /* Set lcommunity-list. */ @@ -1176,7 +1197,7 @@ int lcommunity_list_set(struct community_list_handler *ch, const char *name, } if (str) { - if (!lcommunity_list_valid(str)) + if (!lcommunity_list_valid(str, style)) return COMMUNITY_LIST_ERR_MALFORMED_VAL; if (style == LARGE_COMMUNITY_LIST_STANDARD) diff --git a/bgpd/bgp_clist.h b/bgpd/bgp_clist.h index f7d46525a0..632e1730cc 100644 --- a/bgpd/bgp_clist.h +++ b/bgpd/bgp_clist.h @@ -154,6 +154,7 @@ extern int extcommunity_list_unset(struct community_list_handler *ch, extern int lcommunity_list_set(struct community_list_handler *ch, const char *name, const char *str, const char *seq, int direct, int style); +extern bool lcommunity_list_valid(const char *community, int style); extern int lcommunity_list_unset(struct community_list_handler *ch, const char *name, const char *str, const char *seq, int direct, int style); diff --git a/bgpd/bgp_lcommunity.c b/bgpd/bgp_lcommunity.c index 5900fcf862..88a85c979e 100644 --- a/bgpd/bgp_lcommunity.c +++ b/bgpd/bgp_lcommunity.c @@ -348,16 +348,12 @@ void lcommunity_finish(void) lcomhash = NULL; } -/* Large Communities token enum. */ -enum lcommunity_token { - lcommunity_token_unknown = 0, - lcommunity_token_val, -}; - -/* Get next Large Communities token from the string. */ +/* Get next Large Communities token from the string. + * Assumes str is space-delimeted and describes 0 or more + * valid large communities + */ static const char *lcommunity_gettoken(const char *str, - struct lcommunity_val *lval, - enum lcommunity_token *token) + struct lcommunity_val *lval) { const char *p = str; @@ -372,60 +368,55 @@ static const char *lcommunity_gettoken(const char *str, return NULL; /* Community value. */ - if (isdigit((unsigned char)*p)) { - int separator = 0; - int digit = 0; - uint32_t globaladmin = 0; - uint32_t localdata1 = 0; - uint32_t localdata2 = 0; - - while (isdigit((unsigned char)*p) || *p == ':') { - if (*p == ':') { - if (separator == 2) { - *token = lcommunity_token_unknown; - return NULL; - } else { - separator++; - digit = 0; - if (separator == 1) { - globaladmin = localdata2; - } else { - localdata1 = localdata2; - } - localdata2 = 0; - } + int separator = 0; + int digit = 0; + uint32_t globaladmin = 0; + uint32_t localdata1 = 0; + uint32_t localdata2 = 0; + + while (*p && *p != ' ') { + /* large community valid chars */ + assert(isdigit((unsigned char)*p) || *p == ':'); + + if (*p == ':') { + separator++; + digit = 0; + if (separator == 1) { + globaladmin = localdata2; } else { - digit = 1; - localdata2 *= 10; - localdata2 += (*p - '0'); + localdata1 = localdata2; } - p++; + localdata2 = 0; + } else { + digit = 1; + /* left shift the accumulated value and add current + * digit + */ + localdata2 *= 10; + localdata2 += (*p - '0'); } - if (!digit) { - *token = lcommunity_token_unknown; - return NULL; - } - - /* - * Copy the large comm. - */ - lval->val[0] = (globaladmin >> 24) & 0xff; - lval->val[1] = (globaladmin >> 16) & 0xff; - lval->val[2] = (globaladmin >> 8) & 0xff; - lval->val[3] = globaladmin & 0xff; - lval->val[4] = (localdata1 >> 24) & 0xff; - lval->val[5] = (localdata1 >> 16) & 0xff; - lval->val[6] = (localdata1 >> 8) & 0xff; - lval->val[7] = localdata1 & 0xff; - lval->val[8] = (localdata2 >> 24) & 0xff; - lval->val[9] = (localdata2 >> 16) & 0xff; - lval->val[10] = (localdata2 >> 8) & 0xff; - lval->val[11] = localdata2 & 0xff; - - *token = lcommunity_token_val; - return p; + p++; } - *token = lcommunity_token_unknown; + + /* Assert str was a valid large community */ + assert(separator == 2 && digit == 1); + + /* + * Copy the large comm. + */ + lval->val[0] = (globaladmin >> 24) & 0xff; + lval->val[1] = (globaladmin >> 16) & 0xff; + lval->val[2] = (globaladmin >> 8) & 0xff; + lval->val[3] = globaladmin & 0xff; + lval->val[4] = (localdata1 >> 24) & 0xff; + lval->val[5] = (localdata1 >> 16) & 0xff; + lval->val[6] = (localdata1 >> 8) & 0xff; + lval->val[7] = localdata1 & 0xff; + lval->val[8] = (localdata2 >> 24) & 0xff; + lval->val[9] = (localdata2 >> 16) & 0xff; + lval->val[10] = (localdata2 >> 8) & 0xff; + lval->val[11] = localdata2 & 0xff; + return p; } @@ -439,23 +430,16 @@ static const char *lcommunity_gettoken(const char *str, struct lcommunity *lcommunity_str2com(const char *str) { struct lcommunity *lcom = NULL; - enum lcommunity_token token = lcommunity_token_unknown; struct lcommunity_val lval; + if (!lcommunity_list_valid(str, LARGE_COMMUNITY_LIST_STANDARD)) + return NULL; + do { - str = lcommunity_gettoken(str, &lval, &token); - switch (token) { - case lcommunity_token_val: - if (lcom == NULL) - lcom = lcommunity_new(); - lcommunity_add_val(lcom, &lval); - break; - case lcommunity_token_unknown: - default: - if (lcom) - lcommunity_free(&lcom); - return NULL; - } + str = lcommunity_gettoken(str, &lval); + if (lcom == NULL) + lcom = lcommunity_new(); + lcommunity_add_val(lcom, &lval); } while (str); return lcom; diff --git a/bgpd/bgp_lcommunity.h b/bgpd/bgp_lcommunity.h index c96df8482d..6ccb6b7879 100644 --- a/bgpd/bgp_lcommunity.h +++ b/bgpd/bgp_lcommunity.h @@ -23,6 +23,7 @@ #include "lib/json.h" #include "bgpd/bgp_route.h" +#include "bgpd/bgp_clist.h" /* Large Communities value is twelve octets long. */ #define LCOMMUNITY_SIZE 12 diff --git a/bgpd/bgp_nb.c b/bgpd/bgp_nb.c index f098332b29..08ec64242d 100644 --- a/bgpd/bgp_nb.c +++ b/bgpd/bgp_nb.c @@ -352,6 +352,13 @@ const struct frr_yang_module_info frr_bgp_info = { } }, { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-bgp:bgp/global/suppress-duplicates", + .cbs = { + .cli_show = cli_show_router_bgp_suppress_duplicates, + .modify = bgp_global_suppress_duplicates_modify, + } + }, + { .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-bgp:bgp/global/show-hostname", .cbs = { .cli_show = cli_show_router_bgp_show_hostname, diff --git a/bgpd/bgp_nb.h b/bgpd/bgp_nb.h index f608d4f8c1..9c81c2457e 100644 --- a/bgpd/bgp_nb.h +++ b/bgpd/bgp_nb.h @@ -127,6 +127,7 @@ int bgp_global_fast_external_failover_modify(struct nb_cb_modify_args *args); int bgp_global_local_pref_modify(struct nb_cb_modify_args *args); int bgp_global_default_shutdown_modify(struct nb_cb_modify_args *args); int bgp_global_ebgp_requires_policy_modify(struct nb_cb_modify_args *args); +int bgp_global_suppress_duplicates_modify(struct nb_cb_modify_args *args); int bgp_global_show_hostname_modify(struct nb_cb_modify_args *args); int bgp_global_show_nexthop_hostname_modify(struct nb_cb_modify_args *args); int bgp_global_import_check_modify(struct nb_cb_modify_args *args); @@ -3639,6 +3640,9 @@ void cli_show_router_bgp_route_selection(struct vty *vty, void cli_show_router_bgp_ebgp_requires_policy(struct vty *vty, struct lyd_node *dnode, bool show_defaults); +void cli_show_router_bgp_suppress_duplicates(struct vty *vty, + struct lyd_node *dnode, + bool show_defaults); void cli_show_router_bgp_default_shutdown(struct vty *vty, struct lyd_node *dnode, bool show_defaults); diff --git a/bgpd/bgp_nb_config.c b/bgpd/bgp_nb_config.c index ec3b0c13a1..bee9633297 100644 --- a/bgpd/bgp_nb_config.c +++ b/bgpd/bgp_nb_config.c @@ -1599,6 +1599,27 @@ int bgp_global_ebgp_requires_policy_modify(struct nb_cb_modify_args *args) /* * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-bgp:bgp/global/suppress-duplicates + */ +int bgp_global_suppress_duplicates_modify(struct nb_cb_modify_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + struct bgp *bgp; + + bgp = nb_running_get_entry(args->dnode, NULL, true); + + if (yang_dnode_get_bool(args->dnode, NULL)) + SET_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_DUPLICATES); + else + UNSET_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_DUPLICATES); + + return NB_OK; +} + +/* + * XPath: * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-bgp:bgp/global/show-hostname */ int bgp_global_show_hostname_modify(struct nb_cb_modify_args *args) diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index dfc64c043b..b2b9e04bc3 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -444,6 +444,13 @@ int bgp_generate_updgrp_packets(struct thread *thread) * yet. */ if (!next_pkt || !next_pkt->buffer) { + /* Make sure we supress BGP UPDATES + * for normal processing later again. + */ + if (!paf->t_announce_route) + UNSET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_FORCE_UPDATES); + if (CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV)) { if (!(PAF_SUBGRP(paf))->t_coalesce @@ -2116,6 +2123,11 @@ static int bgp_route_refresh_receive(struct peer *peer, bgp_size_t size) peer->orf_plist[afi][safi]; } + /* Avoid supressing duplicate routes later + * when processing in subgroup_announce_table(). + */ + SET_FLAG(paf->subgroup->sflags, SUBGRP_STATUS_FORCE_UPDATES); + /* If the peer is configured for default-originate clear the * SUBGRP_STATUS_DEFAULT_ORIGINATE flag so that we will * re-advertise the diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 60ad8d20e9..7e1f7df533 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -10261,6 +10261,24 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, str, label2vni(&attr->label)); } + /* Output some debug about internal state of the dest flags */ + if (json_paths) { + if (CHECK_FLAG(bn->flags, BGP_NODE_PROCESS_SCHEDULED)) + json_object_boolean_true_add(json_path, "processScheduled"); + if (CHECK_FLAG(bn->flags, BGP_NODE_USER_CLEAR)) + json_object_boolean_true_add(json_path, "userCleared"); + if (CHECK_FLAG(bn->flags, BGP_NODE_LABEL_CHANGED)) + json_object_boolean_true_add(json_path, "labelChanged"); + if (CHECK_FLAG(bn->flags, BGP_NODE_REGISTERED_FOR_LABEL)) + json_object_boolean_true_add(json_path, "registeredForLabel"); + if (CHECK_FLAG(bn->flags, BGP_NODE_SELECT_DEFER)) + json_object_boolean_true_add(json_path, "selectDefered"); + if (CHECK_FLAG(bn->flags, BGP_NODE_FIB_INSTALLED)) + json_object_boolean_true_add(json_path, "fibInstalled"); + if (CHECK_FLAG(bn->flags, BGP_NODE_FIB_INSTALL_PENDING)) + json_object_boolean_true_add(json_path, "fibPending"); + } + /* We've constructed the json object for this path, add it to the json * array of paths */ diff --git a/bgpd/bgp_updgrp.c b/bgpd/bgp_updgrp.c index 2788a8ea4f..059e05ef71 100644 --- a/bgpd/bgp_updgrp.c +++ b/bgpd/bgp_updgrp.c @@ -1387,6 +1387,11 @@ static int updgrp_policy_update_walkcb(struct update_group *updgrp, void *arg) } UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + /* Avoid supressing duplicate routes later + * when processing in subgroup_announce_table(). + */ + SET_FLAG(subgrp->sflags, SUBGRP_STATUS_FORCE_UPDATES); + if (changed) { if (bgp_debug_update(NULL, NULL, updgrp, 0)) zlog_debug( diff --git a/bgpd/bgp_updgrp.h b/bgpd/bgp_updgrp.h index 9cad78c26d..7261933dc9 100644 --- a/bgpd/bgp_updgrp.h +++ b/bgpd/bgp_updgrp.h @@ -252,20 +252,14 @@ struct update_subgroup { uint64_t id; uint16_t sflags; +#define SUBGRP_STATUS_DEFAULT_ORIGINATE (1 << 0) +#define SUBGRP_STATUS_FORCE_UPDATES (1 << 1) - /* Subgroup flags, see below */ uint16_t flags; +#define SUBGRP_FLAG_NEEDS_REFRESH (1 << 0) }; /* - * We need to do an outbound refresh to get this subgroup into a - * consistent state. - */ -#define SUBGRP_FLAG_NEEDS_REFRESH (1 << 0) - -#define SUBGRP_STATUS_DEFAULT_ORIGINATE (1 << 0) - -/* * Add the given value to the specified counter on a subgroup and its * parent structures. */ diff --git a/bgpd/bgp_updgrp_adv.c b/bgpd/bgp_updgrp_adv.c index da9e1f28ae..b1ff9ac251 100644 --- a/bgpd/bgp_updgrp_adv.c +++ b/bgpd/bgp_updgrp_adv.c @@ -464,6 +464,7 @@ void bgp_adj_out_set_subgroup(struct bgp_dest *dest, struct peer *adv_peer; struct peer_af *paf; struct bgp *bgp; + uint32_t attr_hash = attrhash_key_make(attr); peer = SUBGRP_PEER(subgrp); afi = SUBGRP_AFI(subgrp); @@ -487,6 +488,26 @@ void bgp_adj_out_set_subgroup(struct bgp_dest *dest, return; } + /* Check if we are sending the same route. This is needed to + * avoid duplicate UPDATES. For instance, filtering communities + * at egress, neighbors will see duplicate UPDATES despite + * the route wasn't changed actually. + * Do not suppress BGP UPDATES for route-refresh. + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_DUPLICATES) + && !CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_FORCE_UPDATES) + && adj->attr_hash == attr_hash) { + if (BGP_DEBUG(update, UPDATE_OUT)) { + char attr_str[BUFSIZ] = {0}; + + bgp_dump_attr(attr, attr_str, sizeof(attr_str)); + + zlog_debug("%s suppress UPDATE w/ attr: %s", peer->host, + attr_str); + } + return; + } + if (adj->adv) bgp_advertise_clean_subgroup(subgrp, adj); adj->adv = bgp_advertise_new(); @@ -502,6 +523,7 @@ void bgp_adj_out_set_subgroup(struct bgp_dest *dest, else adv->baa = baa_new(); adv->adj = adj; + adj->attr_hash = attr_hash; /* Add new advertisement to advertisement attribute list. */ bgp_advertise_add(adv->baa, adv); diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index cfac0bbb54..4cdd4d2e62 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -119,6 +119,10 @@ FRR_CFG_DEFAULT_BOOL(BGP_EBGP_REQUIRES_POLICY, { .val_bool = false, .match_version = "< 7.4", }, { .val_bool = true }, ) +FRR_CFG_DEFAULT_BOOL(BGP_SUPPRESS_DUPLICATES, + { .val_bool = false, .match_version = "< 7.6", }, + { .val_bool = true }, +) DEFINE_HOOK(bgp_inst_config_write, (struct bgp *bgp, struct vty *vty), @@ -487,6 +491,8 @@ int bgp_get_vty(struct bgp **bgp, as_t *as, const char *name, SET_FLAG((*bgp)->flags, BGP_FLAG_DETERMINISTIC_MED); if (DFLT_BGP_EBGP_REQUIRES_POLICY) SET_FLAG((*bgp)->flags, BGP_FLAG_EBGP_REQUIRES_POLICY); + if (DFLT_BGP_SUPPRESS_DUPLICATES) + SET_FLAG((*bgp)->flags, BGP_FLAG_SUPPRESS_DUPLICATES); ret = BGP_SUCCESS; } @@ -876,6 +882,7 @@ static int bgp_peer_clear(struct peer *peer, afi_t afi, safi_t safi, struct listnode **nnode, enum bgp_clear_type stype) { int ret = 0; + struct peer_af *paf; /* if afi/.safi not specified, spin thru all of them */ if ((afi == AFI_UNSPEC) && (safi == SAFI_UNSPEC)) { @@ -883,6 +890,11 @@ static int bgp_peer_clear(struct peer *peer, afi_t afi, safi_t safi, safi_t tmp_safi; FOREACH_AFI_SAFI (tmp_afi, tmp_safi) { + paf = peer_af_find(peer, tmp_afi, tmp_safi); + if (paf && paf->subgroup) + SET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_FORCE_UPDATES); + if (!peer->afc[tmp_afi][tmp_safi]) continue; @@ -901,6 +913,11 @@ static int bgp_peer_clear(struct peer *peer, afi_t afi, safi_t safi, if (!peer->afc[afi][tmp_safi]) continue; + paf = peer_af_find(peer, afi, tmp_safi); + if (paf && paf->subgroup) + SET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_FORCE_UPDATES); + if (stype == BGP_CLEAR_SOFT_NONE) ret = peer_clear(peer, nnode); else @@ -912,6 +929,11 @@ static int bgp_peer_clear(struct peer *peer, afi_t afi, safi_t safi, if (!peer->afc[afi][safi]) return 1; + paf = peer_af_find(peer, afi, safi); + if (paf && paf->subgroup) + SET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_FORCE_UPDATES); + if (stype == BGP_CLEAR_SOFT_NONE) ret = peer_clear(peer, nnode); else @@ -2555,6 +2577,37 @@ DEFUN_YANG(no_bgp_always_compare_med, return nb_cli_apply_changes(vty, NULL); } +DEFUN_YANG(bgp_suppress_duplicates, + bgp_suppress_duplicates_cmd, + "bgp suppress-duplicates", + "BGP specific commands\n" + "Suppress duplicate updates if the route actually not changed\n") +{ + nb_cli_enqueue_change(vty, "./global/suppress-duplicates", + NB_OP_MODIFY, "true"); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG(no_bgp_suppress_duplicates, + no_bgp_suppress_duplicates_cmd, + "no bgp suppress-duplicates", + NO_STR + "BGP specific commands\n" + "Suppress duplicate updates if the route actually not changed\n") +{ + nb_cli_enqueue_change(vty, "./global/suppress-duplicates", + NB_OP_MODIFY, "false"); + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_router_bgp_suppress_duplicates(struct vty *vty, + struct lyd_node *dnode, + bool show_defaults) +{ + if (yang_dnode_get_bool(dnode, NULL) != SAVE_BGP_SUPPRESS_DUPLICATES) + vty_out(vty, " bgp suppress-duplicates\n"); +} + DEFUN_YANG(bgp_ebgp_requires_policy, bgp_ebgp_requires_policy_cmd, "bgp ebgp-requires-policy", @@ -17031,6 +17084,16 @@ int bgp_config_write(struct vty *vty) if (bgp->reject_as_sets) vty_out(vty, " bgp reject-as-sets\n"); + /* Suppress duplicate updates if the route actually not changed + */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_DUPLICATES) + != SAVE_BGP_SUPPRESS_DUPLICATES) + vty_out(vty, " %sbgp suppress-duplicates\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_SUPPRESS_DUPLICATES) + ? "" + : "no "); + /* BGP default ipv4-unicast. */ if (CHECK_FLAG(bgp->flags, BGP_FLAG_NO_DEFAULT_IPV4)) vty_out(vty, " no bgp default ipv4-unicast\n"); @@ -17614,6 +17677,10 @@ void bgp_vty_init(void) install_element(BGP_NODE, &bgp_ebgp_requires_policy_cmd); install_element(BGP_NODE, &no_bgp_ebgp_requires_policy_cmd); + /* bgp suppress-duplicates */ + install_element(BGP_NODE, &bgp_suppress_duplicates_cmd); + install_element(BGP_NODE, &no_bgp_suppress_duplicates_cmd); + /* bgp reject-as-sets */ install_element(BGP_NODE, &bgp_reject_as_sets_cmd); install_element(BGP_NODE, &no_bgp_reject_as_sets_cmd); diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c index db5c877759..cd1873054e 100644 --- a/bgpd/bgp_zebra.c +++ b/bgpd/bgp_zebra.c @@ -1305,43 +1305,36 @@ void bgp_zebra_announce(struct bgp_dest *dest, const struct prefix *p, ATTR_FLAG_BIT(BGP_ATTR_SRTE_COLOR))) api_nh->srte_color = info->attr->srte_color; - if (nh_family == AF_INET) { - if (bgp_debug_zebra(&api.prefix)) { - if (mpinfo->extra) { - zlog_debug( - "%s: p=%s, bgp_is_valid_label: %d", - __func__, buf_prefix, - bgp_is_valid_label( - &mpinfo->extra - ->label[0])); - } else { - zlog_debug( - "%s: p=%s, extra is NULL, no label", - __func__, buf_prefix); - } - } - - if (bgp->table_map[afi][safi].name) { - /* Copy info and attributes, so the route-map - apply doesn't modify the BGP route info. */ - local_attr = *mpinfo->attr; - mpinfo_cp->attr = &local_attr; + if (bgp_debug_zebra(&api.prefix)) { + if (mpinfo->extra) { + zlog_debug("%s: p=%s, bgp_is_valid_label: %d", + __func__, buf_prefix, + bgp_is_valid_label( + &mpinfo->extra->label[0])); + } else { + zlog_debug("%s: p=%s, extra is NULL, no label", + __func__, buf_prefix); } + } - if (bgp->table_map[afi][safi].name) { - if (!bgp_table_map_apply( - bgp->table_map[afi][safi].map, p, - mpinfo_cp)) - continue; + if (bgp->table_map[afi][safi].name) { + /* Copy info and attributes, so the route-map + apply doesn't modify the BGP route info. */ + local_attr = *mpinfo->attr; + mpinfo_cp->attr = &local_attr; + if (!bgp_table_map_apply(bgp->table_map[afi][safi].map, + p, mpinfo_cp)) + continue; - /* metric/tag is only allowed to be - * overridden on 1st nexthop */ - if (mpinfo == info) { - metric = mpinfo_cp->attr->med; - tag = mpinfo_cp->attr->tag; - } + /* metric/tag is only allowed to be + * overridden on 1st nexthop */ + if (mpinfo == info) { + metric = mpinfo_cp->attr->med; + tag = mpinfo_cp->attr->tag; } + } + if (nh_family == AF_INET) { nh_updated = update_ipv4nh_for_route_install( nh_othervrf, nh_othervrf ? @@ -1352,31 +1345,6 @@ void bgp_zebra_announce(struct bgp_dest *dest, const struct prefix *p, ifindex_t ifindex = IFINDEX_INTERNAL; struct in6_addr *nexthop; - if (bgp->table_map[afi][safi].name) { - /* Copy info and attributes, so the route-map - apply doesn't modify the BGP route info. */ - local_attr = *mpinfo->attr; - mpinfo_cp->attr = &local_attr; - } - - if (bgp->table_map[afi][safi].name) { - /* Copy info and attributes, so the route-map - apply doesn't modify the BGP route info. */ - local_attr = *mpinfo->attr; - mpinfo_cp->attr = &local_attr; - - if (!bgp_table_map_apply( - bgp->table_map[afi][safi].map, p, - mpinfo_cp)) - continue; - - /* metric/tag is only allowed to be - * overridden on 1st nexthop */ - if (mpinfo == info) { - metric = mpinfo_cp->attr->med; - tag = mpinfo_cp->attr->tag; - } - } nexthop = bgp_path_info_to_ipv6_nexthop(mpinfo_cp, &ifindex); nh_updated = update_ipv6nh_for_route_install( @@ -1499,9 +1467,7 @@ void bgp_zebra_announce(struct bgp_dest *dest, const struct prefix *p, api_nh->vrf_id, api_nh->weight, label_buf, eth_buf); } - } - if (bgp_debug_zebra(p)) { int recursion_flag = 0; if (CHECK_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION)) @@ -2409,7 +2375,6 @@ static int bgp_zebra_route_notify_owner(int command, struct zclient *zclient, struct prefix p; enum zapi_route_notify_owner note; uint32_t table_id; - char buf[PREFIX_STRLEN]; afi_t afi; safi_t safi; struct bgp_dest *dest; @@ -2431,9 +2396,6 @@ static int bgp_zebra_route_notify_owner(int command, struct zclient *zclient, return -1; } - if (BGP_DEBUG(zebra, ZEBRA)) - prefix2str(&p, buf, sizeof(buf)); - /* Find the bgp route node */ dest = bgp_afi_node_lookup(bgp->rib[afi][safi], afi, safi, &p, &bgp->vrf_prd); @@ -2452,7 +2414,7 @@ static int bgp_zebra_route_notify_owner(int command, struct zclient *zclient, BGP_NODE_FIB_INSTALL_PENDING); SET_FLAG(dest->flags, BGP_NODE_FIB_INSTALLED); if (BGP_DEBUG(zebra, ZEBRA)) - zlog_debug("route %s : INSTALLED", buf); + zlog_debug("route %pRN : INSTALLED", dest); /* Find the best route */ for (pi = dest->info; pi; pi = pi->next) { /* Process aggregate route */ @@ -2468,8 +2430,8 @@ static int bgp_zebra_route_notify_owner(int command, struct zclient *zclient, dest, new_select); else { flog_err(EC_BGP_INVALID_ROUTE, - "selected route %s not found", - buf); + "selected route %pRN not found", + dest); return -1; } } @@ -2480,16 +2442,24 @@ static int bgp_zebra_route_notify_owner(int command, struct zclient *zclient, * route add later */ UNSET_FLAG(dest->flags, BGP_NODE_FIB_INSTALLED); + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("route %pRN: Removed from Fib", dest); break; case ZAPI_ROUTE_FAIL_INSTALL: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("route: %pRN Failed to Install into Fib", + dest); /* Error will be logged by zebra module */ break; case ZAPI_ROUTE_BETTER_ADMIN_WON: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("route: %pRN removed due to better admin won", + dest); /* No action required */ break; case ZAPI_ROUTE_REMOVE_FAIL: - zlog_warn("%s: Route %s failure to remove", - __func__, buf); + zlog_warn("%s: Route %pRN failure to remove", + __func__, dest); break; } return 0; diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index b6afa391f3..2149b14585 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -3990,6 +3990,8 @@ bool peer_active_nego(struct peer *peer) void peer_change_action(struct peer *peer, afi_t afi, safi_t safi, enum peer_change_type type) { + struct peer_af *paf; + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) return; @@ -4022,7 +4024,12 @@ void peer_change_action(struct peer *peer, afi_t afi, safi_t safi, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } } else if (type == peer_change_reset_out) { - update_group_adjust_peer(peer_af_find(peer, afi, safi)); + paf = peer_af_find(peer, afi, safi); + if (paf && paf->subgroup) + SET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_FORCE_UPDATES); + + update_group_adjust_peer(paf); bgp_announce_route(peer, afi, safi); } } diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index 775be7980e..16210bed15 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -464,6 +464,7 @@ struct bgp { /* This flag is set if the instance is in administrative shutdown */ #define BGP_FLAG_SHUTDOWN (1 << 27) #define BGP_FLAG_SUPPRESS_FIB_PENDING (1 << 28) +#define BGP_FLAG_SUPPRESS_DUPLICATES (1 << 29) enum global_mode GLOBAL_GR_FSM[BGP_GLOBAL_GR_MODE] [BGP_GLOBAL_GR_EVENT_CMD]; diff --git a/doc/developer/link-state.rst b/doc/developer/link-state.rst new file mode 100644 index 0000000000..f1fc52966b --- /dev/null +++ b/doc/developer/link-state.rst @@ -0,0 +1,314 @@ +Link State API Documentation +============================ + +Introduction +------------ + +The Link State (LS) API aims to provide a set of structures and functions to +build and manage a Traffic Engineering Database for the various FRR daemons. +This API has been designed for several use cases: + +- BGP Link State (BGP-LS): where BGP protocol need to collect the link state + information from the routing daemons (IS-IS and/or OSPF) to implement RFC7572 +- Path Computation Element (PCE): where path computation algorithms are based + on Traffic Engineering Database +- ReSerVation Protocol (RSVP): where signaling need to know the Traffic + Engineering topology of the network in order to determine the path of + RSVP tunnels + +Architecture +------------ + +The main requirements from the various uses cases are as follow: + +- Provides a set of data model and function to ease Link State information + manipulation (storage, serialize, parse ...) +- Ease and normalize Link State information exchange between FRR daemons +- Provides database structure for Traffic Engineering Database (TED) + +To ease Link State understanding, FRR daemons have been classified into two +categories: + +- **Consumer**: Daemons that consume Link State information e.g. BGPd +- **Producer**: Daemons that are able to collect Link State information and + send them to consumer daemons e.g. OSPFd IS-ISd + +Zebra daemon, and more precisely, the ZAPI message is used to convey the Link +State information between *producer* and *consumer*, but, Zebra acts as a +simple pass through and does not store any Link State information. A new ZAPI +**Opaque** message has been design for that purpose. + +Each consumer and producer daemons are free to store or not Link State data and +organise the information following the Traffic Engineering Database model +provided by the API or any other data structure e.g. Hash, RB-tree ... + +Link State API +-------------- + +This is the low level API that allows any daemons manipulate the Link State +elements that are stored in the Link State Database. + +Data structures +^^^^^^^^^^^^^^^ + +3 types of Link State structure have been defined: + +.. c:type:: struct ls_node + + that groups all information related to a node + +.. c:type:: struct ls_attributes + + that groups all information related to a link + +.. c:type:: struct ls_prefix + + that groups all information related to a prefix + +These 3 types of structures are those handled by BGP-LS (see RFC7752) and +suitable to describe a Traffic Engineering topology. + +Each structure, in addition to the specific parameters, embed the node +identifier which advertises the Link State and a bit mask as flags to +indicates which parameters are valid i.e. for which the value is valid and +corresponds to a Link State information conveyed by the routing protocol. + +.. c:type:: struct ls_node_id + + defines the Node identifier as router ID IPv4 address plus the area ID for + OSPF or the ISO System ID plus the IS-IS level for IS-IS. + +Functions +^^^^^^^^^ + +A set of functions is provided to create, delete and compare Link State Node: + +.. c:function:: struct ls_node *ls_node_new(struct ls_node_id adv, struct in_addr router_id, struct in6_addr router6_id) +.. c:function:: voidls_node_del(struct ls_node *node) +.. c:function:: int ls_node_same(struct ls_node *n1, struct ls_node *n2) + +and Link State Attributes: + +.. c:function:: struct ls_attributes *ls_attributes_new(struct ls_node_id adv, struct in_addr local, struct in6_addr local6, uint32_t local_id) +.. c:function:: void ls_attributes_del(struct ls_attributes *attr) +.. c:function:: int ls_attributes_same(struct ls_attributes *a1, struct ls_attributes *a2) + +The low level API doesn't provide any particular functions for the Link State +Prefix structure as this latter is simpler to manipulate. + +Link State TED +-------------- + +This is the high level API that provides functions to create, update, delete a +Link State Database to from a Traffic Engineering Database (TED). + +Data Structures +^^^^^^^^^^^^^^^ + +The Traffic Engineering is modeled as a Graph in order to ease Path Computation +algorithm implementation. Denoted **G(V, E)**, a graph is composed by a list of +**Vertices (V)** which represents the network Node and a list of **Edges (E)** +which represents Link. An additional list of **prefixes (P)** is also added and +also attached to the *Vertex (V)* which advertise it. + +*Vertex (V)* contains the list of outgoing *Edges (E)* that connect this Vertex +with its direct neighbors and the list of incoming *Edges (E)* that connect +the direct neighbors to this Vertex. Indeed, the *Edge (E)* is unidirectional, +thus, it is necessary to add 2 Edges to model a bidirectional relation between +2 Vertices. Finally, the *Vertex (V)* contains a pointer to the corresponding +Link State Node. + +*Edge (E)* contains the source and destination Vertex that this Edge +is connecting and a pointer to the corresponding Link State Attributes. + +A unique Key is used to identify both Vertices and Edges within the Graph. + + +:: + + -------------- --------------------------- -------------- + | Connected |---->| Connected Edge Va to Vb |--->| Connected | + --->| Vertex | --------------------------- | Vertex |----> + | | | | + | - Key (Va) | | - Key (Vb) | + <---| - Vertex | --------------------------- | - Vertex |<---- + | |<----| Connected Edge Vb to Va |<---| | + -------------- --------------------------- -------------- + + +4 data structures have been defined to implement the Graph model: + +.. c:type:: struct ls_vertex +.. c:type:: struct ls_edge +.. c:type:: struct ls_prefix +.. c:type:: struct ls_ted + + +Functions +^^^^^^^^^ + +.. c:function:: struct ls_vertex *ls_vertex_add(struct ls_ted *ted, struct ls_node *node) +.. c:function:: struct ls_vertex *ls_vertex_update(struct ls_ted *ted, struct ls_node *node) +.. c:function:: void ls_vertex_del(struct ls_ted *ted, struct ls_vertex *vertex) +.. c:function:: struct ls_vertex *ls_find_vertex_by_key(struct ls_ted *ted, const uint64_t key) +.. c:function:: struct ls_vertex *ls_find_vertex_by_id(struct ls_ted *ted, struct ls_node_id id) +.. c:function:: int ls_vertex_same(struct ls_vertex *v1, struct ls_vertex *v2) + +.. c:function:: struct ls_edge *ls_edge_add(struct ls_ted *ted, struct ls_attributes *attributes) +.. c:function:: struct ls_edge *ls_edge_update(struct ls_ted *ted, struct ls_attributes *attributes) +.. c:function:: void ls_edge_del(struct ls_ted *ted, struct ls_edge *edge) +.. c:function:: struct ls_edge *ls_find_edge_by_key(struct ls_ted *ted, const uint64_t key) +.. c:function:: struct ls_edge *ls_find_edge_by_source(struct ls_ted *ted, struct ls_attributes *attributes); +.. c:function:: struct ls_edge *ls_find_edge_by_destination(struct ls_ted *ted, struct ls_attributes *attributes); + +.. c:function:: struct ls_subnet *ls_subnet_add(struct ls_ted *ted, struct ls_prefix *pref) +.. c:function:: void ls_subnet_del(struct ls_ted *ted, struct ls_subnet *subnet) +.. c:function:: struct ls_subnet *ls_find_subnet(struct ls_ted *ted, const struct prefix prefix) + +.. c:function:: struct ls_ted *ls_ted_new(const uint32_t key, char *name, uint32_t asn) +.. c:function:: void ls_ted_del(struct ls_ted *ted) +.. c:function:: void ls_connect_vertices(struct ls_vertex *src, struct ls_vertex *dst, struct ls_edge *edge) +.. c:function:: void ls_connect(struct ls_vertex *vertex, struct ls_edge *edge, bool source) +.. c:function:: void ls_disconnect(struct ls_vertex *vertex, struct ls_edge *edge, bool source) +.. c:function:: void ls_disconnect_edge(struct ls_edge *edge) + + +Link State Messages +------------------- + +This part of the API provides functions and data structure to ease the +communication between the *Producer* and *Consumer* daemons. + +Communications principles +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Recent ZAPI Opaque Message is used to exchange Link State data between daemons. +For that purpose, Link State API provides new functions to serialize and parse +Link State information through the ZAPI Opaque message. A dedicated flag, +named ZAPI_OPAQUE_FLAG_UNICAST, allows daemons to send a unicast or a multicast +Opaque message and is used as follow for the Link State exchange: + +- Multicast: To send data update to all daemons that have subscribed to the + Link State Update message +- Unicast: To send initial Link State information from a particular daemon. All + data are send only to the daemon that request Link State Synchronisatio + +Figure 1 below, illustrates the ZAPI Opaque message exchange between a +*Producer* (an IGP like OSPF or IS-IS) and a *Consumer* (e.g. BGP). The +message sequences are as follows: + +- First, both *Producer* and *Consumer* must register to their respective ZAPI + Opaque Message. **Link State Sync** for the *Producer* in order to receive + Database synchronisation request from a *Consumer*. **Link State Update** for + the *Consumer* in order to received any Link State update from a *Producer*. + These register messages are stored by Zebra to determine to which daemon it + should redistribute the ZAPI messages it receives. +- Then, the *Consumer* sends a **Link State Synchronistation** request with the + Multicast method in order to receive the complete Link State Database from a + *Producer*. ZEBRA daemon forwards this message to any *Producer* daemons that + previously registered to this message. If no *Producer* has yet registered, + the request is lost. Thus, if the *Consumer* receives no response whithin a + given timer, it means that no *Producer* are available right now. So, the + *Consumer* must send the same request until it receives a Link State Database + Synchronistation message. This behaviour is necessary as we can't control in + which order daemons are started. It is up to the *Consumer* daemon to fix the + timeout and the number of retry. +- When a *Producer* receives a **Link State Synchronisation** request, it + starts sending all elements of its own Link State Database through the + **Link State Database Synchronisation** message. These messages are send with + the Unicast method to avoid flooding other daemons with these elements. ZEBRA + layer ensures to forward the message to the right daemon. +- When a *Producer* update its Link State Database, it automatically sends a + **Link State Update** message with the Multicast method. In turn, ZEBRA + daemon forwards the message to all *Consumer* daemons that previously + registered to this message. if no daemon is registered, the message is lost. +- A daemon could unregister from the ZAPI Opaque message registry at any time. + In this case, the ZEBRA daemon stops to forward any messages it receives to + this daemon, even if it was previously converns. + +:: + + IGP ZEBRA Consumer + (OSPF/IS-IS) (ZAPI Opaque Thread) (e.g. BGP) + | | | \ + | | Register LS Update | | + | |<----------------------------| Register Phase + | | | | + | | Request LS Sync | | + | |<----------------------------| | + : : : A | + | Register LS Sync | | | | + |----------------------------->| | | / + : : : |TimeOut + : : : | + | | | | + | | Request LS Sync | v \ + | Request LS Sync |<----------------------------| | + |<-----------------------------| | Synchronistation + | LS DB Sync | | Phase + |----------------------------->| LS DB Sync | | + | |---------------------------->| | + | LS DB Sync (cont'd) | | | + |----------------------------->| LS DB Sync (cont'd) | | + | . |---------------------------->| | + | . | . | | + | . | . | | + | LS DB Sync (end) | . | | + |----------------------------->| LS DB Sync (end) | | + | |---------------------------->| | + | | | / + : : : + : : : + | LS Update | | \ + |----------------------------->| LS Update | | + | |---------------------------->| Update Phase + | | | | + : : : / + : : : + | | | \ + | | Unregister LS Update | | + | |<----------------------------| Deregister Phase + | | | | + | LS Update | | | + |----------------------------->| | | + | | | / + | | | + + Figure 1: Link State messages exchange + + +Data Structures +^^^^^^^^^^^^^^^ + +The Link State Message is defined to convey Link State parameters from +the routing protocol (OSPF or IS-IS) to other daemons e.g. BGP. + +.. c:type:: struct ls_message + +The structure is composed of: + +- Event of the message: + + - Sync: Send the whole LS DB following a request + - Add: Send the a new Link State element + - Update: Send an update of an existing Link State element + - Delete: Indicate that the given Link State element is removed + +- Type of Link State element: Node, Attribute or Prefix +- Remote node id when known +- Data: Node, Attributes or Prefix + +A Link State Message can carry only one Link State Element (Node, Attributes +of Prefix) at once, and only one Link State Message is sent through ZAPI +Opaque Link State type at once. + +Functions +^^^^^^^^^ + +.. c:function:: struct ls_message *ls_parse_msg(struct stream *s) +.. c:function:: int ls_send_msg(struct zclient *zclient, struct ls_message *msg, struct zapi_opaque_reg_info *dst) +.. c:function:: struct ls_message *ls_vertex2msg(struct ls_message *msg, struct ls_vertex *vertex) +.. c:function:: struct ls_message *ls_edge2msg(struct ls_message *msg, struct ls_edge *edge) +.. c:function:: struct ls_message *ls_subnet2msg(struct ls_message *msg, struct ls_subnet *subnet) +.. c:function:: int ls_sync_ted(struct ls_ted *ted, struct zclient *zclient, struct zapi_opaque_reg_info *dst) + diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index e0e16aaeeb..7549cec3ea 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -454,6 +454,17 @@ Reject routes with AS_SET or AS_CONFED_SET types This command enables rejection of incoming and outgoing routes having AS_SET or AS_CONFED_SET type. +Suppress duplicate updates +-------------------------- + +.. index:: [no] bgp suppress-duplicates +.. clicmd:: [no] bgp suppress-duplicates + + For example, BGP routers can generate multiple identical announcements with + empty community attributes if stripped at egress. This is an undesired behavior. + Suppress duplicate updates if the route actually not changed. + Default: enabled. + Disable checking if nexthop is connected on EBGP sessions --------------------------------------------------------- diff --git a/lib/link_state.c b/lib/link_state.c new file mode 100644 index 0000000000..f8fdda64f0 --- /dev/null +++ b/lib/link_state.c @@ -0,0 +1,1284 @@ +/* + * Link State Database - link_state.c + * + * Author: Olivier Dugeon <olivier.dugeon@orange.com> + * + * Copyright (C) 2020 Orange http://www.orange.com + * + * This file is part of Free Range Routing (FRR). + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "if.h" +#include "linklist.h" +#include "log.h" +#include "command.h" +#include "termtable.h" +#include "memory.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "zclient.h" +#include "stream.h" +#include "link_state.h" + +/* Link State Memory allocation */ +DEFINE_MTYPE_STATIC(LIB, LS_DB, "Link State Database") + +/** + * Link State Node management functions + */ +struct ls_node *ls_node_new(struct ls_node_id adv, struct in_addr rid, + struct in6_addr rid6) +{ + struct ls_node *new; + + if (adv.origin == NONE) + return NULL; + + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_node)); + new->adv = adv; + if (!IPV4_NET0(rid.s_addr)) { + new->router_id = rid; + SET_FLAG(new->flags, LS_NODE_ROUTER_ID); + } else { + if (adv.origin == OSPFv2 || adv.origin == STATIC + || adv.origin == DIRECT) { + new->router_id = adv.id.ip.addr; + SET_FLAG(new->flags, LS_NODE_ROUTER_ID); + } + } + if (!IN6_IS_ADDR_UNSPECIFIED(&rid6)) { + new->router6_id = rid6; + SET_FLAG(new->flags, LS_NODE_ROUTER_ID6); + } + return new; +} + +void ls_node_del(struct ls_node *node) +{ + XFREE(MTYPE_LS_DB, node); + node = NULL; +} + +int ls_node_same(struct ls_node *n1, struct ls_node *n2) +{ + if ((n1 && !n2) || (!n1 && n2)) + return 0; + + if (n1 == n2) + return 1; + + if (n1->flags != n2->flags) + return 0; + + if (n1->adv.origin != n2->adv.origin) + return 0; + + if (!memcmp(&n1->adv.id, &n2->adv.id, sizeof(struct ls_node_id))) + return 0; + + /* Do we need to test individually each field, instead performing a + * global memcmp? There is a risk that an old value that is bit masked + * i.e. corresponding flag = 0, will result into a false negative + */ + if (!memcmp(n1, n2, sizeof(struct ls_node))) + return 0; + else + return 1; +} + +/** + * Link State Attributes management functions + */ +struct ls_attributes *ls_attributes_new(struct ls_node_id adv, + struct in_addr local, + struct in6_addr local6, + uint32_t local_id) +{ + struct ls_attributes *new; + + if (adv.origin == NONE) + return NULL; + + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_attributes)); + new->adv = adv; + if (!IPV4_NET0(local.s_addr)) { + new->standard.local = local; + SET_FLAG(new->flags, LS_ATTR_LOCAL_ADDR); + } + if (!IN6_IS_ADDR_UNSPECIFIED(&local6)) { + new->standard.local6 = local6; + SET_FLAG(new->flags, LS_ATTR_LOCAL_ADDR6); + } + if (local_id != 0) { + new->standard.local_id = local_id; + SET_FLAG(new->flags, LS_ATTR_LOCAL_ID); + } + + /* Check that almost one identifier is set */ + if (!CHECK_FLAG(new->flags, LS_ATTR_LOCAL_ADDR | LS_ATTR_LOCAL_ADDR6 + | LS_ATTR_LOCAL_ID)) { + XFREE(MTYPE_LS_DB, new); + return NULL; + } + + return new; +} + +void ls_attributes_del(struct ls_attributes *attr) +{ + if (!attr) + return; + + if (attr->srlgs) + XFREE(MTYPE_LS_DB, attr->srlgs); + + XFREE(MTYPE_LS_DB, attr); + attr = NULL; +} + +int ls_attributes_same(struct ls_attributes *l1, struct ls_attributes *l2) +{ + if ((l1 && !l2) || (!l1 && l2)) + return 0; + + if (l1 == l2) + return 1; + + if (l1->flags != l2->flags) + return 0; + + if (l1->adv.origin != l2->adv.origin) + return 0; + + if (!memcmp(&l1->adv.id, &l2->adv.id, sizeof(struct ls_node_id))) + return 0; + + /* Do we need to test individually each field, instead performing a + * global memcmp? There is a risk that an old value that is bit masked + * i.e. corresponding flag = 0, will result into a false negative + */ + if (!memcmp(l1, l2, sizeof(struct ls_attributes))) + return 0; + else + return 1; +} + +/** + * Link State Vertices management functions + */ +struct ls_vertex *ls_vertex_new(struct ls_node *node) +{ + struct ls_vertex *new; + + if (node == NULL) + return NULL; + + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_vertex)); + new->node = node; + new->incoming_edges = list_new(); + new->outgoing_edges = list_new(); + new->prefixes = list_new(); + + return new; +} + +void ls_vertex_del(struct ls_vertex *vertex) +{ + if (vertex == NULL) + return; + + list_delete_all_node(vertex->incoming_edges); + list_delete_all_node(vertex->outgoing_edges); + list_delete_all_node(vertex->prefixes); + XFREE(MTYPE_LS_DB, vertex); + vertex = NULL; +} + +struct ls_vertex *ls_vertex_add(struct ls_ted *ted, struct ls_node *node) +{ + struct ls_vertex *new; + + if ((ted == NULL) || (node == NULL)) + return NULL; + + new = ls_vertex_new(node); + if (!new) + return NULL; + + /* set Key as the IPv4/Ipv6 Router ID or ISO System ID */ + switch (node->adv.origin) { + case OSPFv2: + case STATIC: + case DIRECT: + memcpy(&new->key, &node->adv.id.ip.addr, IPV4_MAX_BYTELEN); + break; + case ISIS_L1: + case ISIS_L2: + memcpy(&new->key, &node->adv.id.iso.sys_id, ISO_SYS_ID_LEN); + break; + default: + new->key = 0; + break; + } + + /* Remove Vertex if key is not set */ + if (new->key == 0) { + ls_vertex_del(new); + return NULL; + } + + /* Add Vertex to TED */ + vertices_add(&ted->vertices, new); + + return new; +} + +struct ls_vertex *ls_vertex_update(struct ls_ted *ted, struct ls_node *node) +{ + struct ls_vertex *old; + + if (node == NULL) + return NULL; + + old = ls_find_vertex_by_id(ted, node->adv); + if (old) { + if (!ls_node_same(old->node, node)) { + ls_node_del(old->node); + old->node = node; + } + return old; + } + + return ls_vertex_add(ted, node); +} + +void ls_vertex_remove(struct ls_ted *ted, struct ls_vertex *vertex) +{ + vertices_del(&ted->vertices, vertex); + ls_vertex_del(vertex); +} + +struct ls_vertex *ls_find_vertex_by_key(struct ls_ted *ted, const uint64_t key) +{ + struct ls_vertex node = {}; + + if (key == 0) + return NULL; + + node.key = key; + return vertices_find(&ted->vertices, &node); +} + +struct ls_vertex *ls_find_vertex_by_id(struct ls_ted *ted, + struct ls_node_id nid) +{ + struct ls_vertex node = {}; + + switch (nid.origin) { + case OSPFv2: + case STATIC: + case DIRECT: + memcpy(&node.key, &nid.id.ip.addr, IPV4_MAX_BYTELEN); + break; + case ISIS_L1: + case ISIS_L2: + memcpy(&node.key, &nid.id.iso.sys_id, ISO_SYS_ID_LEN); + break; + default: + return NULL; + } + + return vertices_find(&ted->vertices, &node); +} + +int ls_vertex_same(struct ls_vertex *v1, struct ls_vertex *v2) +{ + if ((v1 && !v2) || (!v1 && v2)) + return 0; + + if (!v1 && !v2) + return 1; + + if (v1->key != v2->key) + return 0; + + if (v1->node == v2->node) + return 1; + + return ls_node_same(v1->node, v2->node); +} + +/** + * Link State Edges management functions + */ + +/** + * This function allows to connect the Edge to the vertices present in the TED. + * A temporary vertex that corresponds to the source of this Edge i.e. the + * advertised router, is created if not found in the Data Base. If a Edge that + * corresponds to the reverse path is found, the Edge is attached to the + * destination vertex as destination and reverse Edge is attached to the source + * vertex as source. + * + * @param ted Link State Data Base + * @param edge Link State Edge to be attached + */ +static void ls_edge_connect_to(struct ls_ted *ted, struct ls_edge *edge) +{ + struct ls_vertex *vertex = NULL; + struct ls_node *node; + struct ls_edge *dst; + const struct in_addr inaddr_any = {.s_addr = INADDR_ANY}; + + /* First, search if there is a Vertex that correspond to the Node ID */ + vertex = ls_find_vertex_by_id(ted, edge->attributes->adv); + if (vertex == NULL) { + /* Create a new temporary Node & Vertex if not found */ + node = ls_node_new(edge->attributes->adv, inaddr_any, + in6addr_any); + vertex = ls_vertex_add(ted, node); + } + /* and attach the edge as source to the vertex */ + listnode_add(vertex->outgoing_edges, edge); + edge->source = vertex; + + /* Then search if there is a reverse Edge */ + dst = ls_find_edge_by_destination(ted, edge->attributes); + /* attach the destination edge to the vertex */ + if (dst) { + listnode_add(vertex->incoming_edges, dst); + dst->destination = vertex; + /* and destination vertex to this edge */ + vertex = dst->source; + listnode_add(vertex->incoming_edges, edge); + edge->destination = vertex; + } +} + +struct ls_edge *ls_edge_add(struct ls_ted *ted, + struct ls_attributes *attributes) +{ + struct ls_edge *new; + + if (attributes == NULL) + return NULL; + + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_edge)); + new->attributes = attributes; + /* Key is the IPv4 local address */ + if (!IPV4_NET0(attributes->standard.local.s_addr)) + new->key = ((uint64_t)attributes->standard.local.s_addr) + & 0xffffffff; + /* or the IPv6 local address if IPv4 is not defined */ + else if (!IN6_IS_ADDR_UNSPECIFIED(&attributes->standard.local6)) + new->key = (uint64_t)(attributes->standard.local6.s6_addr32[0] + & 0xffffffff) + | ((uint64_t)attributes->standard.local6.s6_addr32[1] + << 32); + /* of local identifier if no IP addresses are defined */ + else if (attributes->standard.local_id != 0) + new->key = (uint64_t)( + (attributes->standard.local_id & 0xffffffff) + | ((uint64_t)attributes->standard.remote_id << 32)); + + /* Remove Edge if key is not known */ + if (new->key == 0) { + XFREE(MTYPE_LS_DB, new); + return NULL; + } + + edges_add(&ted->edges, new); + + /* Finally, connect edge to vertices */ + ls_edge_connect_to(ted, new); + + return new; +} + +struct ls_edge *ls_find_edge_by_key(struct ls_ted *ted, const uint64_t key) +{ + struct ls_edge edge = {}; + + if (key == 0) + return NULL; + + edge.key = key; + return edges_find(&ted->edges, &edge); +} + +struct ls_edge *ls_find_edge_by_source(struct ls_ted *ted, + struct ls_attributes *attributes) +{ + struct ls_edge edge = {}; + + if (attributes == NULL) + return NULL; + + /* Key is the IPv4 local address */ + if (!IPV4_NET0(attributes->standard.local.s_addr)) + edge.key = ((uint64_t)attributes->standard.local.s_addr) + & 0xffffffff; + /* or the IPv6 local address if IPv4 is not defined */ + else if (!IN6_IS_ADDR_UNSPECIFIED(&attributes->standard.local6)) + edge.key = (uint64_t)(attributes->standard.local6.s6_addr32[0] + & 0xffffffff) + | ((uint64_t)attributes->standard.local6.s6_addr32[1] + << 32); + /* of local identifier if no IP addresses are defined */ + else if (attributes->standard.local_id != 0) + edge.key = (uint64_t)( + (attributes->standard.local_id & 0xffffffff) + | ((uint64_t)attributes->standard.remote_id << 32)); + + if (edge.key == 0) + return NULL; + + return edges_find(&ted->edges, &edge); +} + +struct ls_edge *ls_find_edge_by_destination(struct ls_ted *ted, + struct ls_attributes *attributes) +{ + struct ls_edge edge = {}; + + if (attributes == NULL) + return NULL; + + /* Key is the IPv4 local address */ + if (!IPV4_NET0(attributes->standard.remote.s_addr)) + edge.key = ((uint64_t)attributes->standard.remote.s_addr) + & 0xffffffff; + /* or the IPv6 local address if IPv4 is not defined */ + else if (!IN6_IS_ADDR_UNSPECIFIED(&attributes->standard.remote6)) + edge.key = + (uint64_t)(attributes->standard.remote6.s6_addr32[0] + & 0xffffffff) + | ((uint64_t)attributes->standard.remote6.s6_addr32[1] + << 32); + /* of local identifier if no IP addresses are defined */ + else if (attributes->standard.remote_id != 0) + edge.key = (uint64_t)( + (attributes->standard.remote_id & 0xffffffff) + | ((uint64_t)attributes->standard.local_id << 32)); + + if (edge.key == 0) + return NULL; + + return edges_find(&ted->edges, &edge); +} + +struct ls_edge *ls_edge_update(struct ls_ted *ted, + struct ls_attributes *attributes) +{ + struct ls_edge *old; + + if (attributes == NULL) + return NULL; + + /* First, search for an existing Edge */ + old = ls_find_edge_by_source(ted, attributes); + if (old) { + /* Check if attributes are similar */ + if (!ls_attributes_same(old->attributes, attributes)) { + ls_attributes_del(old->attributes); + old->attributes = attributes; + } + return old; + } + + /* If not found, add new Edge from the attributes */ + return ls_edge_add(ted, attributes); +} + +void ls_edge_del(struct ls_ted *ted, struct ls_edge *edge) +{ + /* Fist disconnect Edge */ + ls_disconnect_edge(edge); + /* Then remove it from the Data Base */ + edges_del(&ted->edges, edge); + XFREE(MTYPE_LS_DB, edge); +} + +/** + * Link State Subnet Management functions. + */ +struct ls_subnet *ls_subnet_add(struct ls_ted *ted, + struct ls_prefix *ls_pref) +{ + struct ls_subnet *new; + struct ls_vertex *vertex; + struct ls_node *node; + const struct in_addr inaddr_any = {.s_addr = INADDR_ANY}; + + if (ls_pref == NULL) + return NULL; + + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_subnet)); + new->ls_pref = ls_pref; + new->key = ls_pref->pref; + + /* Find Vertex */ + vertex = ls_find_vertex_by_id(ted, ls_pref->adv); + if (vertex == NULL) { + /* Create a new temporary Node & Vertex if not found */ + node = ls_node_new(ls_pref->adv, inaddr_any, in6addr_any); + vertex = ls_vertex_add(ted, node); + } + /* And attach the subnet to the corresponding Vertex */ + new->vertex = vertex; + listnode_add(vertex->prefixes, new); + + subnets_add(&ted->subnets, new); + + return new; +} + +void ls_subnet_del(struct ls_ted *ted, struct ls_subnet *subnet) +{ + subnets_del(&ted->subnets, subnet); + XFREE(MTYPE_LS_DB, subnet); +} + +struct ls_subnet *ls_find_subnet(struct ls_ted *ted, const struct prefix prefix) +{ + struct ls_subnet subnet = {}; + + subnet.key = prefix; + return subnets_find(&ted->subnets, &subnet); +} + +/** + * Link State TED management functions + */ +struct ls_ted *ls_ted_new(const uint32_t key, const char *name, + uint32_t as_number) +{ + struct ls_ted *new; + + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_ted)); + if (new == NULL) + return new; + + /* Set basic information for this ted */ + new->key = key; + new->as_number = as_number; + strlcpy(new->name, name, MAX_NAME_LENGTH); + + /* Initialize the various RB tree */ + vertices_init(&new->vertices); + edges_init(&new->edges); + subnets_init(&new->subnets); + + return new; +} + +void ls_ted_del(struct ls_ted *ted) +{ + if (ted == NULL) + return; + + /* Release RB Tree */ + vertices_fini(&ted->vertices); + edges_fini(&ted->edges); + subnets_fini(&ted->subnets); + + XFREE(MTYPE_LS_DB, ted); + ted = NULL; +} + +void ls_connect(struct ls_vertex *vertex, struct ls_edge *edge, bool source) +{ + if (vertex == NULL || edge == NULL) + return; + + if (source) { + listnode_add(vertex->outgoing_edges, edge); + edge->source = vertex; + } else { + listnode_add(vertex->incoming_edges, edge); + edge->destination = vertex; + } +} + +void ls_disconnect(struct ls_vertex *vertex, struct ls_edge *edge, bool source) +{ + + if (vertex == NULL || edge == NULL) + return; + + if (source) { + listnode_delete(vertex->outgoing_edges, edge); + edge->source = NULL; + } else { + listnode_delete(vertex->incoming_edges, edge); + edge->destination = NULL; + } +} + +void ls_connect_vertices(struct ls_vertex *src, struct ls_vertex *dst, + struct ls_edge *edge) +{ + if (edge == NULL) + return; + + edge->source = src; + edge->destination = dst; + + if (src != NULL) + listnode_add(src->outgoing_edges, edge); + + if (dst != NULL) + listnode_add(dst->incoming_edges, edge); + +} + +void ls_disconnect_edge(struct ls_edge *edge) +{ + if (edge == NULL) + return; + + ls_disconnect(edge->source, edge, true); + ls_disconnect(edge->destination, edge, false); +} + +/** + * Link State Message management functions + */ + +static struct ls_node *ls_parse_node(struct stream *s) +{ + struct ls_node *node; + size_t len; + + node = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_node)); + if (node == NULL) + return NULL; + + STREAM_GET(&node->adv, s, sizeof(struct ls_node_id)); + STREAM_GETW(s, node->flags); + if (CHECK_FLAG(node->flags, LS_NODE_NAME)) { + STREAM_GETC(s, len); + STREAM_GET(node->name, s, len); + } + if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID)) + node->router_id.s_addr = stream_get_ipv4(s); + if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID6)) + STREAM_GET(&node->router6_id, s, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(node->flags, LS_NODE_FLAG)) + STREAM_GETC(s, node->node_flag); + if (CHECK_FLAG(node->flags, LS_NODE_TYPE)) + STREAM_GETC(s, node->type); + if (CHECK_FLAG(node->flags, LS_NODE_AS_NUMBER)) + STREAM_GETL(s, node->as_number); + if (CHECK_FLAG(node->flags, LS_NODE_SR)) { + STREAM_GETL(s, node->srgb.lower_bound); + STREAM_GETL(s, node->srgb.range_size); + STREAM_GETC(s, node->srgb.flag); + STREAM_GET(node->algo, s, 2); + } + if (CHECK_FLAG(node->flags, LS_NODE_SRLB)) { + STREAM_GETL(s, node->srlb.lower_bound); + STREAM_GETL(s, node->srlb.range_size); + } + if (CHECK_FLAG(node->flags, LS_NODE_MSD)) + STREAM_GETC(s, node->msd); + + return node; + +stream_failure: + zlog_err("LS(%s): Could not parse Link State Node. Abort!", __func__); + XFREE(MTYPE_LS_DB, node); + return NULL; +} + +static struct ls_attributes *ls_parse_attributes(struct stream *s) +{ + struct ls_attributes *attr; + size_t len; + + attr = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_attributes)); + if (attr == NULL) + return NULL; + attr->srlgs = NULL; + + STREAM_GET(&attr->adv, s, sizeof(struct ls_node_id)); + STREAM_GETL(s, attr->flags); + if (CHECK_FLAG(attr->flags, LS_ATTR_NAME)) { + STREAM_GETC(s, len); + STREAM_GET(attr->name, s, len); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_METRIC)) + STREAM_GETL(s, attr->standard.metric); + if (CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC)) + STREAM_GETL(s, attr->standard.te_metric); + if (CHECK_FLAG(attr->flags, LS_ATTR_ADM_GRP)) + STREAM_GETL(s, attr->standard.admin_group); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)) + attr->standard.local.s_addr = stream_get_ipv4(s); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR)) + attr->standard.remote.s_addr = stream_get_ipv4(s); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)) + STREAM_GET(&attr->standard.local6, s, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6)) + STREAM_GET(&attr->standard.remote6, s, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID)) + STREAM_GETL(s, attr->standard.local_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID)) + STREAM_GETL(s, attr->standard.remote_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_BW)) + STREAM_GETF(s, attr->standard.max_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW)) + STREAM_GETF(s, attr->standard.max_rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_UNRSV_BW)) + for (len = 0; len < MAX_CLASS_TYPE; len++) + STREAM_GETF(s, attr->standard.unrsv_bw[len]); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_AS)) + STREAM_GETL(s, attr->standard.remote_as); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR)) + attr->standard.remote_addr.s_addr = stream_get_ipv4(s); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR6)) + STREAM_GET(&attr->standard.remote_addr6, s, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_DELAY)) + STREAM_GETL(s, attr->extended.delay); + if (CHECK_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY)) { + STREAM_GETL(s, attr->extended.min_delay); + STREAM_GETL(s, attr->extended.max_delay); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_JITTER)) + STREAM_GETL(s, attr->extended.jitter); + if (CHECK_FLAG(attr->flags, LS_ATTR_PACKET_LOSS)) + STREAM_GETL(s, attr->extended.pkt_loss); + if (CHECK_FLAG(attr->flags, LS_ATTR_AVA_BW)) + STREAM_GETF(s, attr->extended.ava_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_RSV_BW)) + STREAM_GETF(s, attr->extended.rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_USE_BW)) + STREAM_GETF(s, attr->extended.used_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID)) { + STREAM_GETL(s, attr->adj_sid[0].sid); + STREAM_GETC(s, attr->adj_sid[0].flags); + STREAM_GETC(s, attr->adj_sid[0].weight); + if (attr->adv.origin == ISIS_L1 || attr->adv.origin == ISIS_L2) + STREAM_GET(attr->adj_sid[0].neighbor.sysid, s, + ISO_SYS_ID_LEN); + else if (attr->adv.origin == OSPFv2) + attr->adj_sid[0].neighbor.addr.s_addr = + stream_get_ipv4(s); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID)) { + STREAM_GETL(s, attr->adj_sid[1].sid); + STREAM_GETC(s, attr->adj_sid[1].flags); + STREAM_GETC(s, attr->adj_sid[1].weight); + if (attr->adv.origin == ISIS_L1 || attr->adv.origin == ISIS_L2) + STREAM_GET(attr->adj_sid[1].neighbor.sysid, s, + ISO_SYS_ID_LEN); + else if (attr->adv.origin == OSPFv2) + attr->adj_sid[1].neighbor.addr.s_addr = + stream_get_ipv4(s); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_SRLG)) { + STREAM_GETC(s, len); + attr->srlgs = XCALLOC(MTYPE_LS_DB, len*sizeof(uint32_t)); + attr->srlg_len = len; + for (len = 0; len < attr->srlg_len; len++) + STREAM_GETL(s, attr->srlgs[len]); + } + + return attr; + +stream_failure: + zlog_err("LS(%s): Could not parse Link State Attributes. Abort!", + __func__); + /* Clean memeory allocation */ + if (attr->srlgs != NULL) + XFREE(MTYPE_LS_DB, attr->srlgs); + XFREE(MTYPE_LS_DB, attr); + return NULL; + +} + +static struct ls_prefix *ls_parse_prefix(struct stream *s) +{ + struct ls_prefix *ls_pref; + size_t len; + + ls_pref = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_prefix)); + if (ls_pref == NULL) + return NULL; + + STREAM_GET(&ls_pref->adv, s, sizeof(struct ls_node_id)); + STREAM_GETW(s, ls_pref->flags); + STREAM_GETC(s, ls_pref->pref.family); + STREAM_GETW(s, ls_pref->pref.prefixlen); + len = prefix_blen(&ls_pref->pref); + STREAM_GET(&ls_pref->pref.u.prefix, s, len); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_IGP_FLAG)) + STREAM_GETC(s, ls_pref->igp_flag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_ROUTE_TAG)) + STREAM_GETL(s, ls_pref->route_tag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_EXTENDED_TAG)) + STREAM_GETQ(s, ls_pref->extended_tag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_METRIC)) + STREAM_GETL(s, ls_pref->metric); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_SR)) { + STREAM_GETL(s, ls_pref->sr.sid); + STREAM_GETC(s, ls_pref->sr.sid_flag); + STREAM_GETC(s, ls_pref->sr.algo); + } + + return ls_pref; + +stream_failure: + zlog_err("LS(%s): Could not parse Link State Prefix. Abort!", __func__); + XFREE(MTYPE_LS_DB, ls_pref); + return NULL; +} + +struct ls_message *ls_parse_msg(struct stream *s) +{ + struct ls_message *msg; + + msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message)); + if (msg == NULL) + return NULL; + + /* Read LS Message header */ + STREAM_GETC(s, msg->event); + STREAM_GETC(s, msg->type); + STREAM_GET(&msg->remote_id, s, sizeof(struct ls_node_id)); + + /* Read Message Payload */ + switch (msg->type) { + case LS_MSG_TYPE_NODE: + msg->data.node = ls_parse_node(s); + break; + case LS_MSG_TYPE_ATTRIBUTES: + msg->data.attr = ls_parse_attributes(s); + break; + case LS_MSG_TYPE_PREFIX: + msg->data.prefix = ls_parse_prefix(s); + break; + default: + zlog_err("Unsupported Payload"); + goto stream_failure; + } + + if (msg->data.node == NULL || msg->data.attr == NULL + || msg->data.prefix == NULL) + goto stream_failure; + + return msg; + +stream_failure: + zlog_err("LS(%s): Could not parse LS message. Abort!", __func__); + XFREE(MTYPE_LS_DB, msg); + return NULL; +} + +static int ls_format_node(struct stream *s, struct ls_node *node) +{ + size_t len; + + /* Push Advertise node information first */ + stream_put(s, &node->adv, sizeof(struct ls_node_id)); + + /* Push Flags & Origin then Node information if there are present */ + stream_putw(s, node->flags); + if (CHECK_FLAG(node->flags, LS_NODE_NAME)) { + len = strlen(node->name); + stream_putc(s, len + 1); + stream_put(s, node->name, len); + stream_putc(s, '\0'); + } + if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID)) + stream_put_ipv4(s, node->router_id.s_addr); + if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID6)) + stream_put(s, &node->router6_id, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(node->flags, LS_NODE_FLAG)) + stream_putc(s, node->node_flag); + if (CHECK_FLAG(node->flags, LS_NODE_TYPE)) + stream_putc(s, node->type); + if (CHECK_FLAG(node->flags, LS_NODE_AS_NUMBER)) + stream_putl(s, node->as_number); + if (CHECK_FLAG(node->flags, LS_NODE_SR)) { + stream_putl(s, node->srgb.lower_bound); + stream_putl(s, node->srgb.range_size); + stream_putc(s, node->srgb.flag); + stream_put(s, node->algo, 2); + } + if (CHECK_FLAG(node->flags, LS_NODE_SRLB)) { + stream_putl(s, node->srlb.lower_bound); + stream_putl(s, node->srlb.range_size); + } + if (CHECK_FLAG(node->flags, LS_NODE_MSD)) + stream_putc(s, node->msd); + + return 0; +} + +static int ls_format_attributes(struct stream *s, struct ls_attributes *attr) +{ + size_t len; + + /* Push Advertise node information first */ + stream_put(s, &attr->adv, sizeof(struct ls_node_id)); + + /* Push Flags & Origin then LS attributes if there are present */ + stream_putl(s, attr->flags); + if (CHECK_FLAG(attr->flags, LS_ATTR_NAME)) { + len = strlen(attr->name); + stream_putc(s, len + 1); + stream_put(s, attr->name, len); + stream_putc(s, '\0'); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_METRIC)) + stream_putl(s, attr->standard.metric); + if (CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC)) + stream_putl(s, attr->standard.te_metric); + if (CHECK_FLAG(attr->flags, LS_ATTR_ADM_GRP)) + stream_putl(s, attr->standard.admin_group); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)) + stream_put_ipv4(s, attr->standard.local.s_addr); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR)) + stream_put_ipv4(s, attr->standard.remote.s_addr); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)) + stream_put(s, &attr->standard.local6, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6)) + stream_put(s, &attr->standard.remote6, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID)) + stream_putl(s, attr->standard.local_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID)) + stream_putl(s, attr->standard.remote_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_BW)) + stream_putf(s, attr->standard.max_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW)) + stream_putf(s, attr->standard.max_rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_UNRSV_BW)) + for (len = 0; len < MAX_CLASS_TYPE; len++) + stream_putf(s, attr->standard.unrsv_bw[len]); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_AS)) + stream_putl(s, attr->standard.remote_as); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR)) + stream_put_ipv4(s, attr->standard.remote_addr.s_addr); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR6)) + stream_put(s, &attr->standard.remote_addr6, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_DELAY)) + stream_putl(s, attr->extended.delay); + if (CHECK_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY)) { + stream_putl(s, attr->extended.min_delay); + stream_putl(s, attr->extended.max_delay); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_JITTER)) + stream_putl(s, attr->extended.jitter); + if (CHECK_FLAG(attr->flags, LS_ATTR_PACKET_LOSS)) + stream_putl(s, attr->extended.pkt_loss); + if (CHECK_FLAG(attr->flags, LS_ATTR_AVA_BW)) + stream_putf(s, attr->extended.ava_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_RSV_BW)) + stream_putf(s, attr->extended.rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_USE_BW)) + stream_putf(s, attr->extended.used_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID)) { + stream_putl(s, attr->adj_sid[0].sid); + stream_putc(s, attr->adj_sid[0].flags); + stream_putc(s, attr->adj_sid[0].weight); + if (attr->adv.origin == ISIS_L1 || attr->adv.origin == ISIS_L2) + stream_put(s, attr->adj_sid[0].neighbor.sysid, + ISO_SYS_ID_LEN); + else if (attr->adv.origin == OSPFv2) + stream_put_ipv4(s, + attr->adj_sid[0].neighbor.addr.s_addr); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID)) { + stream_putl(s, attr->adj_sid[1].sid); + stream_putc(s, attr->adj_sid[1].flags); + stream_putc(s, attr->adj_sid[1].weight); + if (attr->adv.origin == ISIS_L1 || attr->adv.origin == ISIS_L2) + stream_put(s, attr->adj_sid[1].neighbor.sysid, + ISO_SYS_ID_LEN); + else if (attr->adv.origin == OSPFv2) + stream_put_ipv4(s, + attr->adj_sid[1].neighbor.addr.s_addr); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_SRLG)) { + stream_putc(s, attr->srlg_len); + for (len = 0; len < attr->srlg_len; len++) + stream_putl(s, attr->srlgs[len]); + } + + return 0; +} + +static int ls_format_prefix(struct stream *s, struct ls_prefix *ls_pref) +{ + size_t len; + + /* Push Advertise node information first */ + stream_put(s, &ls_pref->adv, sizeof(struct ls_node_id)); + + /* Push Flags, Origin & Prefix then information if there are present */ + stream_putw(s, ls_pref->flags); + stream_putc(s, ls_pref->pref.family); + stream_putw(s, ls_pref->pref.prefixlen); + len = prefix_blen(&ls_pref->pref); + stream_put(s, &ls_pref->pref.u.prefix, len); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_IGP_FLAG)) + stream_putc(s, ls_pref->igp_flag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_ROUTE_TAG)) + stream_putl(s, ls_pref->route_tag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_EXTENDED_TAG)) + stream_putq(s, ls_pref->extended_tag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_METRIC)) + stream_putl(s, ls_pref->metric); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_SR)) { + stream_putl(s, ls_pref->sr.sid); + stream_putc(s, ls_pref->sr.sid_flag); + stream_putc(s, ls_pref->sr.algo); + } + + return 0; +} + +static int ls_format_msg(struct stream *s, struct ls_message *msg) +{ + + /* Prepare Link State header */ + stream_putc(s, msg->event); + stream_putc(s, msg->type); + stream_put(s, &msg->remote_id, sizeof(struct ls_node_id)); + + /* Add Message Payload */ + switch (msg->type) { + case LS_MSG_TYPE_NODE: + return ls_format_node(s, msg->data.node); + case LS_MSG_TYPE_ATTRIBUTES: + return ls_format_attributes(s, msg->data.attr); + case LS_MSG_TYPE_PREFIX: + return ls_format_prefix(s, msg->data.prefix); + default: + zlog_warn("Unsupported Payload"); + break; + } + + return -1; +} + +int ls_send_msg(struct zclient *zclient, struct ls_message *msg, + struct zapi_opaque_reg_info *dst) +{ + struct stream *s; + uint16_t flags = 0; + + /* Check buffer size */ + if (STREAM_SIZE(zclient->obuf) < + (ZEBRA_HEADER_SIZE + sizeof(uint32_t) + sizeof(msg))) + return -1; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_OPAQUE_MESSAGE, VRF_DEFAULT); + + /* Send sub-type, flags and destination for unicast message */ + stream_putl(s, LINK_STATE_UPDATE); + if (dst != NULL) { + SET_FLAG(flags, ZAPI_OPAQUE_FLAG_UNICAST); + stream_putw(s, flags); + /* Send destination client info */ + stream_putc(s, dst->proto); + stream_putw(s, dst->instance); + stream_putl(s, dst->session_id); + } else + stream_putw(s, flags); + + /* Format Link State message */ + if (ls_format_msg(s, msg) < 0) { + stream_reset(s); + return -1; + } + + /* Put length into the header at the start of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +struct ls_message *ls_vertex2msg(struct ls_message *msg, + struct ls_vertex *vertex) +{ + /* Allocate space if needed */ + if (msg == NULL) + msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message)); + else + memset(msg, 0, sizeof(*msg)); + + msg->type = LS_MSG_TYPE_NODE; + msg->data.node = vertex->node; + msg->remote_id.origin = NONE; + + return msg; +} + +struct ls_message *ls_edge2msg(struct ls_message *msg, struct ls_edge *edge) +{ + /* Allocate space if needed */ + if (msg == NULL) + msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message)); + else + memset(msg, 0, sizeof(*msg)); + + msg->type = LS_MSG_TYPE_ATTRIBUTES; + msg->data.attr = edge->attributes; + if (edge->destination != NULL) + msg->remote_id = edge->destination->node->adv; + else + msg->remote_id.origin = NONE; + + return msg; +} + +struct ls_message *ls_subnet2msg(struct ls_message *msg, + struct ls_subnet *subnet) +{ + /* Allocate space if needed */ + if (msg == NULL) + msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message)); + else + memset(msg, 0, sizeof(*msg)); + + msg->type = LS_MSG_TYPE_PREFIX; + msg->data.prefix = subnet->ls_pref; + msg->remote_id.origin = NONE; + + return msg; +} + +void ls_delete_msg(struct ls_message *msg) +{ + if (msg == NULL) + return; + + switch (msg->type) { + case LS_MSG_TYPE_NODE: + if (msg->data.node) + XFREE(MTYPE_LS_DB, msg->data.node); + break; + case LS_MSG_TYPE_ATTRIBUTES: + if (msg->data.attr) + XFREE(MTYPE_LS_DB, msg->data.attr); + break; + case LS_MSG_TYPE_PREFIX: + if (msg->data.prefix) + XFREE(MTYPE_LS_DB, msg->data.prefix); + break; + default: + break; + } + + XFREE(MTYPE_LS_DB, msg); +} + +int ls_sync_ted(struct ls_ted *ted, struct zclient *zclient, + struct zapi_opaque_reg_info *dst) +{ + struct ls_vertex *vertex; + struct ls_edge *edge; + struct ls_subnet *subnet; + struct ls_message msg; + + /* Prepare message */ + msg.event = LS_MSG_EVENT_SYNC; + + /* Loop TED, start sending Node, then Attributes and finally Prefix */ + frr_each(vertices, &ted->vertices, vertex) { + ls_vertex2msg(&msg, vertex); + ls_send_msg(zclient, &msg, dst); + } + frr_each(edges, &ted->edges, edge) { + ls_edge2msg(&msg, edge); + ls_send_msg(zclient, &msg, dst); + } + frr_each(subnets, &ted->subnets, subnet) { + ls_subnet2msg(&msg, subnet); + ls_send_msg(zclient, &msg, dst); + } + return 0; +} + +void ls_dump_ted(struct ls_ted *ted) +{ + struct ls_vertex *vertex; + struct ls_edge *edge; + struct ls_subnet *subnet; + struct ls_message msg; + + zlog_debug("(%s) Ted init", __func__); + /* Prepare message */ + msg.event = LS_MSG_EVENT_SYNC; + + /* Loop TED, start printing Node, then Attributes and finally Prefix */ + frr_each(vertices, &ted->vertices, vertex) { + ls_vertex2msg(&msg, vertex); + zlog_debug("\tTed node (%s %pI4 %s)", + vertex->node->name[0] ? vertex->node->name + : "no name node", + &vertex->node->router_id, + vertex->node->adv.origin == DIRECT ? "DIRECT" + : "NO DIRECT"); + struct listnode *lst_node; + struct ls_edge *vertex_edge; + + for (ALL_LIST_ELEMENTS_RO(vertex->incoming_edges, lst_node, + vertex_edge)) { + zlog_debug( + "\t\tinc edge key:%lldn attr key:%pI4 loc:(%pI4) rmt:(%pI4)", + vertex_edge->key, + &vertex_edge->attributes->adv.id.ip.addr, + &vertex_edge->attributes->standard.local, + &vertex_edge->attributes->standard.remote); + } + for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, lst_node, + vertex_edge)) { + zlog_debug( + "\t\tout edge key:%lld attr key:%pI4 loc:(%pI4) rmt:(%pI4)", + vertex_edge->key, + &vertex_edge->attributes->adv.id.ip.addr, + &vertex_edge->attributes->standard.local, + &vertex_edge->attributes->standard.remote); + } + } + frr_each(edges, &ted->edges, edge) { + ls_edge2msg(&msg, edge); + zlog_debug("\tTed edge key:%lld src:%s dst:%s", edge->key, + edge->source ? edge->source->node->name + : "no_source", + edge->destination ? edge->destination->node->name + : "no_dest"); + } + frr_each(subnets, &ted->subnets, subnet) { + ls_subnet2msg(&msg, subnet); + zlog_debug( + "\tTed subnet key:%s vertex:%pI4 pfx:%pFX", + subnet->key.family == AF_INET + ? inet_ntoa(subnet->key.u.prefix4) + : inet6_ntoa(subnet->key.u.prefix6), + &subnet->vertex->node->adv.id.ip.addr, + &subnet->ls_pref->pref); + } + zlog_debug("(%s) Ted end", __func__); +} diff --git a/lib/link_state.h b/lib/link_state.h new file mode 100644 index 0000000000..93669f5b23 --- /dev/null +++ b/lib/link_state.h @@ -0,0 +1,780 @@ +/* + * Link State Database definition - ted.h + * + * Author: Olivier Dugeon <olivier.dugeon@orange.com> + * + * Copyright (C) 2020 Orange http://www.orange.com + * + * This file is part of Free Range Routing (FRR). + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FRR_LINK_STATE_H_ +#define _FRR_LINK_STATE_H_ + +#include "typesafe.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This file defines the model used to implement a Link State Database + * suitable to be used by various protocol like RSVP-TE, BGP-LS, PCEP ... + * This database is normally fulfill by the link state routing protocol, + * commonly OSPF or ISIS, carrying Traffic Engineering information within + * Link State Attributes. See, RFC3630.(OSPF-TE) and RFC5305 (ISIS-TE). + * + * At least, 3 types of Link State structure are defined: + * - Link State Node that groups all information related to a node + * - Link State Attributes that groups all information related to a link + * - Link State Prefix that groups all information related to a prefix + * + * These 3 types of structures are those handled by BGP-LS (see RFC7752). + * + * Each structure, in addition to the specific parameters, embed the node + * identifier which advertises the Link State and a bit mask as flags to + * indicates which parameters are valid i.e. for which the value corresponds + * to a Link State information convey by the routing protocol. + * Node identifier is composed of the route id as IPv4 address plus the area + * id for OSPF and the ISO System id plus the IS-IS level for IS-IS. + */ + +/* Link State Common definitions */ +#define MAX_NAME_LENGTH 256 +#define ISO_SYS_ID_LEN 6 + +/* Type of Node */ +enum ls_node_type { + STANDARD, /* a P or PE node */ + ABR, /* an Array Border Node */ + ASBR, /* an Autonomous System Border Node */ + PSEUDO, /* a Pseudo Node */ +}; + +/* Origin of the Link State information */ +enum ls_origin {NONE = 0, ISIS_L1, ISIS_L2, OSPFv2, DIRECT, STATIC}; + +/** + * Link State Node Identifier as: + * - IPv4 address + Area ID for OSPF + * - ISO System ID + ISIS Level for ISIS + */ +struct ls_node_id { + enum ls_origin origin; /* Origin of the LS information */ + union { + struct { + struct in_addr addr; /* OSPF Router IS */ + struct in_addr area_id; /* OSPF Area ID */ + } ip; + struct { + uint8_t sys_id[ISO_SYS_ID_LEN]; /* ISIS System ID */ + uint8_t level; /* ISIS Level */ + uint8_t padding; + } iso; + } id __attribute__((aligned(8))); +}; + +/* Link State flags to indicate which Node parameters are valid */ +#define LS_NODE_UNSET 0x0000 +#define LS_NODE_NAME 0x0001 +#define LS_NODE_ROUTER_ID 0x0002 +#define LS_NODE_ROUTER_ID6 0x0004 +#define LS_NODE_FLAG 0x0008 +#define LS_NODE_TYPE 0x0010 +#define LS_NODE_AS_NUMBER 0x0020 +#define LS_NODE_SR 0x0040 +#define LS_NODE_SRLB 0x0080 +#define LS_NODE_MSD 0x0100 + +/* Link State Node structure */ +struct ls_node { + uint16_t flags; /* Flag for parameters validity */ + struct ls_node_id adv; /* Adv. Router of this Link State */ + char name[MAX_NAME_LENGTH]; /* Name of the Node (IS-IS only) */ + struct in_addr router_id; /* IPv4 Router ID */ + struct in6_addr router6_id; /* IPv6 Router ID */ + uint8_t node_flag; /* IS-IS or OSPF Node flag */ + enum node_type type; /* Type of Node */ + uint32_t as_number; /* Local or neighbor AS number */ + struct { /* Segment Routing Global Block */ + uint32_t lower_bound; /* MPLS label lower bound */ + uint32_t range_size; /* MPLS label range size */ + uint8_t flag; /* IS-IS SRGB flags */ + } srgb; +#define LS_NODE_SRGB_SIZE 9 + struct { /* Segment Routing Local Block */ + uint32_t lower_bound; /* MPLS label lower bound */ + uint32_t range_size; /* MPLS label range size */ + } srlb; +#define LS_NODE_SRLB_SIZE 8 + uint8_t algo[2]; /* Segment Routing Algorithms */ + uint8_t msd; /* Maximum Stack Depth */ +}; + +/* Link State flags to indicate which Attribute parameters are valid */ +#define LS_ATTR_UNSET 0x00000000 +#define LS_ATTR_NAME 0x00000001 +#define LS_ATTR_METRIC 0x00000002 +#define LS_ATTR_TE_METRIC 0x00000004 +#define LS_ATTR_ADM_GRP 0x00000008 +#define LS_ATTR_LOCAL_ADDR 0x00000010 +#define LS_ATTR_NEIGH_ADDR 0x00000020 +#define LS_ATTR_LOCAL_ADDR6 0x00000040 +#define LS_ATTR_NEIGH_ADDR6 0x00000080 +#define LS_ATTR_LOCAL_ID 0x00000100 +#define LS_ATTR_NEIGH_ID 0x00000200 +#define LS_ATTR_MAX_BW 0x00000400 +#define LS_ATTR_MAX_RSV_BW 0x00000800 +#define LS_ATTR_UNRSV_BW 0x00001000 +#define LS_ATTR_REMOTE_AS 0x00002000 +#define LS_ATTR_REMOTE_ADDR 0x00004000 +#define LS_ATTR_REMOTE_ADDR6 0x00008000 +#define LS_ATTR_DELAY 0x00010000 +#define LS_ATTR_MIN_MAX_DELAY 0x00020000 +#define LS_ATTR_JITTER 0x00040000 +#define LS_ATTR_PACKET_LOSS 0x00080000 +#define LS_ATTR_AVA_BW 0x00100000 +#define LS_ATTR_RSV_BW 0x00200000 +#define LS_ATTR_USE_BW 0x00400000 +#define LS_ATTR_ADJ_SID 0x00800000 +#define LS_ATTR_BCK_ADJ_SID 0x01000000 +#define LS_ATTR_SRLG 0x02000000 + +/* Link State Attributes */ +struct ls_attributes { + uint32_t flags; /* Flag for parameters validity */ + struct ls_node_id adv; /* Adv. Router of this Link State */ + char name[MAX_NAME_LENGTH]; /* Name of the Edge. Could be null */ + struct { /* Standard TE metrics */ + uint32_t metric; /* IGP standard metric */ + uint32_t te_metric; /* Traffic Engineering metric */ + uint32_t admin_group; /* Administrative Group */ + struct in_addr local; /* Local IPv4 address */ + struct in_addr remote; /* Remote IPv4 address */ + struct in6_addr local6; /* Local IPv6 address */ + struct in6_addr remote6; /* Remote IPv6 address */ + uint32_t local_id; /* Local Identifier */ + uint32_t remote_id; /* Remote Identifier */ + float max_bw; /* Maximum Link Bandwidth */ + float max_rsv_bw; /* Maximum Reservable BW */ + float unrsv_bw[8]; /* Unreserved BW per CT (8) */ + uint32_t remote_as; /* Remote AS number */ + struct in_addr remote_addr; /* Remote IPv4 address */ + struct in6_addr remote_addr6; /* Remote IPv6 address */ + } standard; +#define LS_ATTR_STANDARD_SIZE 124 + struct { /* Extended TE Metrics */ + uint32_t delay; /* Unidirectional average delay */ + uint32_t min_delay; /* Unidirectional minimum delay */ + uint32_t max_delay; /* Unidirectional maximum delay */ + uint32_t jitter; /* Unidirectional delay variation */ + uint32_t pkt_loss; /* Unidirectional packet loss */ + float ava_bw; /* Available Bandwidth */ + float rsv_bw; /* Reserved Bandwidth */ + float used_bw; /* Utilized Bandwidth */ + } extended; +#define LS_ATTR_EXTENDED_SIZE 32 + struct { /* (LAN)-Adjacency SID for OSPF */ + uint32_t sid; /* SID as MPLS label or index */ + uint8_t flags; /* Flags */ + uint8_t weight; /* Administrative weight */ + union { + struct in_addr addr; /* Neighbor @IP for OSPF */ + uint8_t sysid[ISO_SYS_ID_LEN]; /* or Sys-ID for ISIS */ + } neighbor; + } adj_sid[2]; /* Primary & Backup (LAN)-Adj. SID */ +#define LS_ATTR_ADJ_SID_SIZE 120 + uint32_t *srlgs; /* List of Shared Risk Link Group */ + uint8_t srlg_len; /* number of SRLG in the list */ +}; + +/* Link State flags to indicate which Prefix parameters are valid */ +#define LS_PREF_UNSET 0x00 +#define LS_PREF_IGP_FLAG 0x01 +#define LS_PREF_ROUTE_TAG 0x02 +#define LS_PREF_EXTENDED_TAG 0x04 +#define LS_PREF_METRIC 0x08 +#define LS_PREF_SR 0x10 + +/* Link State Prefix */ +struct ls_prefix { + uint8_t flags; /* Flag for parameters validity */ + struct ls_node_id adv; /* Adv. Router of this Link State */ + struct prefix pref; /* IPv4 or IPv6 prefix */ + uint8_t igp_flag; /* IGP Flags associated to the prefix */ + uint32_t route_tag; /* IGP Route Tag */ + uint64_t extended_tag; /* IGP Extended Route Tag */ + uint32_t metric; /* Route metric for this prefix */ + struct { + uint32_t sid; /* Segment Routing ID */ + uint8_t sid_flag; /* Segment Routing Flags */ + uint8_t algo; /* Algorithm for Segment Routing */ + } sr; +}; + +/** + * Create a new Link State Node. Structure is dynamically allocated. + * + * @param adv Mandatory Link State Node ID i.e. advertise router information + * @param rid Router ID as IPv4 address + * @param rid6 Router ID as IPv6 address + * + * @return New Link State Node + */ +extern struct ls_node *ls_node_new(struct ls_node_id adv, struct in_addr rid, + struct in6_addr rid6); + +/** + * Remove Link State Node. Data structure is freed. + * + * @param node Pointer to a valid Link State Node structure + */ +extern void ls_node_del(struct ls_node *node); + +/** + * Check if two Link State Nodes are equal. Note that this routine has the same + * return value sense as '==' (which is different from a comparison). + * + * @param n1 First Link State Node to be compare + * @param n2 Second Link State Node to be compare + * + * @return 1 if equal, 0 otherwise + */ +extern int ls_node_same(struct ls_node *n1, struct ls_node *n2); + +/** + * Create a new Link State Attributes. Structure is dynamically allocated. + * At least one of parameters MUST be valid and not equal to 0. + * + * @param adv Mandatory Link State Node ID i.e. advertise router ID + * @param local Local IPv4 address + * @param local6 Local Ipv6 address + * @param local_id Local Identifier + * + * @return New Link State Attributes + */ +extern struct ls_attributes *ls_attributes_new(struct ls_node_id adv, + struct in_addr local, + struct in6_addr local6, + uint32_t local_id); + +/** + * Remove Link State Attributes. Data structure is freed. + * + * @param attr Pointer to a valid Link State Attribute structure + */ +extern void ls_attributes_del(struct ls_attributes *attr); + +/** + * Check if two Link State Attributes are equal. Note that this routine has the + * same return value sense as '==' (which is different from a comparison). + * + * @param a1 First Link State Attributes to be compare + * @param a2 Second Link State Attributes to be compare + * + * @return 1 if equal, 0 otherwise + */ +extern int ls_attributes_same(struct ls_attributes *a1, + struct ls_attributes *a2); + +/** + * In addition a Graph model is defined as an overlay on top of link state + * database in order to ease Path Computation algorithm implementation. + * Denoted G(V, E), a graph is composed by a list of Vertices (V) which + * represents the network Node and a list of Edges (E) which represents node + * Link. An additional list of prefixes (P) is also added. + * A prefix (P) is also attached to the Vertex (V) which advertise it. + * + * Vertex (V) contains the list of outgoing Edges (E) that connect this Vertex + * with its direct neighbors and the list of incoming Edges (E) that connect + * the direct neighbors to this Vertex. Indeed, the Edge (E) is unidirectional, + * thus, it is necessary to add 2 Edges to model a bidirectional relation + * between 2 Vertices. + * + * Edge (E) contains the source and destination Vertex that this Edge + * is connecting. + * + * A unique Key is used to identify both Vertices and Edges within the Graph. + * An easy way to build this key is to used the IP address: i.e. loopback + * address for Vertices and link IP address for Edges. + * + * -------------- --------------------------- -------------- + * | Connected |---->| Connected Edge Va to Vb |--->| Connected | + * --->| Vertex | --------------------------- | Vertex |----> + * | | | | + * | - Key (Va) | | - Key (Vb) | + * <---| - Vertex | --------------------------- | - Vertex |<---- + * | |<----| Connected Edge Vb to Va |<---| | + * -------------- --------------------------- -------------- + * + */ + +/* Link State Vertex structure */ +PREDECL_RBTREE_UNIQ(vertices) +struct ls_vertex { + struct vertices_item entry; /* Entry in RB Tree */ + uint64_t key; /* Unique Key identifier */ + struct ls_node *node; /* Link State Node */ + struct list *incoming_edges; /* List of incoming Link State links */ + struct list *outgoing_edges; /* List of outgoing Link State links */ + struct list *prefixes; /* List of advertised prefix */ +}; + +/* Link State Edge structure */ +PREDECL_RBTREE_UNIQ(edges) +struct ls_edge { + struct edges_item entry; /* Entry in RB tree */ + uint64_t key; /* Unique Key identifier */ + struct ls_attributes *attributes; /* Link State attributes */ + struct ls_vertex *source; /* Pointer to the source Vertex */ + struct ls_vertex *destination; /* Pointer to the destination Vertex */ +}; + +/* Link State Subnet structure */ +PREDECL_RBTREE_UNIQ(subnets) +struct ls_subnet { + struct subnets_item entry; /* Entry in RB tree */ + struct prefix key; /* Unique Key identifier */ + struct ls_vertex *vertex; /* Back pointer to the Vertex owner */ + struct ls_prefix *ls_pref; /* Link State Prefix */ +}; + +/* Declaration of Vertices, Edges and Prefixes RB Trees */ +macro_inline int vertex_cmp(const struct ls_vertex *node1, + const struct ls_vertex *node2) +{ + return (node1->key - node2->key); +} +DECLARE_RBTREE_UNIQ(vertices, struct ls_vertex, entry, vertex_cmp) + +macro_inline int edge_cmp(const struct ls_edge *edge1, + const struct ls_edge *edge2) +{ + return (edge1->key - edge2->key); +} +DECLARE_RBTREE_UNIQ(edges, struct ls_edge, entry, edge_cmp) + +macro_inline int subnet_cmp(const struct ls_subnet *a, + const struct ls_subnet *b) +{ + return prefix_cmp(&a->key, &b->key); +} +DECLARE_RBTREE_UNIQ(subnets, struct ls_subnet, entry, subnet_cmp) + +/* Link State TED Structure */ +struct ls_ted { + uint32_t key; /* Unique identifier */ + char name[MAX_NAME_LENGTH]; /* Name of this graph. Could be null */ + uint32_t as_number; /* AS number of the modeled network */ + struct ls_vertex *self; /* Vertex of the FRR instance */ + struct vertices_head vertices; /* List of Vertices */ + struct edges_head edges; /* List of Edges */ + struct subnets_head subnets; /* List of Subnets */ +}; + +/** + * Create a new Link State Vertex structure and initialize is with the Link + * State Node parameter. + * + * @param node Link State Node + * + * @return New Vertex + */ +extern struct ls_vertex *ls_vertex_new(struct ls_node *node); + +/** + * Delete Link State Vertex. This function clean internal Vertex lists (incoming + * and outgoing Link State Edge and Link State Subnet). Note that referenced + * objects of the different lists (Edges & SubNet) are not removed as they could + * be connected to other Vertices. + * + * @param vertex Link State Vertex to be removed + */ +extern void ls_vertex_del(struct ls_vertex *vertex); + +/** + * Add new vertex to the Link State DB. Vertex is created from the Link State + * Node. Vertex data structure is dynamically allocated. + * + * @param ted Traffic Engineering Database structure + * @param node Link State Node + * + * @return New Vertex or NULL in case of error + */ +extern struct ls_vertex *ls_vertex_add(struct ls_ted *ted, + struct ls_node *node); + +/** + * Update Vertex with the Link State Node. A new vertex is created if no one + * corresponds to the Link State Node. + * + * @param ted Link State Data Base + * @param node Link State Node to be updated + * + * @return Updated Link State Vertex or Null in case of error + */ +extern struct ls_vertex *ls_vertex_update(struct ls_ted *ted, + struct ls_node *node); + +/** + * Remove Vertex from the Link State DB. Vertex Data structure is freed but + * not the Link State Node. Link State DB is not modified if Vertex is NULL or + * not found in the Data Base. + * + * @param ted Link State Data Base + * @param vertex Vertex to be removed + */ +extern void ls_vertex_remove(struct ls_ted *ted, struct ls_vertex *vertex); + +/** + * Find Vertex in the Link State DB by its unique key. + * + * @param ted Link State Data Base + * @param key Vertex Key different from 0 + * + * @return Vertex if found, NULL otherwise + */ +extern struct ls_vertex *ls_find_vertex_by_key(struct ls_ted *ted, + const uint64_t key); + +/** + * Find Vertex in the Link State DB by its Link State Node. + * + * @param ted Link State Data Base + * @param nid Link State Node ID + * + * @return Vertex if found, NULL otherwise + */ +extern struct ls_vertex *ls_find_vertex_by_id(struct ls_ted *ted, + struct ls_node_id nid); + +/** + * Check if two Vertices are equal. Note that this routine has the same return + * value sense as '==' (which is different from a comparison). + * + * @param v1 First vertex to compare + * @param v2 Second vertex to compare + * + * @return 1 if equal, 0 otherwise + */ +extern int ls_vertex_same(struct ls_vertex *v1, struct ls_vertex *v2); + +/** + * Add new Edge to the Link State DB. Edge is created from the Link State + * Attributes. Edge data structure is dynamically allocated. + * + * @param ted Link State Data Base + * @param attributes Link State attributes + * + * @return New Edge or NULL in case of error + */ +extern struct ls_edge *ls_edge_add(struct ls_ted *ted, + struct ls_attributes *attributes); + +/** + * Update the Link State Attributes information of an existing Edge. If there is + * no corresponding Edge in the Link State Data Base, a new Edge is created. + * + * @param ted Link State Data Base + * @param attributes Link State Attributes + * + * @return Updated Link State Edge, or NULL in case of error + */ +extern struct ls_edge *ls_edge_update(struct ls_ted *ted, + struct ls_attributes *attributes); + +/** + * Remove Edge from the Link State DB. Edge data structure is freed but not the + * Link State Attributes data structure. Link State DB is not modified if Edge + * is NULL or not found in the Data Base. + * + * @param ted Link State Data Base + * @param edge Edge to be removed + */ +extern void ls_edge_del(struct ls_ted *ted, struct ls_edge *edge); + +/** + * Find Edge in the Link State Data Base by Edge key. + * + * @param ted Link State Data Base + * @param key Edge key + * + * @return Edge if found, NULL otherwise + */ +extern struct ls_edge *ls_find_edge_by_key(struct ls_ted *ted, + const uint64_t key); + +/** + * Find Edge in the Link State Data Base by the source (local IPv4 or IPv6 + * address or local ID) informations of the Link + * State Attributes + * + * @param ted Link State Data Base + * @param attributes Link State Attributes + * + * @return Edge if found, NULL otherwise + */ +extern struct ls_edge * +ls_find_edge_by_source(struct ls_ted *ted, struct ls_attributes *attributes); + +/** + * Find Edge in the Link State Data Base by the destination (remote IPv4 or IPv6 + * address of remote ID) information of the Link State Attributes + * + * @param ted Link State Data Base + * @param attributes Link State Attributes + * + * @return Edge if found, NULL otherwise + */ +extern struct ls_edge * +ls_find_edge_by_destination(struct ls_ted *ted, + struct ls_attributes *attributes); + +/** + * Add new Subnet to the Link State DB. Subnet is created from the Link State + * prefix. Subnet data structure is dynamically allocated. + * + * @param ted Link State Data Base + * @param pref Link State Prefix + * + * @return New Subnet + */ +extern struct ls_subnet *ls_subnet_add(struct ls_ted *ted, + struct ls_prefix *pref); + +/** + * Remove Subnet from the Link State DB. Subnet data structure is freed but + * not the Link State prefix data structure. Link State DB is not modified + * if Subnet is NULL or not found in the Data Base. + * + * @param ted Link State Data Base + * @param subnet Subnet to be removed + */ +extern void ls_subnet_del(struct ls_ted *ted, struct ls_subnet *subnet); + +/** + * Find Subnet in the Link State Data Base by prefix. + * + * @param ted Link State Data Base + * @param prefix Link State Prefix + * + * @return Subnet if found, NULL otherwise + */ +extern struct ls_subnet *ls_find_subnet(struct ls_ted *ted, + const struct prefix prefix); + +/** + * Create a new Link State Data Base. + * + * @param key Unique key of the data base. Must be different from 0 + * @param name Name of the data base (may be NULL) + * @param asn AS Number for this data base. Must be different from 0 + * + * @return New Link State Database or NULL in case of error + */ +extern struct ls_ted *ls_ted_new(const uint32_t key, const char *name, + uint32_t asn); + +/** + * Delete existing Link State Data Base. + * + * @param ted Link State Data Base + */ +extern void ls_ted_del(struct ls_ted *ted); + +/** + * Connect Source and Destination Vertices by given Edge. Only non NULL source + * and destination vertices are connected. + * + * @param src Link State Source Vertex + * @param dst Link State Destination Vertex + * @param edge Link State Edge. Must not be NULL + */ +extern void ls_connect_vertices(struct ls_vertex *src, struct ls_vertex *dst, + struct ls_edge *edge); + +/** + * Connect Link State Edge to the Link State Vertex which could be a Source or + * a Destination Vertex. + * + * @param vertex Link State Vertex to be connected. Must not be NULL + * @param edge Link State Edge connection. Must not be NULL + * @param source True for a Source, false for a Destination Vertex + */ +extern void ls_connect(struct ls_vertex *vertex, struct ls_edge *edge, + bool source); + +/** + * Disconnect Link State Edge from the Link State Vertex which could be a + * Source or a Destination Vertex. + * + * @param vertex Link State Vertex to be connected. Must not be NULL + * @param edge Link State Edge connection. Must not be NULL + * @param source True for a Source, false for a Destination Vertex + */ +extern void ls_disconnect(struct ls_vertex *vertex, struct ls_edge *edge, + bool source); + +/** + * Disconnect Link State Edge from both Source and Destination Vertex. + * + * @param edge Link State Edge to be disconnected + */ +extern void ls_disconnect_edge(struct ls_edge *edge); + + +/** + * The Link State Message is defined to convey Link State parameters from + * the routing protocol (OSPF or IS-IS) to other daemons e.g. BGP. + * + * The structure is composed of: + * - Event of the message: + * - Sync: Send the whole LS DB following a request + * - Add: Send the a new Link State element + * - Update: Send an update of an existing Link State element + * - Delete: Indicate that the given Link State element is removed + * - Type of Link State element: Node, Attribute or Prefix + * - Remote node id when known + * - Data: Node, Attributes or Prefix + * + * A Link State Message can carry only one Link State Element (Node, Attributes + * of Prefix) at once, and only one Link State Message is sent through ZAPI + * Opaque Link State type at once. + */ + +/* ZAPI Opaque Link State Message Event */ +#define LS_MSG_EVENT_SYNC 1 +#define LS_MSG_EVENT_ADD 2 +#define LS_MSG_EVENT_UPDATE 3 +#define LS_MSG_EVENT_DELETE 4 + +/* ZAPI Opaque Link State Message sub-Type */ +#define LS_MSG_TYPE_NODE 1 +#define LS_MSG_TYPE_ATTRIBUTES 2 +#define LS_MSG_TYPE_PREFIX 3 + +/* Link State Message */ +struct ls_message { + uint8_t event; /* Message Event: Sync, Add, Update, Delete */ + uint8_t type; /* Message Data Type: Node, Attribute, Prefix */ + struct ls_node_id remote_id; /* Remote Link State Node ID */ + union { + struct ls_node *node; /* Link State Node */ + struct ls_attributes *attr; /* Link State Attributes */ + struct ls_prefix *prefix; /* Link State Prefix */ + } data; +}; + +/** + * Parse Link State Message from stream. Used this function once receiving a + * new ZAPI Opaque message of type Link State. + * + * @param s Stream buffer. Must not be NULL. + * + * @return New Link State Message or NULL in case of error + */ +extern struct ls_message *ls_parse_msg(struct stream *s); + +/** + * Delete existing message, freeing all substructure. + * + * @param msg Link state message to be deleted + */ +extern void ls_delete_msg(struct ls_message *msg); + +/** + * Send Link State Message as new ZAPI Opaque message of type Link State. + * If destination is not NULL, message is sent as Unicast otherwise it is + * broadcast to all registered daemon. + * + * @param zclient Zebra Client + * @param msg Link State Message to be sent + * @param dst Destination daemon for unicast message, + * NULL for broadcast message + * + * @return 0 on success, -1 otherwise + */ +extern int ls_send_msg(struct zclient *zclient, struct ls_message *msg, + struct zapi_opaque_reg_info *dst); + +/** + * Create a new Link State Message from a Link State Vertex. If Link State + * Message is NULL, a new data structure is dynamically allocated. + * + * @param msg Link State Message to be filled or NULL + * @param vertex Link State Vertex. Must not be NULL + * + * @return New Link State Message msg parameter is NULL or pointer + * to the provided Link State Message + */ +extern struct ls_message *ls_vertex2msg(struct ls_message *msg, + struct ls_vertex *vertex); + +/** + * Create a new Link State Message from a Link State Edge. If Link State + * Message is NULL, a new data structure is dynamically allocated. + * + * @param msg Link State Message to be filled or NULL + * @param edge Link State Edge. Must not be NULL + * + * @return New Link State Message msg parameter is NULL or pointer + * to the provided Link State Message + */ +extern struct ls_message *ls_edge2msg(struct ls_message *msg, + struct ls_edge *edge); + +/** + * Create a new Link State Message from a Link State Subnet. If Link State + * Message is NULL, a new data structure is dynamically allocated. + * + * @param msg Link State Message to be filled or NULL + * @param subnet Link State Subnet. Must not be NULL + * + * @return New Link State Message msg parameter is NULL or pointer + * to the provided Link State Message + */ +extern struct ls_message *ls_subnet2msg(struct ls_message *msg, + struct ls_subnet *subnet); + +/** + * Send all the content of the Link State Data Base to the given destination. + * Link State content is sent is this order: Vertices, Edges, Subnet. + * This function must be used when a daemon request a Link State Data Base + * Synchronization. + * + * @param ted Link State Data Base. Must not be NULL + * @param zclient Zebra Client. Must not be NULL + * @param dst Destination FRR daemon. Must not be NULL + * + * @return 0 on success, -1 otherwise + */ +extern int ls_sync_ted(struct ls_ted *ted, struct zclient *zclient, + struct zapi_opaque_reg_info *dst); + +/** + * Dump all Link State Data Base elements for debugging purposes + * + * @param ted Link State Data Base. Must not be NULL + * + */ +extern void ls_dump_ted(struct ls_ted *ted); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_LINK_STATE_H_ */ diff --git a/lib/subdir.am b/lib/subdir.am index 038282a99b..ee9e827ee8 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -48,6 +48,7 @@ lib_libfrr_la_SOURCES = \ lib/libfrr.c \ lib/libfrr_trace.c \ lib/linklist.c \ + lib/link_state.c \ lib/log.c \ lib/log_filter.c \ lib/log_vty.c \ @@ -208,6 +209,7 @@ pkginclude_HEADERS += \ lib/libfrr_trace.h \ lib/libospf.h \ lib/linklist.h \ + lib/link_state.h \ lib/log.h \ lib/log_vty.h \ lib/md5.h \ diff --git a/pathd/path_pcep_controller.c b/pathd/path_pcep_controller.c index e467a79a87..255503b459 100644 --- a/pathd/path_pcep_controller.c +++ b/pathd/path_pcep_controller.c @@ -26,6 +26,7 @@ #include "northbound.h" #include "frr_pthread.h" #include "jhash.h" +#include "network.h" #include "pathd/pathd.h" #include "pathd/path_errors.h" @@ -1043,7 +1044,7 @@ void remove_pcc_state(struct ctrl_state *ctrl_state, uint32_t backoff_delay(uint32_t max, uint32_t base, uint32_t retry_count) { uint32_t a = min(max, base * (1 << retry_count)); - uint64_t r = rand(), m = RAND_MAX; + uint64_t r = frr_weak_random(), m = RAND_MAX; uint32_t b = (a / 2) + (r * (a / 2)) / m; return b; } diff --git a/pathd/pathd.c b/pathd/pathd.c index 2e2fa86714..e2c7c95728 100644 --- a/pathd/pathd.c +++ b/pathd/pathd.c @@ -21,6 +21,7 @@ #include "memory.h" #include "log.h" #include "lib_errors.h" +#include "network.h" #include "pathd/pathd.h" #include "pathd/path_memory.h" @@ -480,7 +481,7 @@ struct srte_candidate *srte_candidate_add(struct srte_policy *policy, candidate->preference = preference; candidate->policy = policy; candidate->type = SRTE_CANDIDATE_TYPE_UNDEFINED; - candidate->discriminator = rand(); + candidate->discriminator = frr_weak_random(); lsp->candidate = candidate; candidate->lsp = lsp; diff --git a/pbrd/pbr_vty.c b/pbrd/pbr_vty.c index 26163dcc56..216834fe0c 100644 --- a/pbrd/pbr_vty.c +++ b/pbrd/pbr_vty.c @@ -137,6 +137,11 @@ DEFPY(pbr_map_match_src, pbr_map_match_src_cmd, { struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + if (pbrms->dst && pbrms->family && prefix->family != pbrms->family) { + vty_out(vty, "Cannot mismatch families within match src/dst\n"); + return CMD_WARNING_CONFIG_FAILED; + } + pbrms->family = prefix->family; if (!no) { @@ -165,6 +170,11 @@ DEFPY(pbr_map_match_dst, pbr_map_match_dst_cmd, { struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + if (pbrms->src && pbrms->family && prefix->family != pbrms->family) { + vty_out(vty, "Cannot mismatch families within match src/dst\n"); + return CMD_WARNING_CONFIG_FAILED; + } + pbrms->family = prefix->family; if (!no) { diff --git a/snapcraft/snapcraft.yaml.in b/snapcraft/snapcraft.yaml.in index c0c31b5fd1..1836f34979 100644 --- a/snapcraft/snapcraft.yaml.in +++ b/snapcraft/snapcraft.yaml.in @@ -259,7 +259,7 @@ parts: - usr/lib/x86_64-linux-gnu/libssh.so* source: https://github.com/rtrlib/rtrlib.git source-type: git - source-tag: v0.6.3 + source-tag: v0.7.0 plugin: cmake configflags: - -DCMAKE_BUILD_TYPE=Release @@ -392,6 +392,6 @@ parts: passthrough: layout: - /usr/lib/x86_64-linux-gnu/libyang: - bind: $SNAP/usr/lib/x86_64-linux-gnu/libyang + /usr/lib/x86_64-linux-gnu/libyang1: + bind: $SNAP/usr/lib/x86_64-linux-gnu/libyang1 diff --git a/tests/topotests/bgp_community_change_update/__init__.py b/tests/topotests/bgp_community_change_update/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/__init__.py diff --git a/tests/topotests/bgp_community_change_update/c1/bgpd.conf b/tests/topotests/bgp_community_change_update/c1/bgpd.conf new file mode 100644 index 0000000000..24cf9dffb9 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/c1/bgpd.conf @@ -0,0 +1,11 @@ +! +debug bgp updates +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 10.0.1.2 remote-as external + neighbor 10.0.1.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_community_change_update/c1/zebra.conf b/tests/topotests/bgp_community_change_update/c1/zebra.conf new file mode 100644 index 0000000000..e3dbbc051d --- /dev/null +++ b/tests/topotests/bgp_community_change_update/c1/zebra.conf @@ -0,0 +1,6 @@ +! +interface c1-eth0 + ip address 10.0.1.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_community_change_update/test_bgp_community_change_update.py b/tests/topotests/bgp_community_change_update/test_bgp_community_change_update.py new file mode 100644 index 0000000000..5fc4310266 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/test_bgp_community_change_update.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python + +# Copyright (c) 2020 by +# Donatas Abraitis <donatas.abraitis@gmail.com> +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +Reference: https://www.cmand.org/communityexploration + + --y2-- + / | \ + c1 ---- x1 ---- y1 | z1 + \ | / + --y3-- + +1. z1 announces 192.168.255.254/32 to y2, y3. +2. y2 and y3 tags this prefix at ingress with appropriate +communities 65004:2 (y2) and 65004:3 (y3). +3. x1 filters all communities at the egress to c1. +4. Shutdown the link between y1 and y2. +5. y1 will generate a BGP UPDATE message regarding the next-hop change. +6. x1 will generate a BGP UPDATE message regarding community change. + +To avoid sending duplicate BGP UPDATE messages we should make sure +we send only actual route updates. In this example, x1 will skip +BGP UPDATE to c1 because the actual route is the same +(filtered communities - nothing changes). +""" + +import os +import sys +import json +import pytest +import functools + +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 +from mininet.topo import Topo + +from lib.common_config import step +from time import sleep + + +class TemplateTopo(Topo): + def build(self, *_args, **_opts): + tgen = get_topogen(self) + + tgen.add_router("z1") + tgen.add_router("y1") + tgen.add_router("y2") + tgen.add_router("y3") + tgen.add_router("x1") + tgen.add_router("c1") + + # 10.0.1.0/30 + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["c1"]) + switch.add_link(tgen.gears["x1"]) + + # 10.0.2.0/30 + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["x1"]) + switch.add_link(tgen.gears["y1"]) + + # 10.0.3.0/30 + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["y1"]) + switch.add_link(tgen.gears["y2"]) + + # 10.0.4.0/30 + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["y1"]) + switch.add_link(tgen.gears["y3"]) + + # 10.0.5.0/30 + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["y2"]) + switch.add_link(tgen.gears["y3"]) + + # 10.0.6.0/30 + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["y2"]) + switch.add_link(tgen.gears["z1"]) + + # 10.0.7.0/30 + switch = tgen.add_switch("s7") + switch.add_link(tgen.gears["y3"]) + switch.add_link(tgen.gears["z1"]) + + +def setup_module(mod): + tgen = Topogen(TemplateTopo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + 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)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_community_update_path_change(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_converge_initial(): + output = json.loads( + tgen.gears["c1"].vtysh_cmd("show ip bgp neighbor 10.0.1.2 json") + ) + expected = { + "10.0.1.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 8}}, + } + } + return topotest.json_cmp(output, expected) + + step("Check if an initial topology is converged") + test_func = functools.partial(_bgp_converge_initial) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see bgp convergence in c1" + + step("Disable link between y1 and y2") + tgen.gears["y1"].run("ip link set dev y1-eth1 down") + + def _bgp_converge_link_disabled(): + output = json.loads(tgen.gears["y1"].vtysh_cmd("show ip bgp nei 10.0.3.2 json")) + expected = {"10.0.3.2": {"bgpState": "Active"}} + return topotest.json_cmp(output, expected) + + step("Check if a topology is converged after a link down between y1 and y2") + test_func = functools.partial(_bgp_converge_link_disabled) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see bgp convergence in y1" + + def _bgp_check_for_duplicate_updates(): + duplicate = False + i = 0 + while i < 5: + if ( + len( + tgen.gears["c1"].run( + 'grep "10.0.1.2 rcvd 192.168.255.254/32 IPv4 unicast...duplicate ignored" bgpd.log' + ) + ) + > 0 + ): + duplicate = True + i += 1 + sleep(0.5) + return duplicate + + step("Check if we see duplicate BGP UPDATE message in c1 (suppress-duplicates)") + assert ( + _bgp_check_for_duplicate_updates() == False + ), "Seen duplicate BGP UPDATE message in c1 from x1" + + step("Disable bgp suppress-duplicates at x1") + tgen.gears["x1"].run( + "vtysh -c 'conf' -c 'router bgp' -c 'no bgp suppress-duplicates'" + ) + + step("Enable link between y1 and y2") + tgen.gears["y1"].run("ip link set dev y1-eth1 up") + + def _bgp_converge_link_enabled(): + output = json.loads(tgen.gears["y1"].vtysh_cmd("show ip bgp nei 10.0.3.2 json")) + expected = { + "10.0.3.2": { + "bgpState": "Established", + "addressFamilyInfo": { + "ipv4Unicast": {"acceptedPrefixCounter": 5, "sentPrefixCounter": 4} + }, + } + } + return topotest.json_cmp(output, expected) + + step("Check if a topology is converged after a link up between y1 and y2") + test_func = functools.partial(_bgp_converge_link_enabled) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see bgp convergence in y1" + + step( + "Check if we see duplicate BGP UPDATE message in c1 (no bgp suppress-duplicates)" + ) + assert ( + _bgp_check_for_duplicate_updates() == True + ), "Didn't see duplicate BGP UPDATE message in c1 from x1" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_community_change_update/x1/bgpd.conf b/tests/topotests/bgp_community_change_update/x1/bgpd.conf new file mode 100644 index 0000000000..8d7bcb948b --- /dev/null +++ b/tests/topotests/bgp_community_change_update/x1/bgpd.conf @@ -0,0 +1,20 @@ +! +debug bgp updates +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 10.0.1.1 remote-as external + neighbor 10.0.1.1 timers 3 10 + neighbor 10.0.2.2 remote-as external + neighbor 10.0.2.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + neighbor 10.0.1.1 route-map c1 out + exit-address-family +! +bgp community-list standard c1 seq 1 permit 65004:2 +bgp community-list standard c1 seq 2 permit 65004:3 +! +route-map c1 permit 10 + set comm-list c1 delete +! diff --git a/tests/topotests/bgp_community_change_update/x1/zebra.conf b/tests/topotests/bgp_community_change_update/x1/zebra.conf new file mode 100644 index 0000000000..8b7c03ffbf --- /dev/null +++ b/tests/topotests/bgp_community_change_update/x1/zebra.conf @@ -0,0 +1,9 @@ +! +interface x1-eth0 + ip address 10.0.1.2/30 +! +interface x1-eth1 + ip address 10.0.2.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_community_change_update/y1/bgpd.conf b/tests/topotests/bgp_community_change_update/y1/bgpd.conf new file mode 100644 index 0000000000..a010085835 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y1/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 10.0.2.1 remote-as external + neighbor 10.0.2.1 timers 3 10 + neighbor 10.0.3.2 remote-as internal + neighbor 10.0.3.2 timers 3 10 + neighbor 10.0.4.2 remote-as internal + neighbor 10.0.4.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_community_change_update/y1/zebra.conf b/tests/topotests/bgp_community_change_update/y1/zebra.conf new file mode 100644 index 0000000000..4096f2a2e3 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y1/zebra.conf @@ -0,0 +1,12 @@ +! +interface y1-eth0 + ip address 10.0.2.2/30 +! +interface y1-eth1 + ip address 10.0.3.1/30 +! +interface y1-eth2 + ip address 10.0.4.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_community_change_update/y2/bgpd.conf b/tests/topotests/bgp_community_change_update/y2/bgpd.conf new file mode 100644 index 0000000000..27f1e81f7d --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y2/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 10.0.3.1 remote-as internal + neighbor 10.0.3.1 timers 3 10 + neighbor 10.0.5.2 remote-as internal + neighbor 10.0.5.2 timers 3 10 + neighbor 10.0.6.2 remote-as external + neighbor 10.0.6.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + neighbor 10.0.3.1 route-reflector-client + neighbor 10.0.5.2 route-reflector-client + neighbor 10.0.6.2 route-map z1 in + exit-address-family +! +route-map z1 permit 10 + set community 65004:2 +! diff --git a/tests/topotests/bgp_community_change_update/y2/zebra.conf b/tests/topotests/bgp_community_change_update/y2/zebra.conf new file mode 100644 index 0000000000..7e9458a45c --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y2/zebra.conf @@ -0,0 +1,12 @@ +! +interface y2-eth0 + ip address 10.0.3.2/30 +! +interface y2-eth1 + ip address 10.0.5.1/30 +! +interface y2-eth2 + ip address 10.0.6.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_community_change_update/y3/bgpd.conf b/tests/topotests/bgp_community_change_update/y3/bgpd.conf new file mode 100644 index 0000000000..8e57284929 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y3/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 10.0.4.1 remote-as internal + neighbor 10.0.4.1 timers 3 10 + neighbor 10.0.5.1 remote-as internal + neighbor 10.0.5.1 timers 3 10 + neighbor 10.0.7.2 remote-as external + neighbor 10.0.7.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + neighbor 10.0.4.1 route-reflector-client + neighbor 10.0.5.1 route-reflector-client + neighbor 10.0.7.2 route-map z1 in + exit-address-family +! +route-map z1 permit 10 + set community 65004:3 +! diff --git a/tests/topotests/bgp_community_change_update/y3/zebra.conf b/tests/topotests/bgp_community_change_update/y3/zebra.conf new file mode 100644 index 0000000000..93dd87dbd2 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y3/zebra.conf @@ -0,0 +1,12 @@ +! +interface y3-eth0 + ip address 10.0.4.2/30 +! +interface y3-eth1 + ip address 10.0.5.2/30 +! +interface y3-eth2 + ip address 10.0.7.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_community_change_update/z1/bgpd.conf b/tests/topotests/bgp_community_change_update/z1/bgpd.conf new file mode 100644 index 0000000000..2f470f18b8 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/z1/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 65004 + no bgp ebgp-requires-policy + neighbor 10.0.6.1 remote-as external + neighbor 10.0.6.1 timers 3 10 + neighbor 10.0.7.1 remote-as external + neighbor 10.0.7.1 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_community_change_update/z1/zebra.conf b/tests/topotests/bgp_community_change_update/z1/zebra.conf new file mode 100644 index 0000000000..7f6bbb66b3 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/z1/zebra.conf @@ -0,0 +1,12 @@ +! +interface lo + ip address 192.168.255.254/32 +! +interface z1-eth0 + ip address 10.0.6.2/30 +! +interface z1-eth1 + ip address 10.0.7.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1.py b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1.py index 94fd17e012..3a2d283025 100644 --- a/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1.py +++ b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1.py @@ -1488,7 +1488,7 @@ def test_BGP_GR_TC_5_1_2_p1(request): shutdown_bringup_interface(tgen, "r2", intf) # Bring up Interface - shutdown_bringup_interface(tgen, dut, intf, ifaceaction=True) + shutdown_bringup_interface(tgen, "r2", intf, ifaceaction=True) for addr_type in ADDR_TYPES: result = verify_graceful_restart( @@ -1775,7 +1775,7 @@ def test_BGP_GR_TC_6_1_2_p1(request): shutdown_bringup_interface(tgen, "r2", intf) # Bring up Interface - shutdown_bringup_interface(tgen, dut, intf, ifaceaction=True) + shutdown_bringup_interface(tgen, "r2", intf, ifaceaction=True) for addr_type in ADDR_TYPES: result = verify_graceful_restart( diff --git a/tests/topotests/bgp_large_community/test_bgp_large_community_topo_1.py b/tests/topotests/bgp_large_community/test_bgp_large_community_topo_1.py index 40489f438f..31fbdcd4b5 100644 --- a/tests/topotests/bgp_large_community/test_bgp_large_community_topo_1.py +++ b/tests/topotests/bgp_large_community/test_bgp_large_community_topo_1.py @@ -1174,6 +1174,39 @@ def test_large_community_boundary_values(request): ) +def test_large_community_invalid_chars(request): + """ + BGP canonical lcommunities must only be digits + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + input_dict = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": "1:a:2", + "large": True, + } + ] + } + } + + step("Checking boundary value for community 1:a:2") + result = create_bgp_community_lists(tgen, input_dict) + assert result is not True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + def test_large_community_after_clear_bgp(request): """ Clear BGP neighbor-ship and check if large community and community diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py index 70c4c061f8..27efecb762 100644 --- a/tests/topotests/lib/common_config.py +++ b/tests/topotests/lib/common_config.py @@ -31,13 +31,14 @@ from re import search as re_search from tempfile import mkdtemp import os -import io import sys import traceback import socket import ipaddress import platform + if sys.version_info[0] > 2: + import io import configparser else: import StringIO @@ -150,8 +151,8 @@ class InvalidCLIError(Exception): def run_frr_cmd(rnode, cmd, isjson=False): """ - Execute frr show commands in priviledged mode - * `rnode`: router node on which commands needs to executed + Execute frr show commands in privileged mode + * `rnode`: router node on which command needs to be executed * `cmd`: Command to be executed on frr * `isjson`: If command is to get json data or not :return str: @@ -183,11 +184,11 @@ def apply_raw_config(tgen, input_dict): """ API to configure raw configuration on device. This can be used for any cli - which does has not been implemented in JSON. + which has not been implemented in JSON. Parameters ---------- - * `tgen`: tgen onject + * `tgen`: tgen object * `input_dict`: configuration that needs to be applied Usage @@ -231,8 +232,8 @@ def create_common_configuration( frr_json.conf and load to router Parameters ---------- - * `tgen`: tgen onject - * `data`: Congiguration data saved in a list. + * `tgen`: tgen object + * `data`: Configuration data saved in a list. * `router` : router id to be configured. * `config_type` : Syntactic information while writing configuration. Should be one of the value as mentioned in the config_map below. @@ -256,6 +257,7 @@ def create_common_configuration( "bgp": "! BGP Config\n", "vrf": "! VRF Config\n", "ospf": "! OSPF Config\n", + "pim": "! PIM Config\n", } ) @@ -291,8 +293,8 @@ def create_common_configuration( def kill_router_daemons(tgen, router, daemons): """ - Router's current config would be saved to /etc/frr/ for each deamon - and deamon would be killed forcefully using SIGKILL. + Router's current config would be saved to /etc/frr/ for each daemon + and daemon would be killed forcefully using SIGKILL. * `tgen` : topogen object * `router`: Device under test * `daemons`: list of daemons to be killed @@ -388,6 +390,8 @@ def check_router_status(tgen): daemons.append("bgpd") if "zebra" in result: daemons.append("zebra") + if "pimd" in result: + daemons.append("pimd") rnode.startDaemons(daemons) @@ -592,13 +596,13 @@ def load_config_to_router(tgen, routerName, save_bkup=False): def get_frr_ipv6_linklocal(tgen, router, intf=None, vrf=None): """ - API to get the link local ipv6 address of a perticular interface using + API to get the link local ipv6 address of a particular interface using FRR command 'show interface' - * `tgen`: tgen onject - * `router` : router for which hightest interface should be + * `tgen`: tgen object + * `router` : router for which highest interface should be calculated - * `intf` : interface for which linklocal address needs to be taken + * `intf` : interface for which link-local address needs to be taken * `vrf` : VRF name Usage @@ -687,7 +691,7 @@ def generate_support_bundle(): def start_topology(tgen, daemon=None): """ Starting topology, create tmp files which are loaded to routers - to start deamons and then start routers + to start daemons and then start routers * `tgen` : topogen object """ @@ -695,7 +699,7 @@ def start_topology(tgen, daemon=None): # Starting topology tgen.start_topology() - # Starting deamons + # Starting daemons router_list = tgen.routers() ROUTER_LIST = sorted( @@ -734,28 +738,35 @@ def start_topology(tgen, daemon=None): except IOError as err: logger.error("I/O error({0}): {1}".format(err.errno, err.strerror)) - # Loading empty zebra.conf file to router, to start the zebra deamon + # Loading empty zebra.conf file to router, to start the zebra daemon router.load_config( TopoRouter.RD_ZEBRA, "{}/{}/zebra.conf".format(TMPDIR, rname) ) - # Loading empty bgpd.conf file to router, to start the bgp deamon + # Loading empty bgpd.conf file to router, to start the bgp daemon router.load_config(TopoRouter.RD_BGP, "{}/{}/bgpd.conf".format(TMPDIR, rname)) if daemon and "ospfd" in daemon: - # Loading empty ospf.conf file to router, to start the bgp deamon + # Loading empty ospf.conf file to router, to start the bgp daemon router.load_config( TopoRouter.RD_OSPF, "{}/{}/ospfd.conf".format(TMPDIR, rname) ) - # Starting routers + + if daemon and "pimd" in daemon: + # Loading empty pimd.conf file to router, to start the pim deamon + router.load_config( + TopoRouter.RD_PIM, "{}/{}/pimd.conf".format(TMPDIR, rname) + ) + + # Starting routers logger.info("Starting all routers once topology is created") tgen.start_router() def stop_router(tgen, router): """ - Router"s current config would be saved to /tmp/topotest/<suite>/<router> for each deamon - and router and its deamons would be stopped. + Router"s current config would be saved to /tmp/topotest/<suite>/<router> for each daemon + and router and its daemons would be stopped. * `tgen` : topogen object * `router`: Device under test @@ -773,8 +784,8 @@ def stop_router(tgen, router): def start_router(tgen, router): """ - Router will started and config would be loaded from /tmp/topotest/<suite>/<router> for each - deamon + Router will be started and config would be loaded from /tmp/topotest/<suite>/<router> for each + daemon * `tgen` : topogen object * `router`: Device under test @@ -785,8 +796,8 @@ def start_router(tgen, router): try: router_list = tgen.routers() - # Router and its deamons would be started and config would - # be loaded to router for each deamon from /etc/frr + # Router and its daemons would be started and config would + # be loaded to router for each daemon from /etc/frr router_list[router].start() # Waiting for router to come up @@ -834,9 +845,201 @@ def topo_daemons(tgen, topo): if "ospf" in topo["routers"][rtr] and "ospfd" not in daemon_list: daemon_list.append("ospfd") + for val in topo["routers"][rtr]["links"].values(): + if "pim" in val and "pimd" not in daemon_list: + daemon_list.append("pimd") + break + return daemon_list +def add_interfaces_to_vlan(tgen, input_dict): + """ + Add interfaces to VLAN, we need vlan pakcage to be installed on machine + + * `tgen`: tgen onject + * `input_dict` : interfaces to be added to vlans + + input_dict= { + "r1":{ + "vlan":{ + VLAN_1: [{ + intf_r1_s1: { + "ip": "10.1.1.1", + "subnet": "255.255.255.0 + } + }] + } + } + } + + add_interfaces_to_vlan(tgen, input_dict) + + """ + + router_list = tgen.routers() + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + if "vlan" in input_dict[dut]: + for vlan, interfaces in input_dict[dut]["vlan"].items(): + for intf_dict in interfaces: + for interface, data in intf_dict.items(): + # Adding interface to VLAN + cmd = "vconfig add {} {}".format(interface, vlan) + logger.info("[DUT: %s]: Running command: %s", dut, cmd) + rnode.run(cmd) + + vlan_intf = "{}.{}".format(interface, vlan) + + ip = data["ip"] + subnet = data["subnet"] + + # Bringing interface up + cmd = "ip link set up {}".format(vlan_intf) + logger.info("[DUT: %s]: Running command: %s", dut, cmd) + rnode.run(cmd) + + # Assigning IP address + cmd = "ifconfig {} {} netmask {}".format(vlan_intf, ip, subnet) + logger.info("[DUT: %s]: Running command: %s", dut, cmd) + rnode.run(cmd) + + +def tcpdump_capture_start( + tgen, + router, + intf, + protocol=None, + grepstr=None, + timeout=0, + options=None, + cap_file=None, + background=True, +): + """ + API to capture network packets using tcp dump. + + Packages used : + + Parameters + ---------- + * `tgen`: topogen object. + * `router`: router on which ping has to be performed. + * `intf` : interface for capture. + * `protocol` : protocol for which packet needs to be captured. + * `grepstr` : string to filter out tcp dump output. + * `timeout` : Time for which packet needs to be captured. + * `options` : options for TCP dump, all tcpdump options can be used. + * `cap_file` : filename to store capture dump. + * `background` : Make tcp dump run in back ground. + + Usage + ----- + tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20, + options='-A -vv -x > r2bgp.txt ') + Returns + ------- + 1) True for successful capture + 2) errormsg - when tcp dump fails + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + rnode = tgen.routers()[router] + + if timeout > 0: + cmd = "timeout {}".format(timeout) + else: + cmd = "" + + cmdargs = "{} tcpdump".format(cmd) + + if intf: + cmdargs += " -i {}".format(str(intf)) + if protocol: + cmdargs += " {}".format(str(protocol)) + if options: + cmdargs += " -s 0 {}".format(str(options)) + + if cap_file: + file_name = os.path.join(LOGDIR, tgen.modname, router, cap_file) + cmdargs += " -w {}".format(str(file_name)) + # Remove existing capture file + rnode.run("rm -rf {}".format(file_name)) + + if grepstr: + cmdargs += ' | grep "{}"'.format(str(grepstr)) + + logger.info("Running tcpdump command: [%s]", cmdargs) + if not background: + rnode.run(cmdargs) + else: + rnode.run("nohup {} & /dev/null 2>&1".format(cmdargs)) + + # Check if tcpdump process is running + if background: + result = rnode.run("pgrep tcpdump") + logger.debug("ps -ef | grep tcpdump \n {}".format(result)) + + if not result: + errormsg = "tcpdump is not running {}".format("tcpdump") + return errormsg + else: + logger.info("Packet capture started on %s: interface %s", router, intf) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def tcpdump_capture_stop(tgen, router): + """ + API to capture network packets using tcp dump. + + Packages used : + + Parameters + ---------- + * `tgen`: topogen object. + * `router`: router on which ping has to be performed. + * `intf` : interface for capture. + * `protocol` : protocol for which packet needs to be captured. + * `grepstr` : string to filter out tcp dump output. + * `timeout` : Time for which packet needs to be captured. + * `options` : options for TCP dump, all tcpdump options can be used. + * `cap2file` : filename to store capture dump. + * `bakgrnd` : Make tcp dump run in back ground. + + Usage + ----- + tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20, + options='-A -vv -x > r2bgp.txt ') + Returns + ------- + 1) True for successful capture + 2) errormsg - when tcp dump fails + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + rnode = tgen.routers()[router] + + # Check if tcpdump process is running + result = rnode.run("ps -ef | grep tcpdump") + logger.debug("ps -ef | grep tcpdump \n {}".format(result)) + + if not re_search(r"{}".format("tcpdump"), result): + errormsg = "tcpdump is not running {}".format("tcpdump") + return errormsg + else: + ppid = tgen.net.nameToNode[rnode.name].pid + rnode.run("set +m; pkill -P %s tcpdump &> /dev/null" % ppid) + logger.info("Stopped tcpdump capture") + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + ############################################# # Common APIs, will be used by all protocols ############################################# @@ -1136,11 +1339,11 @@ def generate_ips(network, no_of_ips): """ Returns list of IPs. based on start_ip and no_of_ips + * `network` : from here the ip will start generating, start_ip will be * `no_of_ips` : these many IPs will be generated """ - ipaddress_list = [] if type(network) is not list: network = [network] @@ -1186,7 +1389,7 @@ def find_interface_with_greater_ip(topo, router, loopback=True, interface=True): it will return highest IP from loopback IPs otherwise from physical interface IPs. * `topo` : json file data - * `router` : router for which hightest interface should be calculated + * `router` : router for which highest interface should be calculated """ link_data = topo["routers"][router]["links"] @@ -1292,7 +1495,6 @@ def retry(attempts=3, wait=2, return_is_str=True, initial_wait=0, return_is_dict _wait = kwargs.pop("wait", wait) _attempts = kwargs.pop("attempts", attempts) _attempts = int(_attempts) - expected = True if _attempts < 0: raise ValueError("attempts must be 0 or greater") @@ -1302,12 +1504,10 @@ def retry(attempts=3, wait=2, return_is_str=True, initial_wait=0, return_is_dict _return_is_str = kwargs.pop("return_is_str", return_is_str) _return_is_dict = kwargs.pop("return_is_str", return_is_dict) + _expected = kwargs.setdefault("expected", True) + kwargs.pop("expected") for i in range(1, _attempts + 1): try: - _expected = kwargs.setdefault("expected", True) - if _expected is False: - expected = _expected - kwargs.pop("expected") ret = func(*args, **kwargs) logger.debug("Function returned %s", ret) if _return_is_str and isinstance(ret, bool) and _expected: @@ -1319,11 +1519,11 @@ def retry(attempts=3, wait=2, return_is_str=True, initial_wait=0, return_is_dict if _return_is_dict and isinstance(ret, dict): return ret - if _attempts == i and expected: + if _attempts == i: generate_support_bundle() return ret except Exception as err: - if _attempts == i and expected: + if _attempts == i: generate_support_bundle() logger.info("Max number of attempts (%r) reached", _attempts) raise @@ -1365,6 +1565,17 @@ def step(msg, reset=False): _step(msg, reset) +def do_countdown(secs): + """ + Countdown timer display + """ + for i in range(secs, 0, -1): + sys.stdout.write("{} ".format(str(i))) + sys.stdout.flush() + sleep(1) + return + + ############################################# # These APIs, will used by testcase ############################################# @@ -2227,9 +2438,9 @@ def shutdown_bringup_interface(tgen, dut, intf_name, ifaceaction=False): ----- dut = "r3" intf = "r3-r1-eth0" - # Shut down ineterface + # Shut down interface shutdown_bringup_interface(tgen, dut, intf, False) - # Bring up ineterface + # Bring up interface shutdown_bringup_interface(tgen, dut, intf, True) Returns ------- @@ -2238,9 +2449,9 @@ def shutdown_bringup_interface(tgen, dut, intf_name, ifaceaction=False): router_list = tgen.routers() if ifaceaction: - logger.info("Bringing up interface : {}".format(intf_name)) + logger.info("Bringing up interface {} : {}".format(dut, intf_name)) else: - logger.info("Shutting down interface : {}".format(intf_name)) + logger.info("Shutting down interface {} : {}".format(dut, intf_name)) interface_set_status(router_list[dut], intf_name, ifaceaction) @@ -2248,7 +2459,7 @@ def shutdown_bringup_interface(tgen, dut, intf_name, ifaceaction=False): def stop_router(tgen, router): """ Router's current config would be saved to /tmp/topotest/<suite>/<router> - for each deamon and router and its deamons would be stopped. + for each daemon and router and its daemons would be stopped. * `tgen` : topogen object * `router`: Device under test @@ -2267,7 +2478,7 @@ def stop_router(tgen, router): def start_router(tgen, router): """ Router will be started and config would be loaded from - /tmp/topotest/<suite>/<router> for each deamon + /tmp/topotest/<suite>/<router> for each daemon * `tgen` : topogen object * `router`: Device under test @@ -2278,8 +2489,8 @@ def start_router(tgen, router): try: router_list = tgen.routers() - # Router and its deamons would be started and config would - # be loaded to router for each deamon from /etc/frr + # Router and its daemons would be started and config would + # be loaded to router for each daemon from /etc/frr router_list[router].start() except Exception as e: @@ -2300,7 +2511,7 @@ def addKernelRoute( ----------- * `tgen` : Topogen object * `router`: router for which kernal routes needs to be added - * `intf`: interface name, for which kernal routes needs to be added + * `intf`: interface name, for which kernel routes needs to be added * `bindToAddress`: bind to <host>, an interface or multicast address @@ -2363,7 +2574,7 @@ def configure_vxlan(tgen, input_dict): """ Add and configure vxlan - * `tgen`: tgen onject + * `tgen`: tgen object * `input_dict` : data for vxlan config Usage: @@ -2464,7 +2675,7 @@ def configure_brctl(tgen, topo, input_dict): """ Add and configure brctl - * `tgen`: tgen onject + * `tgen`: tgen object * `input_dict` : data for brctl config Usage: @@ -2558,7 +2769,7 @@ def configure_interface_mac(tgen, input_dict): """ Add and configure brctl - * `tgen`: tgen onject + * `tgen`: tgen object * `input_dict` : data for mac config input_mac= { @@ -2854,11 +3065,7 @@ def verify_rib( errormsg = ( "[DUT: {}]: tag value {}" " is not matched for" - " route {} in RIB \n".format( - dut, - _tag, - st_rt, - ) + " route {} in RIB \n".format(dut, _tag, st_rt,) ) return errormsg @@ -2875,11 +3082,7 @@ def verify_rib( errormsg = ( "[DUT: {}]: metric value " "{} is not matched for " - "route {} in RIB \n".format( - dut, - metric, - st_rt, - ) + "route {} in RIB \n".format(dut, metric, st_rt,) ) return errormsg @@ -3918,3 +4121,215 @@ def required_linux_kernel_version(required_version): ) return error_msg return True + + +def iperfSendIGMPJoin( + tgen, server, bindToAddress, l4Type="UDP", join_interval=1, inc_step=0, repeat=0 +): + """ + Run iperf to send IGMP join and traffic + + Parameters: + ----------- + * `tgen` : Topogen object + * `l4Type`: string, one of [ TCP, UDP ] + * `server`: iperf server, from where IGMP join would be sent + * `bindToAddress`: bind to <host>, an interface or multicast + address + * `join_interval`: seconds between periodic bandwidth reports + * `inc_step`: increamental steps, by default 0 + * `repeat`: Repetition of group, by default 0 + + returns: + -------- + errormsg or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + rnode = tgen.routers()[server] + + iperfArgs = "iperf -s " + + # UDP/TCP + if l4Type == "UDP": + iperfArgs += "-u " + + iperfCmd = iperfArgs + # Group address range to cover + if bindToAddress: + if type(bindToAddress) is not list: + Address = [] + start = ipaddress.IPv4Address(frr_unicode(bindToAddress)) + + Address = [start] + next_ip = start + + count = 1 + while count < repeat: + next_ip += inc_step + Address.append(next_ip) + count += 1 + bindToAddress = Address + + for bindTo in bindToAddress: + iperfArgs = iperfCmd + iperfArgs += "-B %s " % bindTo + + # Join interval + if join_interval: + iperfArgs += "-i %d " % join_interval + + iperfArgs += " &>/dev/null &" + # Run iperf command to send IGMP join + logger.debug("[DUT: {}]: Running command: [{}]".format(server, iperfArgs)) + output = rnode.run("set +m; {} sleep 0.5".format(iperfArgs)) + + # Check if iperf process is running + if output: + pid = output.split()[1] + rnode.run("touch /var/run/frr/iperf_server.pid") + rnode.run("echo %s >> /var/run/frr/iperf_server.pid" % pid) + else: + errormsg = "IGMP join is not sent for {}. Error: {}".format(bindTo, output) + logger.error(output) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def iperfSendTraffic( + tgen, + client, + bindToAddress, + ttl, + time=0, + l4Type="UDP", + inc_step=0, + repeat=0, + mappedAddress=None, +): + """ + Run iperf to send IGMP join and traffic + + Parameters: + ----------- + * `tgen` : Topogen object + * `l4Type`: string, one of [ TCP, UDP ] + * `client`: iperf client, from where iperf traffic would be sent + * `bindToAddress`: bind to <host>, an interface or multicast + address + * `ttl`: time to live + * `time`: time in seconds to transmit for + * `inc_step`: increamental steps, by default 0 + * `repeat`: Repetition of group, by default 0 + * `mappedAddress`: Mapped Interface ip address + + returns: + -------- + errormsg or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + rnode = tgen.routers()[client] + + iperfArgs = "iperf -c " + + iperfCmd = iperfArgs + # Group address range to cover + if bindToAddress: + if type(bindToAddress) is not list: + Address = [] + start = ipaddress.IPv4Address(frr_unicode(bindToAddress)) + + Address = [start] + next_ip = start + + count = 1 + while count < repeat: + next_ip += inc_step + Address.append(next_ip) + count += 1 + bindToAddress = Address + + for bindTo in bindToAddress: + iperfArgs = iperfCmd + iperfArgs += "%s " % bindTo + + # Mapped Interface IP + if mappedAddress: + iperfArgs += "-B %s " % mappedAddress + + # UDP/TCP + if l4Type == "UDP": + iperfArgs += "-u -b 0.012m " + + # TTL + if ttl: + iperfArgs += "-T %d " % ttl + + # Time + if time: + iperfArgs += "-t %d " % time + + iperfArgs += " &>/dev/null &" + + # Run iperf command to send multicast traffic + logger.debug("[DUT: {}]: Running command: [{}]".format(client, iperfArgs)) + output = rnode.run("set +m; {} sleep 0.5".format(iperfArgs)) + + # Check if iperf process is running + if output: + pid = output.split()[1] + rnode.run("touch /var/run/frr/iperf_client.pid") + rnode.run("echo %s >> /var/run/frr/iperf_client.pid" % pid) + else: + errormsg = "Multicast traffic is not sent for {}. Error {}".format( + bindTo, output + ) + logger.error(output) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def kill_iperf(tgen, dut=None, action=None): + """ + Killing iperf process if running for any router in topology + Parameters: + ----------- + * `tgen` : Topogen object + * `dut` : Any iperf hostname to send igmp prune + * `action`: to kill igmp join iperf action is remove_join + to kill traffic iperf action is remove_traffic + + Usage + ---- + kill_iperf(tgen, dut ="i6", action="remove_join") + + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + router_list = tgen.routers() + for router, rnode in router_list.items(): + # Run iperf command to send IGMP join + pid_client = rnode.run("cat /var/run/frr/iperf_client.pid") + pid_server = rnode.run("cat /var/run/frr/iperf_server.pid") + if action == "remove_join": + pids = pid_server + elif action == "remove_traffic": + pids = pid_client + else: + pids = "\n".join([pid_client, pid_server]) + for pid in pids.split("\n"): + pid = pid.strip() + if pid.isdigit(): + cmd = "set +m; kill -9 %s &> /dev/null" % pid + logger.debug("[DUT: {}]: Running command: [{}]".format(router, cmd)) + rnode.run(cmd) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) diff --git a/tests/topotests/lib/lutil.py b/tests/topotests/lib/lutil.py index 9cbea67af1..0b6a946fda 100644 --- a/tests/topotests/lib/lutil.py +++ b/tests/topotests/lib/lutil.py @@ -23,6 +23,7 @@ import time import datetime import json import math +import time from lib.topolog import logger from mininet.net import Mininet @@ -194,8 +195,9 @@ Total %-4d %-4d %d\n\ if op != "wait": self.l_line += 1 self.log( - "(#%d) %s:%s COMMAND:%s:%s:%s:%s:%s:" + "%s (#%d) %s:%s COMMAND:%s:%s:%s:%s:%s:" % ( + time.asctime(), self.l_total + 1, self.l_filename, self.l_line, diff --git a/tests/topotests/lib/ospf.py b/tests/topotests/lib/ospf.py index 5c7514a641..23b647d094 100644 --- a/tests/topotests/lib/ospf.py +++ b/tests/topotests/lib/ospf.py @@ -172,9 +172,6 @@ def __create_ospf_global(tgen, input_dict, router, build=False, load_config=True if del_action: cmd = "no {}".format(cmd) config_data.append(cmd) - result = create_common_configuration( - tgen, router, config_data, "ospf", build, load_config - ) # summary information summary_data = ospf_data.setdefault("summary-address", {}) diff --git a/tests/topotests/lib/pim.py b/tests/topotests/lib/pim.py new file mode 100644 index 0000000000..6bb1326519 --- /dev/null +++ b/tests/topotests/lib/pim.py @@ -0,0 +1,3389 @@ +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND VMWARE DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. + +import sys +import os +import re +import datetime +import traceback +import pytest +from time import sleep +from copy import deepcopy +from lib.topolog import logger + +# Import common_config to use commomnly used APIs +from lib.common_config import ( + create_common_configuration, + InvalidCLIError, + retry, + run_frr_cmd, +) + +#### +CWD = os.path.dirname(os.path.realpath(__file__)) + + +def create_pim_config(tgen, topo, input_dict=None, build=False, load_config=True): + """ + API to configure pim on router + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `input_dict` : Input dict data, required when configuring from + testcase + * `build` : Only for initial setup phase this is set as True. + + Usage + ----- + input_dict = { + "r1": { + "pim": { + "disable" : ["l1-i1-eth1"], + "rp": [{ + "rp_addr" : "1.0.3.17". + "keep-alive-timer": "100" + "group_addr_range": ["224.1.1.0/24", "225.1.1.0/24"] + "prefix-list": "pf_list_1" + "delete": True + }] + } + } + } + + + Returns + ------- + True or False + """ + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + result = False + if not input_dict: + input_dict = deepcopy(topo) + else: + topo = topo["routers"] + input_dict = deepcopy(input_dict) + for router in input_dict.keys(): + result = _enable_disable_pim(tgen, topo, input_dict, router, build) + + if "pim" not in input_dict[router]: + logger.debug("Router %s: 'pim' is not present in " "input_dict", router) + continue + + if result is True: + if "rp" not in input_dict[router]["pim"]: + continue + + result = _create_pim_config( + tgen, topo, input_dict, router, build, load_config + ) + if result is not True: + return False + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +def _create_pim_config(tgen, topo, input_dict, router, build=False, load_config=False): + """ + Helper API to create pim configuration. + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `input_dict` : Input dict data, required when configuring from testcase + * `router` : router id to be configured. + * `build` : Only for initial setup phase this is set as True. + + Returns + ------- + True or False + """ + + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + try: + + pim_data = input_dict[router]["pim"] + + for dut in tgen.routers(): + if "pim" not in input_dict[router]: + continue + + for destLink, data in topo[dut]["links"].items(): + if "pim" not in data: + continue + + if "rp" in pim_data: + config_data = [] + rp_data = pim_data["rp"] + + for rp_dict in deepcopy(rp_data): + # ip address of RP + if "rp_addr" not in rp_dict and build: + logger.error( + "Router %s: 'ip address of RP' not " + "present in input_dict/JSON", + router, + ) + + return False + rp_addr = rp_dict.setdefault("rp_addr", None) + + # Keep alive Timer + keep_alive_timer = rp_dict.setdefault("keep_alive_timer", None) + + # Group Address range to cover + if "group_addr_range" not in rp_dict and build: + logger.error( + "Router %s:'Group Address range to cover'" + " not present in input_dict/JSON", + router, + ) + + return False + group_addr_range = rp_dict.setdefault("group_addr_range", None) + + # Group prefix-list filter + prefix_list = rp_dict.setdefault("prefix_list", None) + + # Delete rp config + del_action = rp_dict.setdefault("delete", False) + + if keep_alive_timer: + cmd = "ip pim rp keep-alive-timer {}".format(keep_alive_timer) + config_data.append(cmd) + + if del_action: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + if rp_addr: + if group_addr_range: + if type(group_addr_range) is not list: + group_addr_range = [group_addr_range] + + for grp_addr in group_addr_range: + cmd = "ip pim rp {} {}".format(rp_addr, grp_addr) + config_data.append(cmd) + + if del_action: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + if prefix_list: + cmd = "ip pim rp {} prefix-list {}".format( + rp_addr, prefix_list + ) + config_data.append(cmd) + + if del_action: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + result = create_common_configuration( + tgen, dut, config_data, "pim", build, load_config + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +def create_igmp_config(tgen, topo, input_dict=None, build=False): + """ + API to configure igmp on router + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `input_dict` : Input dict data, required when configuring from + testcase + * `build` : Only for initial setup phase this is set as True. + + Usage + ----- + input_dict = { + "r1": { + "igmp": { + "interfaces": { + "r1-r0-eth0" :{ + "igmp":{ + "version": "2", + "delete": True + "query": { + "query-interval" : 100, + "query-max-response-time": 200 + } + } + } + } + } + } + } + + Returns + ------- + True or False + """ + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + result = False + if not input_dict: + input_dict = deepcopy(topo) + else: + topo = topo["routers"] + input_dict = deepcopy(input_dict) + for router in input_dict.keys(): + if "igmp" not in input_dict[router]: + logger.debug("Router %s: 'igmp' is not present in " "input_dict", router) + continue + + igmp_data = input_dict[router]["igmp"] + + if "interfaces" in igmp_data: + config_data = [] + intf_data = igmp_data["interfaces"] + + for intf_name in intf_data.keys(): + cmd = "interface {}".format(intf_name) + config_data.append(cmd) + protocol = "igmp" + del_action = intf_data[intf_name]["igmp"].setdefault("delete", False) + cmd = "ip igmp" + if del_action: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + del_attr = intf_data[intf_name]["igmp"].setdefault("delete_attr", False) + for attribute, data in intf_data[intf_name]["igmp"].items(): + if attribute == "version": + cmd = "ip {} {} {}".format(protocol, attribute, data) + if del_action: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + if attribute == "join": + for group in data: + cmd = "ip {} {} {}".format(protocol, attribute, group) + if del_attr: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + if attribute == "query": + for query, value in data.items(): + if query != "delete": + cmd = "ip {} {} {}".format(protocol, query, value) + + if "delete" in intf_data[intf_name][protocol]["query"]: + cmd = "no {}".format(cmd) + + config_data.append(cmd) + try: + + result = create_common_configuration( + tgen, router, config_data, "interface_config", build=build + ) + except InvalidCLIError: + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +def _enable_disable_pim(tgen, topo, input_dict, router, build=False): + """ + Helper API to enable or disable pim on interfaces + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `input_dict` : Input dict data, required when configuring from testcase + * `router` : router id to be configured. + * `build` : Only for initial setup phase this is set as True. + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + try: + config_data = [] + + enable_flag = True + # Disable pim on interface + if "pim" in input_dict[router]: + if "disable" in input_dict[router]["pim"]: + enable_flag = False + interfaces = input_dict[router]["pim"]["disable"] + + if type(interfaces) is not list: + interfaces = [interfaces] + + for interface in interfaces: + cmd = "interface {}".format(interface) + config_data.append(cmd) + config_data.append("no ip pim") + + # Enable pim on interface + if enable_flag: + for destRouterLink, data in sorted(topo[router]["links"].items()): + if "pim" in data and data["pim"] == "enable": + + # Loopback interfaces + if "type" in data and data["type"] == "loopback": + interface_name = destRouterLink + else: + interface_name = data["interface"] + + cmd = "interface {}".format(interface_name) + config_data.append(cmd) + config_data.append("ip pim") + + result = create_common_configuration( + tgen, router, config_data, "interface_config", build=build + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +def add_rp_interfaces_and_pim_config(tgen, topo, interface, rp, rp_mapping): + """ + Add physical interfaces tp RP for all the RPs + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `interface` : RP interface + * `rp` : rp for given topology + * `rp_mapping` : dictionary of all groups and RPs + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + try: + config_data = [] + + for group, rp_list in rp_mapping.items(): + for _rp in rp_list: + config_data.append("interface {}".format(interface)) + config_data.append("ip address {}".format(_rp)) + config_data.append("ip pim") + + result = create_common_configuration( + tgen, rp, config_data, "interface_config" + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +def find_rp_details(tgen, topo): + """ + Find who is RP in topology and returns list of RPs + + Parameters: + ----------- + * `tgen` : Topogen object + * `topo` : json file data + + returns: + -------- + errormsg or True + """ + + rp_details = {} + + router_list = tgen.routers() + topo_data = topo["routers"] + + for router in router_list.keys(): + + if "pim" not in topo_data[router]: + continue + + pim_data = topo_data[router]["pim"] + if "rp" in pim_data: + rp_data = pim_data["rp"] + for rp_dict in rp_data: + # ip address of RP + rp_addr = rp_dict["rp_addr"] + + for link, data in topo["routers"][router]["links"].items(): + if data["ipv4"].split("/")[0] == rp_addr: + rp_details[router] = rp_addr + + return rp_details + + +def configure_pim_force_expire(tgen, topo, input_dict, build=False): + """ + Helper API to create pim configuration. + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `input_dict` : Input dict data, required when configuring from testcase + * `build` : Only for initial setup phase this is set as True. + + Usage + ----- + input_dict ={ + "l1": { + "pim": { + "force_expire":{ + "10.0.10.1": ["255.1.1.1"] + } + } + } + } + + result = create_pim_config(tgen, topo, input_dict) + + Returns + ------- + True or False + """ + + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + try: + + for dut in input_dict.keys(): + if "pim" not in input_dict[dut]: + continue + + pim_data = input_dict[dut]["pim"] + + if "force_expire" in pim_data: + config_data = [] + force_expire_data = pim_data["force_expire"] + + for source, groups in force_expire_data.items(): + if type(groups) is not list: + groups = [groups] + + for group in groups: + cmd = "ip pim force-expire source {} group {}".format( + source, group + ) + config_data.append(cmd) + + result = create_common_configuration( + tgen, dut, config_data, "pim", build=build + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +############################################# +# Verification APIs +############################################# +def verify_pim_neighbors(tgen, topo, dut=None, iface=None): + """ + Verify all PIM neighbors are up and running, config is verified + using "show ip pim neighbor" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo` : json file data + * `dut` : dut info + * `iface` : link for which PIM nbr need to check + + Usage + ----- + result = verify_pim_neighbors(tgen, topo, dut, link) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for router in tgen.routers(): + if dut is not None and dut != router: + continue + + rnode = tgen.routers()[router] + show_ip_pim_neighbor_json = rnode.vtysh_cmd( + "show ip pim neighbor json", isjson=True + ) + + for destLink, data in topo["routers"][router]["links"].items(): + if iface is not None and iface != data["interface"]: + continue + + if "type" in data and data["type"] == "loopback": + continue + + if "pim" not in data: + continue + + if "pim" in data and data["pim"] == "enable": + local_interface = data["interface"] + + if "-" in destLink: + # Spliting and storing destRouterLink data in tempList + tempList = destLink.split("-") + + # destRouter + destLink = tempList.pop(0) + + # Current Router Link + tempList.insert(0, router) + curRouter = "-".join(tempList) + else: + curRouter = router + if destLink not in topo["routers"]: + continue + data = topo["routers"][destLink]["links"][curRouter] + if "type" in data and data["type"] == "loopback": + continue + + if "pim" not in data: + continue + + logger.info("[DUT: %s]: Verifying PIM neighbor status:", router) + + if "pim" in data and data["pim"] == "enable": + pim_nh_intf_ip = data["ipv4"].split("/")[0] + + # Verifying PIM neighbor + if local_interface in show_ip_pim_neighbor_json: + if show_ip_pim_neighbor_json[local_interface]: + if ( + show_ip_pim_neighbor_json[local_interface][pim_nh_intf_ip][ + "neighbor" + ] + != pim_nh_intf_ip + ): + errormsg = ( + "[DUT %s]: Local interface: %s, PIM" + " neighbor check failed " + "Expected neighbor: %s, Found neighbor:" + " %s" + % ( + router, + local_interface, + pim_nh_intf_ip, + show_ip_pim_neighbor_json[local_interface][ + pim_nh_intf_ip + ]["neighbor"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: Local interface: %s, Found" + " expected PIM neighbor %s", + router, + local_interface, + pim_nh_intf_ip, + ) + else: + errormsg = ( + "[DUT %s]: Local interface: %s, and" + "interface ip: %s is not found in " + "PIM neighbor " % (router, local_interface, pim_nh_intf_ip) + ) + return errormsg + else: + errormsg = ( + "[DUT %s]: Local interface: %s, is not " + "present in PIM neighbor " % (router, local_interface) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=21, wait=2, return_is_str=True) +def verify_igmp_groups(tgen, dut, interface, group_addresses): + """ + Verify IGMP groups are received from an intended interface + by running "show ip igmp groups" command + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `interface`: interface, from which IGMP groups would be received + * `group_addresses`: IGMP group address + + Usage + ----- + dut = "r1" + interface = "r1-r0-eth0" + group_address = "225.1.1.1" + result = verify_igmp_groups(tgen, dut, interface, group_address) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying IGMP groups received:", dut) + show_ip_igmp_json = run_frr_cmd(rnode, "show ip igmp groups json", isjson=True) + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + if interface in show_ip_igmp_json: + show_ip_igmp_json = show_ip_igmp_json[interface]["groups"] + else: + errormsg = ( + "[DUT %s]: Verifying IGMP group received" + " from interface %s [FAILED]!! " % (dut, interface) + ) + return errormsg + + found = False + for grp_addr in group_addresses: + for index in show_ip_igmp_json: + if index["group"] == grp_addr: + found = True + break + if found is not True: + errormsg = ( + "[DUT %s]: Verifying IGMP group received" + " from interface %s [FAILED]!! " + " Expected not found: %s" % (dut, interface, grp_addr) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying IGMP group %s received " + "from interface %s [PASSED]!! ", + dut, + grp_addr, + interface, + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_upstream_iif( + tgen, dut, iif, src_address, group_addresses, joinState=None, refCount=1 +): + """ + Verify upstream inbound interface is updated correctly + by running "show ip pim upstream" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `iif`: inbound interface + * `src_address`: source address + * `group_addresses`: IGMP group address + * `joinState`: upstream join state + * `refCount`: refCount value + + Usage + ----- + dut = "r1" + iif = "r1-r0-eth0" + src_address = "*" + group_address = "225.1.1.1" + result = verify_upstream_iif(tgen, dut, iif, src_address, group_address, + state, refCount) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info( + "[DUT: %s]: Verifying upstream Inbound Interface" " for IGMP groups received:", + dut, + ) + show_ip_pim_upstream_json = run_frr_cmd( + rnode, "show ip pim upstream json", isjson=True + ) + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + if type(iif) is not list: + iif = [iif] + + for grp_addr in group_addresses: + # Verify group address + if grp_addr not in show_ip_pim_upstream_json: + errormsg = "[DUT %s]: Verifying upstream" " for group %s [FAILED]!!" % ( + dut, + grp_addr, + ) + return errormsg + group_addr_json = show_ip_pim_upstream_json[grp_addr] + + # Verify source address + if src_address not in group_addr_json: + errormsg = "[DUT %s]: Verifying upstream" " for (%s,%s) [FAILED]!!" % ( + dut, + src_address, + grp_addr, + ) + return errormsg + + # Verify Inbound Interface + found = False + for in_interface in iif: + if group_addr_json[src_address]["inboundInterface"] == in_interface: + if refCount > 0: + logger.info( + "[DUT %s]: Verifying refCount " + "for (%s,%s) [PASSED]!! " + " Found Expected: %s", + dut, + src_address, + grp_addr, + group_addr_json[src_address]["refCount"], + ) + found = True + if found: + if joinState is None: + if group_addr_json[src_address]["joinState"] != "Joined": + errormsg = ( + "[DUT %s]: Verifying iif " + "(Inbound Interface) for (%s,%s) and" + " joinState :%s [FAILED]!! " + " Expected: %s, Found: %s" + % ( + dut, + src_address, + grp_addr, + group_addr_json[src_address]["joinState"], + in_interface, + group_addr_json[src_address]["inboundInterface"], + ) + ) + return errormsg + + elif group_addr_json[src_address]["joinState"] != joinState: + errormsg = ( + "[DUT %s]: Verifying iif " + "(Inbound Interface) for (%s,%s) and" + " joinState :%s [FAILED]!! " + " Expected: %s, Found: %s" + % ( + dut, + src_address, + grp_addr, + group_addr_json[src_address]["joinState"], + in_interface, + group_addr_json[src_address]["inboundInterface"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying iif(Inbound Interface)" + " for (%s,%s) and joinState is %s [PASSED]!! " + " Found Expected: (%s)", + dut, + src_address, + grp_addr, + group_addr_json[src_address]["joinState"], + group_addr_json[src_address]["inboundInterface"], + ) + if not found: + errormsg = ( + "[DUT %s]: Verifying iif " + "(Inbound Interface) for (%s, %s) " + "[FAILED]!! " + " Expected: %s, Found: %s" + % ( + dut, + src_address, + grp_addr, + in_interface, + group_addr_json[src_address]["inboundInterface"], + ) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=6, wait=2, return_is_str=True) +def verify_join_state_and_timer(tgen, dut, iif, src_address, group_addresses): + """ + Verify join state is updated correctly and join timer is + running with the help of "show ip pim upstream" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `iif`: inbound interface + * `src_address`: source address + * `group_addresses`: IGMP group address + + Usage + ----- + dut = "r1" + iif = "r1-r0-eth0" + group_address = "225.1.1.1" + result = verify_join_state_and_timer(tgen, dut, iif, group_address) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + errormsg = "" + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info( + "[DUT: %s]: Verifying Join state and Join Timer" " for IGMP groups received:", + dut, + ) + show_ip_pim_upstream_json = run_frr_cmd( + rnode, "show ip pim upstream json", isjson=True + ) + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + for grp_addr in group_addresses: + # Verify group address + if grp_addr not in show_ip_pim_upstream_json: + errormsg = "[DUT %s]: Verifying upstream" " for group %s [FAILED]!!" % ( + dut, + grp_addr, + ) + return errormsg + + group_addr_json = show_ip_pim_upstream_json[grp_addr] + + # Verify source address + if src_address not in group_addr_json: + errormsg = "[DUT %s]: Verifying upstream" " for (%s,%s) [FAILED]!!" % ( + dut, + src_address, + grp_addr, + ) + return errormsg + + # Verify join state + joinState = group_addr_json[src_address]["joinState"] + if joinState != "Joined": + error = ( + "[DUT %s]: Verifying join state for" + " (%s,%s) [FAILED]!! " + " Expected: %s, Found: %s" + % (dut, src_address, grp_addr, "Joined", joinState) + ) + errormsg = errormsg + "\n" + str(error) + else: + logger.info( + "[DUT %s]: Verifying join state for" + " (%s,%s) [PASSED]!! " + " Found Expected: %s", + dut, + src_address, + grp_addr, + joinState, + ) + + # Verify join timer + joinTimer = group_addr_json[src_address]["joinTimer"] + if not re.match(r"(\d{2}):(\d{2}):(\d{2})", joinTimer): + error = ( + "[DUT %s]: Verifying join timer for" + " (%s,%s) [FAILED]!! " + " Expected: %s, Found: %s", + dut, + src_address, + grp_addr, + "join timer should be running", + joinTimer, + ) + errormsg = errormsg + "\n" + str(error) + else: + logger.info( + "[DUT %s]: Verifying join timer is running" + " for (%s,%s) [PASSED]!! " + " Found Expected: %s", + dut, + src_address, + grp_addr, + joinTimer, + ) + + if errormsg != "": + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=41, wait=2, return_is_dict=True) +def verify_ip_mroutes( + tgen, dut, src_address, group_addresses, iif, oil, return_uptime=False, mwait=0 +): + """ + Verify ip mroutes and make sure (*, G)/(S, G) is present in mroutes + by running "show ip pim upstream" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `src_address`: source address + * `group_addresses`: IGMP group address + * `iif`: Incoming interface + * `oil`: Outgoing interface + * `return_uptime`: If True, return uptime dict, default is False + * `mwait`: Wait time, default is 0 + + + Usage + ----- + dut = "r1" + group_address = "225.1.1.1" + result = verify_ip_mroutes(tgen, dut, src_address, group_address) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + if return_uptime: + logger.info("Sleeping for %s sec..", mwait) + sleep(mwait) + + logger.info("[DUT: %s]: Verifying ip mroutes", dut) + show_ip_mroute_json = run_frr_cmd(rnode, "show ip mroute json", isjson=True) + + if return_uptime: + uptime_dict = {} + + if bool(show_ip_mroute_json) == False: + error_msg = "[DUT %s]: mroutes are not present or flushed out !!" % (dut) + return error_msg + + if not isinstance(group_addresses, list): + group_addresses = [group_addresses] + + if not isinstance(iif, list) and iif is not "none": + iif = [iif] + + if not isinstance(oil, list) and oil is not "none": + oil = [oil] + + for grp_addr in group_addresses: + if grp_addr not in show_ip_mroute_json: + errormsg = "[DUT %s]: Verifying (%s, %s) mroute," "[FAILED]!! " % ( + dut, + src_address, + grp_addr, + ) + return errormsg + else: + if return_uptime: + uptime_dict[grp_addr] = {} + + group_addr_json = show_ip_mroute_json[grp_addr] + + if src_address not in group_addr_json: + errormsg = "[DUT %s]: Verifying (%s, %s) mroute," "[FAILED]!! " % ( + dut, + src_address, + grp_addr, + ) + return errormsg + else: + if return_uptime: + uptime_dict[grp_addr][src_address] = {} + + mroutes = group_addr_json[src_address] + + if mroutes["installed"] != 0: + logger.info( + "[DUT %s]: mroute (%s,%s) is installed", dut, src_address, grp_addr + ) + + if "oil" not in mroutes: + if oil == "none" and mroutes["iif"] in iif: + logger.info( + "[DUT %s]: Verifying (%s, %s) mroute," + " [PASSED]!! Found Expected: " + "(iif: %s, oil: %s, installed: (%s,%s))", + dut, + src_address, + grp_addr, + mroutes["iif"], + oil, + src_address, + grp_addr, + ) + else: + errormsg = ( + "[DUT %s]: Verifying (%s, %s) mroute," + " [FAILED]!! " + "Expected: (oil: %s, installed:" + " (%s,%s)) Found: ( oil: none, " + "installed: (%s,%s))" + % ( + dut, + src_address, + grp_addr, + oil, + src_address, + grp_addr, + src_address, + grp_addr, + ) + ) + + return errormsg + + else: + found = False + for route, data in mroutes["oil"].items(): + if route in oil and route != "pimreg": + if ( + data["source"] == src_address + and data["group"] == grp_addr + and data["inboundInterface"] in iif + and data["outboundInterface"] in oil + ): + if return_uptime: + + uptime_dict[grp_addr][src_address] = data["upTime"] + + logger.info( + "[DUT %s]: Verifying (%s, %s)" + " mroute, [PASSED]!! " + "Found Expected: " + "(iif: %s, oil: %s, installed:" + " (%s,%s)", + dut, + src_address, + grp_addr, + data["inboundInterface"], + data["outboundInterface"], + data["source"], + data["group"], + ) + found = True + break + else: + continue + + if not found: + errormsg = ( + "[DUT %s]: Verifying (%s, %s)" + " mroute [FAILED]!! " + "Expected in: (iif: %s, oil: %s," + " installed: (%s,%s)) Found: " + "(iif: %s, oil: %s, " + "installed: (%s,%s))" + % ( + dut, + src_address, + grp_addr, + iif, + oil, + src_address, + grp_addr, + data["inboundInterface"], + data["outboundInterface"], + data["source"], + data["group"], + ) + ) + return errormsg + + else: + errormsg = "[DUT %s]: mroute (%s,%s) is not installed" % ( + dut, + src_address, + grp_addr, + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True if return_uptime == False else uptime_dict + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_pim_rp_info( + tgen, topo, dut, group_addresses, oif=None, rp=None, source=None, iamrp=None +): + """ + Verify pim rp info by running "show ip pim rp-info" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo`: JSON file handler + * `dut`: device under test + * `group_addresses`: IGMP group address + * `oif`: outbound interface name + * `rp`: RP address + * `source`: Source of RP + * `iamrp`: User defined RP + + Usage + ----- + dut = "r1" + result = verify_pim_rp_info(tgen, topo, dut, group_address, + rp=rp, source="BSR") + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying ip rp info", dut) + show_ip_rp_info_json = run_frr_cmd(rnode, "show ip pim rp-info json", isjson=True) + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + if type(oif) is not list: + oif = [oif] + + for grp_addr in group_addresses: + if rp is None: + rp_details = find_rp_details(tgen, topo) + + if dut in rp_details: + iamRP = True + else: + iamRP = False + else: + show_ip_route_json = run_frr_cmd( + rnode, "show ip route connected json", isjson=True + ) + for _rp in show_ip_route_json.keys(): + if rp == _rp.split("/")[0]: + iamRP = True + break + else: + iamRP = False + + if rp not in show_ip_rp_info_json: + errormsg = "[DUT %s]: Verifying rp-info" "for rp_address %s [FAILED]!! " % ( + dut, + rp, + ) + return errormsg + else: + group_addr_json = show_ip_rp_info_json[rp] + + for rp_json in group_addr_json: + if oif is not None: + found = False + if rp_json["outboundInterface"] not in oif: + errormsg = ( + "[DUT %s]: Verifying OIF " + "for group %s and RP %s [FAILED]!! " + "Expected interfaces: (%s)," + " Found: (%s)" + % (dut, grp_addr, rp, oif, rp_json["outboundInterface"]) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying OIF " + "for group %s and RP %s [PASSED]!! " + "Found Expected: (%s)" + % (dut, grp_addr, rp, rp_json["outboundInterface"]) + ) + + if source is not None: + if rp_json["source"] != source: + errormsg = ( + "[DUT %s]: Verifying SOURCE " + "for group %s and RP %s [FAILED]!! " + "Expected: (%s)," + " Found: (%s)" % (dut, grp_addr, rp, source, rp_json["source"]) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying SOURCE " + "for group %s and RP %s [PASSED]!! " + "Found Expected: (%s)" % (dut, grp_addr, rp, rp_json["source"]) + ) + + if rp_json["group"] == grp_addr and iamrp is not None: + if iamRP: + if rp_json["iAmRP"]: + logger.info( + "[DUT %s]: Verifying group " + "and iAmRP [PASSED]!!" + " Found Expected: (%s, %s:%s)", + dut, + grp_addr, + "iAmRP", + rp_json["iAmRP"], + ) + else: + errormsg = ( + "[DUT %s]: Verifying group" + "%s and iAmRP [FAILED]!! " + "Expected: (iAmRP: %s)," + " Found: (iAmRP: %s)" + % (dut, grp_addr, "true", rp_json["iAmRP"]) + ) + return errormsg + + if not iamRP: + if rp_json["iAmRP"] == False: + logger.info( + "[DUT %s]: Verifying group " + "and iAmNotRP [PASSED]!!" + " Found Expected: (%s, %s:%s)", + dut, + grp_addr, + "iAmRP", + rp_json["iAmRP"], + ) + else: + errormsg = ( + "[DUT %s]: Verifying group" + "%s and iAmRP [FAILED]!! " + "Expected: (iAmRP: %s)," + " Found: (iAmRP: %s)" + % (dut, grp_addr, "false", rp_json["iAmRP"]) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_pim_state( + tgen, dut, iif, oil, group_addresses, src_address=None, installed_fl=None +): + """ + Verify pim state by running "show ip pim state" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `iif`: inbound interface + * `oil`: outbound interface + * `group_addresses`: IGMP group address + * `src_address`: source address, default = None + * installed_fl` : Installed flag + + Usage + ----- + dut = "r1" + iif = "r1-r3-eth1" + oil = "r1-r0-eth0" + group_address = "225.1.1.1" + result = verify_pim_state(tgen, dut, iif, oil, group_address) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying pim state", dut) + show_pim_state_json = run_frr_cmd(rnode, "show ip pim state json", isjson=True) + + if installed_fl is None: + installed_fl = 1 + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + for grp_addr in group_addresses: + if src_address is None: + src_address = "*" + pim_state_json = show_pim_state_json[grp_addr][src_address] + else: + pim_state_json = show_pim_state_json[grp_addr][src_address] + + if pim_state_json["Installed"] == installed_fl: + logger.info( + "[DUT %s]: group %s is installed flag: %s", + dut, + grp_addr, + pim_state_json["Installed"], + ) + for interface, data in pim_state_json[iif].items(): + if interface != oil: + continue + + # Verify iif, oil and installed state + if ( + data["group"] == grp_addr + and data["installed"] == installed_fl + and data["inboundInterface"] == iif + and data["outboundInterface"] == oil + ): + logger.info( + "[DUT %s]: Verifying pim state for group" + " %s [PASSED]!! Found Expected: " + "(iif: %s, oil: %s, installed: %s) ", + dut, + grp_addr, + data["inboundInterface"], + data["outboundInterface"], + data["installed"], + ) + else: + errormsg = ( + "[DUT %s]: Verifying pim state for group" + " %s, [FAILED]!! Expected: " + "(iif: %s, oil: %s, installed: %s) ", + "Found: (iif: %s, oil: %s, installed: %s)" + % ( + dut, + grp_addr, + iif, + oil, + "1", + data["inboundInterface"], + data["outboundInterface"], + data["installed"], + ), + ) + return errormsg + else: + errormsg = "[DUT %s]: %s install flag value not as expected" % ( + dut, + grp_addr, + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def verify_pim_interface_traffic(tgen, input_dict): + """ + Verify ip pim interface traffice by running + "show ip pim interface traffic" cli + + Parameters + ---------- + * `tgen`: topogen object + * `input_dict(dict)`: defines DUT, what and from which interfaces + traffic needs to be verified + Usage + ----- + input_dict = { + "r1": { + "r1-r0-eth0": { + "helloRx": 0, + "helloTx": 1, + "joinRx": 0, + "joinTx": 0 + } + } + } + + result = verify_pim_interface_traffic(tgen, input_dict) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + output_dict = {} + for dut in input_dict.keys(): + if dut not in tgen.routers(): + continue + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying pim interface traffic", dut) + show_pim_intf_traffic_json = run_frr_cmd( + rnode, "show ip pim interface traffic json", isjson=True + ) + + output_dict[dut] = {} + for intf, data in input_dict[dut].items(): + interface_json = show_pim_intf_traffic_json[intf] + for state in data: + + # Verify Tx/Rx + if state in interface_json: + output_dict[dut][state] = interface_json[state] + else: + errormsg = ( + "[DUT %s]: %s is not present" + "for interface %s [FAILED]!! " % (dut, state, intf) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return output_dict + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_pim_interface(tgen, topo, dut): + """ + Verify all PIM interface are up and running, config is verified + using "show ip pim interface" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo` : json file data + * `dut` : device under test + + Usage + ----- + result = verify_pim_interfacetgen, topo, dut) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for router in tgen.routers(): + if router != dut: + continue + + logger.info("[DUT: %s]: Verifying PIM interface status:", dut) + + rnode = tgen.routers()[dut] + show_ip_pim_interface_json = run_frr_cmd( + rnode, "show ip pim interface json", isjson=True + ) + + for destLink, data in topo["routers"][dut]["links"].items(): + if "type" in data and data["type"] == "loopback": + continue + + if "pim" in data and data["pim"] == "enable": + pim_interface = data["interface"] + pim_intf_ip = data["ipv4"].split("/")[0] + + if pim_interface in show_ip_pim_interface_json: + pim_intf_json = show_ip_pim_interface_json[pim_interface] + + # Verifying PIM interface + if ( + pim_intf_json["address"] != pim_intf_ip + and pim_intf_json["state"] != "up" + ): + errormsg = ( + "[DUT %s]: PIM interface: %s " + "PIM interface ip: %s, status check " + "[FAILED]!! Expected : %s, Found : %s" + % ( + dut, + pim_interface, + pim_intf_ip, + pim_interface, + pim_intf_json["state"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: PIM interface: %s, " + "interface ip: %s, status: %s" + " [PASSED]!!", + dut, + pim_interface, + pim_intf_ip, + pim_intf_json["state"], + ) + else: + errormsg = ( + "[DUT %s]: PIM interface: %s " + "PIM interface ip: %s, is not present " + % (dut, pim_interface, pim_intf_ip,) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def clear_ip_pim_interface_traffic(tgen, topo): + """ + Clear ip pim interface traffice by running + "clear ip pim interface traffic" cli + + Parameters + ---------- + * `tgen`: topogen object + Usage + ----- + + result = clear_ip_pim_interface_traffic(tgen, topo) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for dut in tgen.routers(): + if "pim" not in topo["routers"][dut]: + continue + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Clearing pim interface traffic", dut) + result = run_frr_cmd(rnode, "clear ip pim interface traffic") + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return True + + +def clear_ip_pim_interfaces(tgen, dut): + """ + Clear ip pim interface by running + "clear ip pim interfaces" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: Device Under Test + Usage + ----- + + result = clear_ip_pim_interfaces(tgen, dut) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + nh_before_clear = {} + nh_after_clear = {} + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verify pim neighbor before pim" " neighbor clear", dut) + # To add uptime initially + sleep(10) + run_json_before = run_frr_cmd(rnode, "show ip pim neighbor json", isjson=True) + + for key, value in run_json_before.items(): + if bool(value): + for _key, _value in value.items(): + nh_before_clear[key] = _value["upTime"] + + # Clearing PIM neighbors + logger.info("[DUT: %s]: Clearing pim interfaces", dut) + run_frr_cmd(rnode, "clear ip pim interfaces") + + logger.info("[DUT: %s]: Verify pim neighbor after pim" " neighbor clear", dut) + + found = False + + # Waiting for maximum 60 sec + fail_intf = [] + for retry in range(1, 13): + logger.info("[DUT: %s]: Waiting for 5 sec for PIM neighbors" " to come up", dut) + sleep(5) + run_json_after = run_frr_cmd(rnode, "show ip pim neighbor json", isjson=True) + found = True + for pim_intf in nh_before_clear.keys(): + if pim_intf not in run_json_after or not run_json_after[pim_intf]: + found = False + fail_intf.append(pim_intf) + + if found is True: + break + else: + errormsg = ( + "[DUT: %s]: pim neighborship is not formed for %s" + "after clear_ip_pim_interfaces %s [FAILED!!]", + dut, + fail_intf, + ) + return errormsg + + for key, value in run_json_after.items(): + if bool(value): + for _key, _value in value.items(): + nh_after_clear[key] = _value["upTime"] + + # Verify uptime for neighbors + for pim_intf in nh_before_clear.keys(): + d1 = datetime.datetime.strptime(nh_before_clear[pim_intf], "%H:%M:%S") + d2 = datetime.datetime.strptime(nh_after_clear[pim_intf], "%H:%M:%S") + if d2 >= d1: + errormsg = ( + "[DUT: %s]: PIM neighborship is not cleared for", + " interface %s [FAILED!!]", + dut, + pim_intf, + ) + + logger.info("[DUT: %s]: PIM neighborship is cleared [PASSED!!]") + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return True + + +def clear_ip_igmp_interfaces(tgen, dut): + """ + Clear ip igmp interfaces by running + "clear ip igmp interfaces" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + + Usage + ----- + dut = "r1" + result = clear_ip_igmp_interfaces(tgen, dut) + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + group_before_clear = {} + group_after_clear = {} + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: IGMP group uptime before clear" " igmp groups:", dut) + igmp_json = run_frr_cmd(rnode, "show ip igmp groups json", isjson=True) + + total_groups_before_clear = igmp_json["totalGroups"] + + for key, value in igmp_json.items(): + if type(value) is not dict: + continue + + groups = value["groups"] + group = groups[0]["group"] + uptime = groups[0]["uptime"] + group_before_clear[group] = uptime + + logger.info("[DUT: %s]: Clearing ip igmp interfaces", dut) + result = run_frr_cmd(rnode, "clear ip igmp interfaces") + + # Waiting for maximum 60 sec + for retry in range(1, 13): + logger.info( + "[DUT: %s]: Waiting for 5 sec for igmp interfaces" " to come up", dut + ) + sleep(5) + igmp_json = run_frr_cmd(rnode, "show ip igmp groups json", isjson=True) + + total_groups_after_clear = igmp_json["totalGroups"] + + if total_groups_before_clear == total_groups_after_clear: + break + + for key, value in igmp_json.items(): + if type(value) is not dict: + continue + + groups = value["groups"] + group = groups[0]["group"] + uptime = groups[0]["uptime"] + group_after_clear[group] = uptime + + # Verify uptime for groups + for group in group_before_clear.keys(): + d1 = datetime.datetime.strptime(group_before_clear[group], "%H:%M:%S") + d2 = datetime.datetime.strptime(group_after_clear[group], "%H:%M:%S") + if d2 >= d1: + errormsg = ("[DUT: %s]: IGMP group is not cleared", " [FAILED!!]", dut) + + logger.info("[DUT: %s]: IGMP group is cleared [PASSED!!]") + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return True + + +@retry(attempts=10, wait=2, return_is_str=True) +def clear_ip_mroute_verify(tgen, dut): + """ + Clear ip mroute by running "clear ip mroute" cli and verify + mroutes are up again after mroute clear + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: Device Under Test + Usage + ----- + + result = clear_ip_mroute_verify(tgen, dut) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + mroute_before_clear = {} + mroute_after_clear = {} + + rnode = tgen.routers()[dut] + + # sleep(60) + logger.info("[DUT: %s]: IP mroutes uptime before clear", dut) + mroute_json_1 = run_frr_cmd(rnode, "show ip mroute json", isjson=True) + + for group in mroute_json_1.keys(): + mroute_before_clear[group] = {} + for key in mroute_json_1[group].keys(): + for _key, _value in mroute_json_1[group][key]["oil"].items(): + if _key != "pimreg": + mroute_before_clear[group][key] = _value["upTime"] + + logger.info("[DUT: %s]: Clearing ip mroute", dut) + result = run_frr_cmd(rnode, "clear ip mroute") + + # RFC 3376: 8.2. Query Interval - Default: 125 seconds + # So waiting for maximum 130 sec to get the igmp report + for retry in range(1, 26): + logger.info("[DUT: %s]: Waiting for 2 sec for mroutes" " to come up", dut) + sleep(5) + keys_json1 = mroute_json_1.keys() + mroute_json_2 = run_frr_cmd(rnode, "show ip mroute json", isjson=True) + + if bool(mroute_json_2): + keys_json2 = mroute_json_2.keys() + + for group in mroute_json_2.keys(): + flag = False + for key in mroute_json_2[group].keys(): + if "oil" not in mroute_json_2[group]: + continue + + for _key, _value in mroute_json_2[group][key]["oil"].items(): + if _key != "pimreg" and keys_json1 == keys_json2: + break + flag = True + if flag: + break + else: + continue + + for group in mroute_json_2.keys(): + mroute_after_clear[group] = {} + for key in mroute_json_2[group].keys(): + for _key, _value in mroute_json_2[group][key]["oil"].items(): + if _key != "pimreg": + mroute_after_clear[group][key] = _value["upTime"] + + # Verify uptime for mroute + for group in mroute_before_clear.keys(): + for source in mroute_before_clear[group].keys(): + if set(mroute_before_clear[group]) != set(mroute_after_clear[group]): + errormsg = ( + "[DUT: %s]: mroute (%s, %s) has not come" + " up after mroute clear [FAILED!!]" % (dut, source, group) + ) + return errormsg + + d1 = datetime.datetime.strptime( + mroute_before_clear[group][source], "%H:%M:%S" + ) + d2 = datetime.datetime.strptime( + mroute_after_clear[group][source], "%H:%M:%S" + ) + if d2 >= d1: + errormsg = "[DUT: %s]: IP mroute is not cleared" " [FAILED!!]" % (dut) + + logger.info("[DUT: %s]: IP mroute is cleared [PASSED!!]", dut) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return True + + +def clear_ip_mroute(tgen, dut=None): + """ + Clear ip mroute by running "clear ip mroute" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test, default None + + Usage + ----- + clear_ip_mroute(tgen, dut) + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + router_list = tgen.routers() + for router, rnode in router_list.items(): + if dut is not None and router != dut: + continue + + logger.debug("[DUT: %s]: Clearing ip mroute", router) + rnode.vtysh_cmd("clear ip mroute") + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + +def reconfig_interfaces(tgen, topo, senderRouter, receiverRouter, packet=None): + """ + Configure interface ip for sender and receiver routers + as per bsr packet + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `senderRouter` : Sender router + * `receiverRouter` : Receiver router + * `packet` : BSR packet in raw format + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + try: + config_data = [] + + src_ip = topo["routers"][senderRouter]["bsm"]["bsr_packets"][packet]["src_ip"] + dest_ip = topo["routers"][senderRouter]["bsm"]["bsr_packets"][packet]["dest_ip"] + + for destLink, data in topo["routers"][senderRouter]["links"].items(): + if "type" in data and data["type"] == "loopback": + continue + + if "pim" in data and data["pim"] == "enable": + sender_interface = data["interface"] + sender_interface_ip = data["ipv4"] + + config_data.append("interface {}".format(sender_interface)) + config_data.append("no ip address {}".format(sender_interface_ip)) + config_data.append("ip address {}".format(src_ip)) + + result = create_common_configuration( + tgen, senderRouter, config_data, "interface_config" + ) + if result is not True: + return False + + config_data = [] + links = topo["routers"][destLink]["links"] + pim_neighbor = {key: links[key] for key in [senderRouter]} + + data = pim_neighbor[senderRouter] + if "type" in data and data["type"] == "loopback": + continue + + if "pim" in data and data["pim"] == "enable": + receiver_interface = data["interface"] + receiver_interface_ip = data["ipv4"] + + config_data.append("interface {}".format(receiver_interface)) + config_data.append("no ip address {}".format(receiver_interface_ip)) + config_data.append("ip address {}".format(dest_ip)) + + result = create_common_configuration( + tgen, receiverRouter, config_data, "interface_config" + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: reconfig_interfaces()") + return result + + +def add_rp_interfaces_and_pim_config(tgen, topo, interface, rp, rp_mapping): + """ + Add physical interfaces tp RP for all the RPs + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `interface` : RP interface + * `rp` : rp for given topology + * `rp_mapping` : dictionary of all groups and RPs + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + try: + config_data = [] + + for group, rp_list in rp_mapping.items(): + for _rp in rp_list: + config_data.append("interface {}".format(interface)) + config_data.append("ip address {}".format(_rp)) + config_data.append("ip pim") + + result = create_common_configuration( + tgen, rp, config_data, "interface_config" + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: add_rp_interfaces_and_pim_config()") + return result + + +def scapy_send_bsr_raw_packet( + tgen, topo, senderRouter, receiverRouter, packet=None, interval=1, count=1 +): + """ + Using scapy Raw() method to send BSR raw packet from one FRR + to other + + Parameters: + ----------- + * `tgen` : Topogen object + * `topo` : json file data + * `senderRouter` : Sender router + * `receiverRouter` : Receiver router + * `packet` : BSR packet in raw format + * `interval` : Interval between the packets + * `count` : Number of packets to be sent + + returns: + -------- + errormsg or True + """ + + global CWD + result = "" + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + rnode = tgen.routers()[senderRouter] + + for destLink, data in topo["routers"][senderRouter]["links"].items(): + if "type" in data and data["type"] == "loopback": + continue + + if "pim" in data and data["pim"] == "enable": + sender_interface = data["interface"] + + packet = topo["routers"][senderRouter]["bsm"]["bsr_packets"][packet]["data"] + + if interval > 1 or count > 1: + cmd = ( + "nohup /usr/bin/python {}/send_bsr_packet.py '{}' '{}' " + "--interval={} --count={} &".format( + CWD, packet, sender_interface, interval, count + ) + ) + else: + cmd = ( + "/usr/bin/python {}/send_bsr_packet.py '{}' '{}' " + "--interval={} --count={}".format( + CWD, packet, sender_interface, interval, count + ) + ) + + logger.info("Scapy cmd: \n %s", cmd) + result = rnode.run(cmd) + + if result == "": + return result + + logger.debug("Exiting lib API: scapy_send_bsr_raw_packet") + return True + + +def find_rp_from_bsrp_info(tgen, dut, bsr, grp=None): + """ + Find which RP is having lowest prioriy and returns rp IP + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `bsr`: BSR address + * 'grp': Group Address + + Usage + ----- + dut = "r1" + result = verify_pim_rp_info(tgen, dut, bsr) + + Returns: + dictionary: group and RP, which has to be installed as per + lowest priority or highest priority + """ + + rp_details = {} + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Fetching rp details from bsrp-info", dut) + bsrp_json = run_frr_cmd(rnode, "show ip pim bsrp-info json", isjson=True) + + if grp not in bsrp_json: + return {} + + for group, rp_data in bsrp_json.items(): + if group == "BSR Address" and bsrp_json["BSR Address"] == bsr: + continue + + if group != grp: + continue + + rp_priority = {} + rp_hash = {} + + for rp, value in rp_data.items(): + if rp == "Pending RP count": + continue + rp_priority[value["Rp Address"]] = value["Rp Priority"] + rp_hash[value["Rp Address"]] = value["Hash Val"] + + priority_dict = dict(zip(rp_priority.values(), rp_priority.keys())) + hash_dict = dict(zip(rp_hash.values(), rp_hash.keys())) + + # RP with lowest priority + if len(priority_dict) != 1: + rp_p, lowest_priority = sorted(rp_priority.items(), key=lambda x: x[1])[0] + rp_details[group] = rp_p + + # RP with highest hash value + if len(priority_dict) == 1: + rp_h, highest_hash = sorted(rp_hash.items(), key=lambda x: x[1])[-1] + rp_details[group] = rp_h + + # RP with highest IP address + if len(priority_dict) == 1 and len(hash_dict) == 1: + rp_details[group] = sorted(rp_priority.keys())[-1] + + return rp_details + + +@retry(attempts=6, wait=2, return_is_str=True) +def verify_pim_grp_rp_source(tgen, topo, dut, grp_addr, rp_source, rpadd=None): + """ + Verify pim rp info by running "show ip pim rp-info" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo`: JSON file handler + * `dut`: device under test + * `grp_addr`: IGMP group address + * 'rp_source': source from which rp installed + * 'rpadd': rp address + + Usage + ----- + dut = "r1" + group_address = "225.1.1.1" + rp_source = "BSR" + result = verify_pim_rp_and_source(tgen, topo, dut, group_address, rp_source) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying ip rp info", dut) + show_ip_rp_info_json = run_frr_cmd(rnode, "show ip pim rp-info json", isjson=True) + + if rpadd != None: + rp_json = show_ip_rp_info_json[rpadd] + if rp_json[0]["group"] == grp_addr: + if rp_json[0]["source"] == rp_source: + logger.info( + "[DUT %s]: Verifying Group and rp_source [PASSED]" + "Found Expected: %s, %s" + % (dut, rp_json[0]["group"], rp_json[0]["source"]) + ) + return True + else: + errormsg = ( + "[DUT %s]: Verifying Group and rp_source [FAILED]" + "Expected (%s, %s) " + "Found (%s, %s)" + % ( + dut, + grp_addr, + rp_source, + rp_json[0]["group"], + rp_json[0]["source"], + ) + ) + return errormsg + errormsg = ( + "[DUT %s]: Verifying Group and rp_source [FAILED]" + "Expected: %s, %s but not found" % (dut, grp_addr, rp_source) + ) + return errormsg + + for rp in show_ip_rp_info_json: + rp_json = show_ip_rp_info_json[rp] + logger.info("%s", rp_json) + if rp_json[0]["group"] == grp_addr: + if rp_json[0]["source"] == rp_source: + logger.info( + "[DUT %s]: Verifying Group and rp_source [PASSED]" + "Found Expected: %s, %s" + % (dut, rp_json[0]["group"], rp_json[0]["source"]) + ) + return True + else: + errormsg = ( + "[DUT %s]: Verifying Group and rp_source [FAILED]" + "Expected (%s, %s) " + "Found (%s, %s)" + % ( + dut, + grp_addr, + rp_source, + rp_json[0]["group"], + rp_json[0]["source"], + ) + ) + return errormsg + + errormsg = ( + "[DUT %s]: Verifying Group and rp_source [FAILED]" + "Expected: %s, %s but not found" % (dut, grp_addr, rp_source) + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return errormsg + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_pim_bsr(tgen, topo, dut, bsr_ip): + """ + Verify all PIM interface are up and running, config is verified + using "show ip pim interface" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo` : json file data + * `dut` : device under test + * 'bsr' : bsr ip to be verified + + Usage + ----- + result = verify_pim_bsr(tgen, topo, dut, bsr_ip) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for router in tgen.routers(): + if router != dut: + continue + + logger.info("[DUT: %s]: Verifying PIM bsr status:", dut) + + rnode = tgen.routers()[dut] + pim_bsr_json = rnode.vtysh_cmd("show ip pim bsr json", isjson=True) + + logger.info("show_ip_pim_bsr_json: \n %s", pim_bsr_json) + + # Verifying PIM bsr + if pim_bsr_json["bsr"] != bsr_ip: + errormsg = ( + "[DUT %s]:" + "bsr status: not found" + "[FAILED]!! Expected : %s, Found : %s" + % (dut, bsr_ip, pim_bsr_json["bsr"]) + ) + return errormsg + + logger.info( + "[DUT %s]:" " bsr status: found, Address :%s" " [PASSED]!!", + dut, + pim_bsr_json["bsr"], + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_ip_pim_upstream_rpf(tgen, topo, dut, interface, group_addresses, rp=None): + """ + Verify IP PIM upstream rpf, config is verified + using "show ip pim neighbor" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo` : json file data + * `dut` : devuce under test + * `interface` : upstream interface + * `group_addresses` : list of group address for which upstream info + needs to be checked + * `rp` : RP address + + Usage + ----- + result = verify_ip_pim_upstream_rpf(gen, topo, dut, interface, + group_addresses, rp=None) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if "pim" in topo["routers"][dut]: + + logger.info("[DUT: %s]: Verifying ip pim upstream rpf:", dut) + + rnode = tgen.routers()[dut] + show_ip_pim_upstream_rpf_json = rnode.vtysh_cmd( + "show ip pim upstream-rpf json", isjson=True + ) + + logger.info( + "show_ip_pim_upstream_rpf_json: \n %s", show_ip_pim_upstream_rpf_json + ) + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + for grp_addr in group_addresses: + for destLink, data in topo["routers"][dut]["links"].items(): + if "type" in data and data["type"] == "loopback": + continue + + if "pim" not in topo["routers"][destLink]: + continue + + # Verify RP info + if rp is None: + rp_details = find_rp_details(tgen, topo) + else: + rp_details = {dut: ip} + rp_details[dut] = rp + + if dut in rp_details: + pim_nh_intf_ip = topo["routers"][dut]["links"]["lo"]["ipv4"].split( + "/" + )[0] + else: + if destLink not in interface: + continue + + links = topo["routers"][destLink]["links"] + pim_neighbor = {key: links[key] for key in [dut]} + + data = pim_neighbor[dut] + if "pim" in data and data["pim"] == "enable": + pim_nh_intf_ip = data["ipv4"].split("/")[0] + + upstream_rpf_json = show_ip_pim_upstream_rpf_json[grp_addr]["*"] + + # Verifying ip pim upstream rpf + if ( + upstream_rpf_json["rpfInterface"] == interface + and upstream_rpf_json["ribNexthop"] != pim_nh_intf_ip + ): + errormsg = ( + "[DUT %s]: Verifying group: %s, " + "rpf interface: %s, " + " rib Nexthop check [FAILED]!!" + "Expected: %s, Found: %s" + % ( + dut, + grp_addr, + interface, + pim_nh_intf_ip, + upstream_rpf_json["ribNexthop"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying group: %s," + " rpf interface: %s, " + " rib Nexthop: %s [PASSED]!!", + dut, + grp_addr, + interface, + pim_nh_intf_ip, + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def enable_disable_pim_unicast_bsm(tgen, router, intf, enable=True): + """ + Helper API to enable or disable pim bsm on interfaces + + Parameters + ---------- + * `tgen` : Topogen object + * `router` : router id to be configured. + * `intf` : Interface to be configured + * `enable` : this flag denotes if config should be enabled or disabled + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + try: + config_data = [] + cmd = "interface {}".format(intf) + config_data.append(cmd) + + if enable == True: + config_data.append("ip pim unicast-bsm") + else: + config_data.append("no ip pim unicast-bsm") + + result = create_common_configuration( + tgen, router, config_data, "interface_config", build=False + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +def enable_disable_pim_bsm(tgen, router, intf, enable=True): + """ + Helper API to enable or disable pim bsm on interfaces + + Parameters + ---------- + * `tgen` : Topogen object + * `router` : router id to be configured. + * `intf` : Interface to be configured + * `enable` : this flag denotes if config should be enabled or disabled + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + try: + config_data = [] + cmd = "interface {}".format(intf) + config_data.append(cmd) + + if enable is True: + config_data.append("ip pim bsm") + else: + config_data.append("no ip pim bsm") + + result = create_common_configuration( + tgen, router, config_data, "interface_config", build=False + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_ip_pim_join(tgen, topo, dut, interface, group_addresses, src_address=None): + """ + Verify ip pim join by running "show ip pim join" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo`: JSON file handler + * `dut`: device under test + * `interface`: interface name, from which PIM join would come + * `group_addresses`: IGMP group address + * `src_address`: Source address + + Usage + ----- + dut = "r1" + interface = "r1-r0-eth0" + group_address = "225.1.1.1" + result = verify_ip_pim_join(tgen, dut, star, group_address, interface) + + Returns + ------- + errormsg(str) or True + """ + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying pim join", dut) + show_pim_join_json = run_frr_cmd(rnode, "show ip pim join json", isjson=True) + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + for grp_addr in group_addresses: + # Verify if IGMP is enabled in DUT + if "igmp" not in topo["routers"][dut]: + pim_join = True + else: + pim_join = False + + interface_json = show_pim_join_json[interface] + + grp_addr = grp_addr.split("/")[0] + for source, data in interface_json[grp_addr].items(): + + # Verify pim join + if pim_join: + if data["group"] == grp_addr and data["channelJoinName"] == "JOIN": + logger.info( + "[DUT %s]: Verifying pim join for group: %s" + "[PASSED]!! Found Expected: (%s)", + dut, + grp_addr, + data["channelJoinName"], + ) + else: + errormsg = ( + "[DUT %s]: Verifying pim join for group: %s" + "[FAILED]!! Expected: (%s) " + "Found: (%s)" % (dut, grp_addr, "JOIN", data["channelJoinName"]) + ) + return errormsg + + if not pim_join: + if data["group"] == grp_addr and data["channelJoinName"] == "NOINFO": + logger.info( + "[DUT %s]: Verifying pim join for group: %s" + "[PASSED]!! Found Expected: (%s)", + dut, + grp_addr, + data["channelJoinName"], + ) + else: + errormsg = ( + "[DUT %s]: Verifying pim join for group: %s" + "[FAILED]!! Expected: (%s) " + "Found: (%s)" + % (dut, grp_addr, "NOINFO", data["channelJoinName"]) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=31, wait=2, return_is_dict=True) +def verify_igmp_config(tgen, input_dict, stats_return=False): + """ + Verify igmp interface details, verifying following configs: + timerQueryInterval + timerQueryResponseIntervalMsec + lastMemberQueryCount + timerLastMemberQueryMsec + + Parameters + ---------- + * `tgen`: topogen object + * `input_dict` : Input dict data, required to verify + timer + * `stats_return`: If user wants API to return statistics + + Usage + ----- + input_dict ={ + "l1": { + "igmp": { + "interfaces": { + "l1-i1-eth1": { + "igmp": { + "query": { + "query-interval" : 200, + "query-max-response-time" : 100 + }, + "statistics": { + "queryV2" : 2, + "reportV2" : 1 + } + } + } + } + } + } + } + result = verify_igmp_config(tgen, input_dict, stats_return) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + for interface, data in input_dict[dut]["igmp"]["interfaces"].items(): + + statistics = False + report = False + if "statistics" in input_dict[dut]["igmp"]["interfaces"][interface]["igmp"]: + statistics = True + cmd = "show ip igmp statistics" + else: + cmd = "show ip igmp" + + logger.info( + "[DUT: %s]: Verifying IGMP interface %s detail:", dut, interface + ) + + if statistics: + if ( + "report" + in input_dict[dut]["igmp"]["interfaces"][interface]["igmp"][ + "statistics" + ] + ): + report = True + + if statistics and report: + show_ip_igmp_intf_json = run_frr_cmd( + rnode, "{} json".format(cmd, interface), isjson=True + ) + intf_detail_json = show_ip_igmp_intf_json["global"] + else: + show_ip_igmp_intf_json = run_frr_cmd( + rnode, "{} interface {} json".format(cmd, interface), isjson=True + ) + + if not report: + if interface not in show_ip_igmp_intf_json: + errormsg = ( + "[DUT %s]: IGMP interface: %s " + " is not present in CLI output " + "[FAILED]!! " % (dut, interface) + ) + return errormsg + + else: + intf_detail_json = show_ip_igmp_intf_json[interface] + + if stats_return: + igmp_stats = {} + + if "statistics" in data["igmp"]: + if stats_return: + igmp_stats["statistics"] = {} + for query, value in data["igmp"]["statistics"].items(): + if query == "queryV2": + # Verifying IGMP interface queryV2 statistics + if stats_return: + igmp_stats["statistics"][query] = intf_detail_json[ + "queryV2" + ] + + else: + if intf_detail_json["queryV2"] != value: + errormsg = ( + "[DUT %s]: IGMP interface: %s " + " queryV2 statistics verification " + "[FAILED]!! Expected : %s," + " Found : %s" + % ( + dut, + interface, + value, + intf_detail_json["queryV2"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " + "queryV2 statistics is %s", + dut, + interface, + value, + ) + + if query == "reportV2": + # Verifying IGMP interface timerV2 statistics + if stats_return: + igmp_stats["statistics"][query] = intf_detail_json[ + "reportV2" + ] + + else: + if intf_detail_json["reportV2"] <= value: + errormsg = ( + "[DUT %s]: IGMP reportV2 " + "statistics verification " + "[FAILED]!! Expected : %s " + "or more, Found : %s" + % ( + dut, + interface, + value, + intf_detail_json["reportV2"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP reportV2 " "statistics is %s", + dut, + intf_detail_json["reportV2"], + ) + + if "query" in data["igmp"]: + for query, value in data["igmp"]["query"].items(): + if query == "query-interval": + # Verifying IGMP interface query interval timer + if intf_detail_json["timerQueryInterval"] != value: + errormsg = ( + "[DUT %s]: IGMP interface: %s " + " query-interval verification " + "[FAILED]!! Expected : %s," + " Found : %s" + % ( + dut, + interface, + value, + intf_detail_json["timerQueryInterval"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " "query-interval is %s", + dut, + interface, + value, + ) + + if query == "query-max-response-time": + # Verifying IGMP interface query max response timer + if ( + intf_detail_json["timerQueryResponseIntervalMsec"] + != value * 100 + ): + errormsg = ( + "[DUT %s]: IGMP interface: %s " + "query-max-response-time " + "verification [FAILED]!!" + " Expected : %s, Found : %s" + % ( + dut, + interface, + value * 1000, + intf_detail_json["timerQueryResponseIntervalMsec"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " + "query-max-response-time is %s ms", + dut, + interface, + value * 100, + ) + + if query == "last-member-query-count": + # Verifying IGMP interface last member query count + if intf_detail_json["lastMemberQueryCount"] != value: + errormsg = ( + "[DUT %s]: IGMP interface: %s " + "last-member-query-count " + "verification [FAILED]!!" + " Expected : %s, Found : %s" + % ( + dut, + interface, + value, + intf_detail_json["lastMemberQueryCount"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " + "last-member-query-count is %s ms", + dut, + interface, + value * 1000, + ) + + if query == "last-member-query-interval": + # Verifying IGMP interface last member query interval + if ( + intf_detail_json["timerLastMemberQueryMsec"] + != value * 100 * intf_detail_json["lastMemberQueryCount"] + ): + errormsg = ( + "[DUT %s]: IGMP interface: %s " + "last-member-query-interval " + "verification [FAILED]!!" + " Expected : %s, Found : %s" + % ( + dut, + interface, + value * 1000, + intf_detail_json["timerLastMemberQueryMsec"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " + "last-member-query-interval is %s ms", + dut, + interface, + value * intf_detail_json["lastMemberQueryCount"] * 100, + ) + + if "version" in data["igmp"]: + # Verifying IGMP interface state is up + if intf_detail_json["state"] != "up": + errormsg = ( + "[DUT %s]: IGMP interface: %s " + " state: %s verification " + "[FAILED]!!" % (dut, interface, intf_detail_json["state"]) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " "state: %s", + dut, + interface, + intf_detail_json["state"], + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True if stats_return == False else igmp_stats + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_pim_config(tgen, input_dict): + """ + Verify pim interface details, verifying following configs: + drPriority + helloPeriod + helloReceived + helloSend + drAddress + + Parameters + ---------- + * `tgen`: topogen object + * `input_dict` : Input dict data, required to verify + timer + + Usage + ----- + input_dict ={ + "l1": { + "igmp": { + "interfaces": { + "l1-i1-eth1": { + "pim": { + "drPriority" : 10, + "helloPeriod" : 5 + } + } + } + } + } + } + } + result = verify_pim_config(tgen, input_dict) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + for interface, data in input_dict[dut]["pim"]["interfaces"].items(): + + logger.info("[DUT: %s]: Verifying PIM interface %s detail:", dut, interface) + + show_ip_igmp_intf_json = run_frr_cmd( + rnode, "show ip pim interface {} json".format(interface), isjson=True + ) + + if interface not in show_ip_igmp_intf_json: + errormsg = ( + "[DUT %s]: PIM interface: %s " + " is not present in CLI output " + "[FAILED]!! " % (dut, interface) + ) + return errormsg + + intf_detail_json = show_ip_igmp_intf_json[interface] + + for config, value in data.items(): + if config == "helloPeriod": + # Verifying PIM interface helloPeriod + if intf_detail_json["helloPeriod"] != value: + errormsg = ( + "[DUT %s]: PIM interface: %s " + " helloPeriod verification " + "[FAILED]!! Expected : %s," + " Found : %s" + % (dut, interface, value, intf_detail_json["helloPeriod"]) + ) + return errormsg + + logger.info( + "[DUT %s]: PIM interface: %s " "helloPeriod is %s", + dut, + interface, + value, + ) + + if config == "drPriority": + # Verifying PIM interface drPriority + if intf_detail_json["drPriority"] != value: + errormsg = ( + "[DUT %s]: PIM interface: %s " + " drPriority verification " + "[FAILED]!! Expected : %s," + " Found : %s" + % (dut, interface, value, intf_detail_json["drPriority"]) + ) + return errormsg + + logger.info( + "[DUT %s]: PIM interface: %s " "drPriority is %s", + dut, + interface, + value, + ) + + if config == "drAddress": + # Verifying PIM interface drAddress + if intf_detail_json["drAddress"] != value: + errormsg = ( + "[DUT %s]: PIM interface: %s " + " drAddress verification " + "[FAILED]!! Expected : %s," + " Found : %s" + % (dut, interface, value, intf_detail_json["drAddress"]) + ) + return errormsg + + logger.info( + "[DUT %s]: PIM interface: %s " "drAddress is %s", + dut, + interface, + value, + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=21, wait=2, return_is_dict=True) +def verify_multicast_traffic(tgen, input_dict, return_traffic=False): + """ + Verify multicast traffic by running + "show multicast traffic count json" cli + + Parameters + ---------- + * `tgen`: topogen object + * `input_dict(dict)`: defines DUT, what and for which interfaces + traffic needs to be verified + * `return_traffic`: returns traffic stats + Usage + ----- + input_dict = { + "r1": { + "traffic_received": ["r1-r0-eth0"], + "traffic_sent": ["r1-r0-eth0"] + } + } + + result = verify_multicast_traffic(tgen, input_dict) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + traffic_dict = {} + for dut in input_dict.keys(): + if dut not in tgen.routers(): + continue + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying multicast " "traffic", dut) + + show_multicast_traffic_json = run_frr_cmd( + rnode, "show ip multicast count json", isjson=True + ) + + for traffic_type, interfaces in input_dict[dut].items(): + traffic_dict[traffic_type] = {} + if traffic_type == "traffic_received": + for interface in interfaces: + traffic_dict[traffic_type][interface] = {} + interface_json = show_multicast_traffic_json[interface] + + if interface_json["pktsIn"] == 0 and interface_json["bytesIn"] == 0: + errormsg = ( + "[DUT %s]: Multicast traffic is " + "not received on interface %s " + "PktsIn: %s, BytesIn: %s " + "[FAILED]!!" + % ( + dut, + interface, + interface_json["pktsIn"], + interface_json["bytesIn"], + ) + ) + return errormsg + + elif ( + interface_json["pktsIn"] != 0 and interface_json["bytesIn"] != 0 + ): + + traffic_dict[traffic_type][interface][ + "pktsIn" + ] = interface_json["pktsIn"] + traffic_dict[traffic_type][interface][ + "bytesIn" + ] = interface_json["bytesIn"] + + logger.info( + "[DUT %s]: Multicast traffic is " + "received on interface %s " + "PktsIn: %s, BytesIn: %s " + "[PASSED]!!" + % ( + dut, + interface, + interface_json["pktsIn"], + interface_json["bytesIn"], + ) + ) + + else: + errormsg = ( + "[DUT %s]: Multicast traffic interface %s:" + " Miss-match in " + "PktsIn: %s, BytesIn: %s" + "[FAILED]!!" + % ( + dut, + interface, + interface_json["pktsIn"], + interface_json["bytesIn"], + ) + ) + return errormsg + + if traffic_type == "traffic_sent": + traffic_dict[traffic_type] = {} + for interface in interfaces: + traffic_dict[traffic_type][interface] = {} + interface_json = show_multicast_traffic_json[interface] + + if ( + interface_json["pktsOut"] == 0 + and interface_json["bytesOut"] == 0 + ): + errormsg = ( + "[DUT %s]: Multicast traffic is " + "not received on interface %s " + "PktsIn: %s, BytesIn: %s" + "[FAILED]!!" + % ( + dut, + interface, + interface_json["pktsOut"], + interface_json["bytesOut"], + ) + ) + return errormsg + + elif ( + interface_json["pktsOut"] != 0 + and interface_json["bytesOut"] != 0 + ): + + traffic_dict[traffic_type][interface][ + "pktsOut" + ] = interface_json["pktsOut"] + traffic_dict[traffic_type][interface][ + "bytesOut" + ] = interface_json["bytesOut"] + + logger.info( + "[DUT %s]: Multicast traffic is " + "received on interface %s " + "PktsOut: %s, BytesOut: %s " + "[PASSED]!!" + % ( + dut, + interface, + interface_json["pktsOut"], + interface_json["bytesOut"], + ) + ) + else: + errormsg = ( + "[DUT %s]: Multicast traffic interface %s:" + " Miss-match in " + "PktsOut: %s, BytesOut: %s " + "[FAILED]!!" + % ( + dut, + interface, + interface_json["pktsOut"], + interface_json["bytesOut"], + ) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True if return_traffic == False else traffic_dict + + +def get_refCount_for_mroute(tgen, dut, iif, src_address, group_addresses): + """ + Verify upstream inbound interface is updated correctly + by running "show ip pim upstream" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `iif`: inbound interface + * `src_address`: source address + * `group_addresses`: IGMP group address + + Usage + ----- + dut = "r1" + iif = "r1-r0-eth0" + src_address = "*" + group_address = "225.1.1.1" + result = get_refCount_for_mroute(tgen, dut, iif, src_address, + group_address) + + Returns + ------- + refCount(int) + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + refCount = 0 + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying refCount for mroutes: ", dut) + show_ip_pim_upstream_json = run_frr_cmd( + rnode, "show ip pim upstream json", isjson=True + ) + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + for grp_addr in group_addresses: + # Verify group address + if grp_addr not in show_ip_pim_upstream_json: + errormsg = "[DUT %s]: Verifying upstream" " for group %s [FAILED]!!" % ( + dut, + grp_addr, + ) + return errormsg + group_addr_json = show_ip_pim_upstream_json[grp_addr] + + # Verify source address + if src_address not in group_addr_json: + errormsg = "[DUT %s]: Verifying upstream" " for (%s,%s) [FAILED]!!" % ( + dut, + src_address, + grp_addr, + ) + return errormsg + + # Verify Inbound Interface + if group_addr_json[src_address]["inboundInterface"] == iif: + refCount = group_addr_json[src_address]["refCount"] + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return refCount + + +@retry(attempts=21, wait=2, return_is_str=True) +def verify_multicast_flag_state(tgen, dut, src_address, group_addresses, flag): + """ + Verify flag state for mroutes and make sure (*, G)/(S, G) are having + coorect flags by running "show ip mroute" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `src_address`: source address + * `group_addresses`: IGMP group address + * `flag`: flag state, needs to be verified + + Usage + ----- + dut = "r1" + flag = "SC" + group_address = "225.1.1.1" + result = verify_multicast_flag_state(tgen, dut, src_address, + group_address, flag) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying flag state for mroutes", dut) + show_ip_mroute_json = run_frr_cmd(rnode, "show ip mroute json", isjson=True) + + if bool(show_ip_mroute_json) == False: + error_msg = "[DUT %s]: mroutes are not present or flushed out !!" % (dut) + return error_msg + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + for grp_addr in group_addresses: + if grp_addr not in show_ip_mroute_json: + errormsg = ( + "[DUT %s]: Verifying (%s, %s) mroute," "[FAILED]!! ", + dut, + src_address, + grp_addr, + ) + return errormsg + else: + group_addr_json = show_ip_mroute_json[grp_addr] + + if src_address not in group_addr_json: + errormsg = "[DUT %s]: Verifying (%s, %s) mroute," "[FAILED]!! " % ( + dut, + src_address, + grp_addr, + ) + return errormsg + else: + mroutes = group_addr_json[src_address] + + if mroutes["installed"] != 0: + logger.info( + "[DUT %s]: mroute (%s,%s) is installed", dut, src_address, grp_addr + ) + + if mroutes["flags"] != flag: + errormsg = ( + "[DUT %s]: Verifying flag for (%s, %s) " + "mroute [FAILED]!! " + "Expected: %s Found: %s" + % (dut, src_address, grp_addr, flag, mroutes["flags"]) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying flag for (%s, %s)" + " mroute, [PASSED]!! " + "Found Expected: %s", + dut, + src_address, + grp_addr, + mroutes["flags"], + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True diff --git a/tests/topotests/lib/topojson.py b/tests/topotests/lib/topojson.py index 0e59f90a20..88e6f78b92 100644 --- a/tests/topotests/lib/topojson.py +++ b/tests/topotests/lib/topojson.py @@ -43,6 +43,7 @@ from lib.common_config import ( create_vrf_cfg, ) +from lib.pim import create_pim_config, create_igmp_config from lib.bgp import create_router_bgp from lib.ospf import create_router_ospf @@ -68,20 +69,18 @@ def build_topo_from_json(tgen, topo): topo["switches"].keys(), key=lambda x: int(re_search("\d+", x).group(0)) ) - listRouters = ROUTER_LIST[:] - listSwitches = SWITCH_LIST[:] + listRouters = sorted(ROUTER_LIST[:]) + listSwitches = sorted(SWITCH_LIST[:]) listAllRouters = deepcopy(listRouters) dictSwitches = {} for routerN in ROUTER_LIST: logger.info("Topo: Add router {}".format(routerN)) tgen.add_router(routerN) - listRouters.append(routerN) for switchN in SWITCH_LIST: logger.info("Topo: Add switch {}".format(switchN)) dictSwitches[switchN] = tgen.add_switch(switchN) - listSwitches.append(switchN) if "ipv4base" in topo: ipv4Next = ipaddress.IPv4Address(topo["link_ip_start"]["ipv4"]) @@ -101,18 +100,8 @@ def build_topo_from_json(tgen, topo): curRouter = listRouters.pop(0) # Physical Interfaces if "links" in topo["routers"][curRouter]: - - def link_sort(x): - if x == "lo": - return 0 - elif "link" in x: - return int(x.split("-link")[1]) - else: - return int(re_search("\d+", x).group(0)) - for destRouterLink, data in sorted( - topo["routers"][curRouter]["links"].items(), - key=lambda x: link_sort(x[0]), + topo["routers"][curRouter]["links"].iteritems() ): currRouter_lo_json = topo["routers"][curRouter]["links"][destRouterLink] # Loopback interfaces @@ -321,6 +310,8 @@ def build_config_from_json(tgen, topo, save_bkup=True): ("prefix_lists", create_prefix_lists), ("bgp_community_list", create_bgp_community_lists), ("route_maps", create_route_maps), + ("pim", create_pim_config), + ("igmp", create_igmp_config), ("bgp", create_router_bgp), ("ospf", create_router_ospf), ] diff --git a/yang/frr-bgp-common.yang b/yang/frr-bgp-common.yang index cb1a6a8f56..1840e3728c 100644 --- a/yang/frr-bgp-common.yang +++ b/yang/frr-bgp-common.yang @@ -358,6 +358,13 @@ submodule frr-bgp-common { "Apply administrative shutdown to newly configured peers."; } + leaf suppress-duplicates { + type boolean; + default "true"; + description + "Suppress duplicate updates if the route actually not changed."; + } + leaf ebgp-requires-policy { type boolean; default "true"; diff --git a/yang/frr-vrrpd.yang b/yang/frr-vrrpd.yang index c99d6d9877..200eaeb0b5 100644 --- a/yang/frr-vrrpd.yang +++ b/yang/frr-vrrpd.yang @@ -246,7 +246,7 @@ module frr-vrrpd { } uses ip-vrrp-state { - augment "./counter/tx" { + augment "counter/tx" { leaf gratuitous-arp { type yang:zero-based-counter32; description @@ -266,7 +266,7 @@ module frr-vrrpd { } uses ip-vrrp-state { - augment "./counter/tx" { + augment "counter/tx" { leaf neighbor-advertisement { type yang:zero-based-counter32; description diff --git a/zebra/rule_netlink.c b/zebra/rule_netlink.c index a63504992e..08a675ef3a 100644 --- a/zebra/rule_netlink.c +++ b/zebra/rule_netlink.c @@ -79,7 +79,15 @@ netlink_rule_msg_encode(int cmd, const struct zebra_dplane_ctx *ctx, if (buflen < sizeof(*req)) return 0; memset(req, 0, sizeof(*req)); - family = PREFIX_FAMILY(src_ip); + + /* Assume ipv4 if no src/dst set, we only support ipv4/ipv6 */ + if (PREFIX_FAMILY(src_ip)) + family = PREFIX_FAMILY(src_ip); + else if (PREFIX_FAMILY(dst_ip)) + family = PREFIX_FAMILY(dst_ip); + else + family = AF_INET; + bytelen = (family == AF_INET ? 4 : 16); req->n.nlmsg_type = cmd; |
