summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_advertise.h3
-rw-r--r--bgpd/bgp_clist.c39
-rw-r--r--bgpd/bgp_clist.h1
-rw-r--r--bgpd/bgp_lcommunity.c130
-rw-r--r--bgpd/bgp_lcommunity.h1
-rw-r--r--bgpd/bgp_nb.c7
-rw-r--r--bgpd/bgp_nb.h4
-rw-r--r--bgpd/bgp_nb_config.c21
-rw-r--r--bgpd/bgp_packet.c12
-rw-r--r--bgpd/bgp_route.c18
-rw-r--r--bgpd/bgp_updgrp.c5
-rw-r--r--bgpd/bgp_updgrp.h12
-rw-r--r--bgpd/bgp_updgrp_adv.c22
-rw-r--r--bgpd/bgp_vty.c67
-rw-r--r--bgpd/bgp_zebra.c106
-rw-r--r--bgpd/bgpd.c9
-rw-r--r--bgpd/bgpd.h1
-rw-r--r--doc/developer/link-state.rst314
-rw-r--r--doc/user/bgp.rst11
-rw-r--r--lib/link_state.c1284
-rw-r--r--lib/link_state.h780
-rw-r--r--lib/subdir.am2
-rw-r--r--pathd/path_pcep_controller.c3
-rw-r--r--pathd/pathd.c3
-rw-r--r--pbrd/pbr_vty.c10
-rw-r--r--snapcraft/snapcraft.yaml.in6
-rw-r--r--tests/topotests/bgp_community_change_update/__init__.py0
-rw-r--r--tests/topotests/bgp_community_change_update/c1/bgpd.conf11
-rw-r--r--tests/topotests/bgp_community_change_update/c1/zebra.conf6
-rw-r--r--tests/topotests/bgp_community_change_update/test_bgp_community_change_update.py225
-rw-r--r--tests/topotests/bgp_community_change_update/x1/bgpd.conf20
-rw-r--r--tests/topotests/bgp_community_change_update/x1/zebra.conf9
-rw-r--r--tests/topotests/bgp_community_change_update/y1/bgpd.conf12
-rw-r--r--tests/topotests/bgp_community_change_update/y1/zebra.conf12
-rw-r--r--tests/topotests/bgp_community_change_update/y2/bgpd.conf18
-rw-r--r--tests/topotests/bgp_community_change_update/y2/zebra.conf12
-rw-r--r--tests/topotests/bgp_community_change_update/y3/bgpd.conf18
-rw-r--r--tests/topotests/bgp_community_change_update/y3/zebra.conf12
-rw-r--r--tests/topotests/bgp_community_change_update/z1/bgpd.conf10
-rw-r--r--tests/topotests/bgp_community_change_update/z1/zebra.conf12
-rw-r--r--tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1.py4
-rw-r--r--tests/topotests/bgp_large_community/test_bgp_large_community_topo_1.py33
-rw-r--r--tests/topotests/lib/common_config.py527
-rw-r--r--tests/topotests/lib/lutil.py4
-rw-r--r--tests/topotests/lib/ospf.py3
-rw-r--r--tests/topotests/lib/pim.py3389
-rw-r--r--tests/topotests/lib/topojson.py21
-rw-r--r--yang/frr-bgp-common.yang7
-rw-r--r--yang/frr-vrrpd.yang4
-rw-r--r--zebra/rule_netlink.c10
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;