summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_route.c15
-rw-r--r--bgpd/bgpd.c49
-rw-r--r--doc/developer/mgmtd-dev.rst8
-rw-r--r--lib/northbound_oper.c85
-rw-r--r--ospfd/ospf_lsa.c10
-rw-r--r--ospfd/ospf_lsa.h1
-rw-r--r--pimd/pim_autorp.c63
-rw-r--r--pimd/pim_autorp.h1
-rw-r--r--pimd/pim_iface.c7
-rw-r--r--pimd/pim_instance.c7
-rw-r--r--pimd/pim_mroute.c10
-rw-r--r--ripd/rip_cli.c4
-rw-r--r--tests/lib/northbound/test_oper_data.in4
-rw-r--r--tests/lib/northbound/test_oper_data.refout30
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/__init__.py0
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/frr.conf52
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast.json24
l---------tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_vpn_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast.json25
l---------tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_vpn_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_summary.json24
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/frr.conf107
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn.json29
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn.json29
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_summary.json46
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/frr.conf79
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn.json28
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn_step1.json12
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn.json28
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn_step1.json12
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_summary.json24
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/test_bgp_l3vpn_hidden.py287
-rw-r--r--tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf14
-rw-r--r--tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py10
-rwxr-xr-xtests/topotests/lib/fe_client.py81
-rw-r--r--tests/topotests/pim_autorp/test_pim_autorp.py97
-rw-r--r--tests/topotests/zebra_operational_data/r1/frr.conf41
-rw-r--r--tests/topotests/zebra_operational_data/test_zebra_operational.py64
-rw-r--r--yang/frr-zebra.yang11
-rw-r--r--zebra/zebra_dplane.c3
-rw-r--r--zebra/zebra_nb.c6
-rw-r--r--zebra/zebra_nb.h1
-rw-r--r--zebra/zebra_nb_state.c9
47 files changed, 1293 insertions, 150 deletions
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index 479a7dfed1..3bbbdee161 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -3211,21 +3211,6 @@ void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest,
bgp_path_info_unset_flag(dest, look_thru,
BGP_PATH_DMED_CHECK);
- if (CHECK_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED) &&
- (!CHECK_FLAG(look_thru->flags,
- BGP_PATH_DMED_SELECTED))) {
- bgp_path_info_unset_flag(dest, look_thru,
- BGP_PATH_DMED_CHECK);
- if (debug)
- zlog_debug("%s: %pBD(%s) pi %s dmed",
- __func__, dest,
- bgp->name_pretty,
- look_thru->peer->host);
-
- worse = look_thru;
- continue;
- }
-
reason = dest->reason;
any_comparisons = true;
if (bgp_path_info_cmp(bgp, first, look_thru, &paths_eq,
diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
index eab059abea..cf1ea78acf 100644
--- a/bgpd/bgpd.c
+++ b/bgpd/bgpd.c
@@ -3412,12 +3412,28 @@ static struct bgp *bgp_create(as_t *as, const char *name,
afi_t afi;
safi_t safi;
- if (hidden) {
+ if (hidden)
bgp = bgp_old;
- goto peer_init;
- }
+ else
+ bgp = XCALLOC(MTYPE_BGP, sizeof(struct bgp));
+
+ bgp->as = *as;
+
+ if (bgp->as_pretty)
+ XFREE(MTYPE_BGP_NAME, bgp->as_pretty);
+ if (as_pretty)
+ bgp->as_pretty = XSTRDUP(MTYPE_BGP_NAME, as_pretty);
+ else
+ bgp->as_pretty = XSTRDUP(MTYPE_BGP_NAME, asn_asn2asplain(*as));
- bgp = XCALLOC(MTYPE_BGP, sizeof(struct bgp));
+ if (asnotation != ASNOTATION_UNDEFINED) {
+ bgp->asnotation = asnotation;
+ SET_FLAG(bgp->config, BGP_CONFIG_ASNOTATION);
+ } else
+ asn_str2asn_notation(bgp->as_pretty, NULL, &bgp->asnotation);
+
+ if (hidden)
+ goto peer_init;
if (BGP_DEBUG(zebra, ZEBRA)) {
if (inst_type == BGP_INSTANCE_TYPE_DEFAULT)
@@ -3461,18 +3477,6 @@ static struct bgp *bgp_create(as_t *as, const char *name,
bgp->peer = list_new();
peer_init:
- bgp->as = *as;
- if (as_pretty)
- bgp->as_pretty = XSTRDUP(MTYPE_BGP_NAME, as_pretty);
- else
- bgp->as_pretty = XSTRDUP(MTYPE_BGP_NAME, asn_asn2asplain(*as));
-
- if (asnotation != ASNOTATION_UNDEFINED) {
- bgp->asnotation = asnotation;
- SET_FLAG(bgp->config, BGP_CONFIG_ASNOTATION);
- } else
- asn_str2asn_notation(bgp->as_pretty, NULL, &bgp->asnotation);
-
bgp->peer->cmp = (int (*)(void *, void *))peer_cmp;
bgp->peerhash = hash_create(peer_hash_key_make, peer_hash_same,
"BGP Peer Hash");
@@ -3569,7 +3573,7 @@ peer_init:
/* printable name we can use in debug messages */
if (inst_type == BGP_INSTANCE_TYPE_DEFAULT && !hidden) {
bgp->name_pretty = XSTRDUP(MTYPE_BGP_NAME, "VRF default");
- } else {
+ } else if (!hidden) {
const char *n;
int len;
@@ -3772,7 +3776,7 @@ int bgp_lookup_by_as_name_type(struct bgp **bgp_val, as_t *as, const char *as_pr
/* Handle AS number change */
if (bgp->as != *as) {
if (hidden || CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) {
- if (force_config == false && hidden) {
+ if (hidden) {
bgp_create(as, name, inst_type,
as_pretty, asnotation, bgp,
hidden);
@@ -4265,12 +4269,11 @@ int bgp_delete(struct bgp *bgp)
bgp_set_evpn(bgp_get_default());
}
- if (bgp->process_queue)
- work_queue_free_and_null(&bgp->process_queue);
-
- if (!IS_BGP_INSTANCE_HIDDEN(bgp))
+ if (!IS_BGP_INSTANCE_HIDDEN(bgp)) {
+ if (bgp->process_queue)
+ work_queue_free_and_null(&bgp->process_queue);
bgp_unlock(bgp); /* initial reference */
- else {
+ } else {
for (afi = AFI_IP; afi < AFI_MAX; afi++) {
enum vpn_policy_direction dir;
diff --git a/doc/developer/mgmtd-dev.rst b/doc/developer/mgmtd-dev.rst
index 6cbd617f8c..f113d1b521 100644
--- a/doc/developer/mgmtd-dev.rst
+++ b/doc/developer/mgmtd-dev.rst
@@ -39,17 +39,18 @@ Conversion Status
Fully Converted To MGMTD
""""""""""""""""""""""""
+- lib/affinitymap
- lib/distribute
- lib/filter
+- lib/if
- lib/if_rmap
+- lib/keychain
- lib/routemap
-- lib/affinitymap
-- lib/if
- lib/vrf
- ripd
- ripngd
- staticd
-- zebra (* - partial)
+- zebra
Converted To Northbound
"""""""""""""""""""""""
@@ -69,7 +70,6 @@ Unconverted
- bgpd
- ldpd
- lib/event
-- lib/keychain
- lib/log_vty
- lib/nexthop_group
- lib/zlog_5424_cli
diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c
index 6336db502a..b7815b001a 100644
--- a/lib/northbound_oper.c
+++ b/lib/northbound_oper.c
@@ -140,6 +140,11 @@ nb_op_create_yield_state(const char *xpath, struct yang_translator *translator,
ys = XCALLOC(MTYPE_NB_YIELD_STATE, sizeof(*ys));
ys->xpath = darr_strdup_cap(xpath, (size_t)XPATH_MAXLEN);
+ /* remove trailing '/'s */
+ while (darr_len(ys->xpath) > 1 && ys->xpath[darr_len(ys->xpath) - 2] == '/') {
+ darr_setlen(ys->xpath, darr_len(ys->xpath) - 1);
+ *darr_last(ys->xpath) = 0;
+ }
ys->xpath_orig = darr_strdup(xpath);
ys->translator = translator;
ys->flags = flags;
@@ -417,8 +422,7 @@ static enum nb_error nb_op_ys_finalize_node_info(struct nb_op_yield_state *ys,
/* Assert that we are walking the rightmost branch */
assert(!inner->parent || inner == inner->parent->child->prev);
- if (CHECK_FLAG(inner->schema->nodetype,
- LYS_CASE | LYS_CHOICE | LYS_CONTAINER)) {
+ if (CHECK_FLAG(inner->schema->nodetype, LYS_CONTAINER)) {
/* containers have only zero or one child on a branch of a tree */
inner = ((struct lyd_node_inner *)inner)->child;
assert(!inner || inner->prev == inner);
@@ -568,7 +572,8 @@ static enum nb_error nb_op_ys_init_node_infos(struct nb_op_yield_state *ys)
inner = node;
prevlen = 0;
xplen = strlen(xpath);
- darr_free(xpath);
+ darr_free(ys->xpath);
+ ys->xpath = xpath;
for (i = len; i > 0; i--, inner = &inner->parent->node) {
ni = &ys->node_infos[i - 1];
ni->inner = inner;
@@ -897,8 +902,7 @@ static const struct lysc_node *nb_op_sib_first(struct nb_op_yield_state *ys,
* base of the user query, return the next schema node from the query
* string (schema_path).
*/
- if (last != NULL &&
- !CHECK_FLAG(last->schema->nodetype, LYS_CASE | LYS_CHOICE))
+ if (last != NULL)
assert(last->schema == parent);
if (darr_lasti(ys->node_infos) < ys->query_base_level)
return ys->schema_path[darr_lasti(ys->node_infos) + 1];
@@ -975,7 +979,8 @@ static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume)
if (!walk_stem_tip)
return NB_ERR_NOT_FOUND;
- if (ys->schema_path[0]->nodetype == LYS_CHOICE) {
+ if (ys->schema_path[0]->parent &&
+ CHECK_FLAG(ys->schema_path[0]->parent->nodetype, LYS_CHOICE|LYS_CASE)) {
flog_err(EC_LIB_NB_OPERATIONAL_DATA,
"%s: unable to walk root level choice node from module: %s",
__func__, ys->schema_path[0]->module->name);
@@ -1082,8 +1087,12 @@ static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume)
LYS_LEAF | LYS_LEAFLIST | LYS_CONTAINER))
xpath_child = nb_op_get_child_path(ys->xpath, sib,
xpath_child);
- else if (CHECK_FLAG(sib->nodetype, LYS_CASE | LYS_CHOICE))
+ else if (CHECK_FLAG(sib->nodetype, LYS_CASE | LYS_CHOICE)) {
darr_in_strdup(xpath_child, ys->xpath);
+ len = darr_last(ys->node_infos)->xpath_len;
+ darr_setlen(xpath_child, len + 1);
+ xpath_child[len] = 0;
+ }
nn = sib->priv;
@@ -1703,22 +1712,22 @@ static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys,
* NOTE: appears to be a bug in nb_node linkage where parent can be NULL,
* or I'm misunderstanding the code, in any case we use the libyang
* linkage to walk which works fine.
- *
- * XXX: we don't actually support choice/case yet, they are container
- * types in the libyang schema, but won't be in data so our length
- * checking gets messed up.
*/
- for (sn = nblast->snode, count = 0; sn; count++, sn = sn->parent)
+ for (sn = nblast->snode, count = 0; sn; sn = sn->parent) {
if (sn != nblast->snode)
assert(CHECK_FLAG(sn->nodetype,
- LYS_CONTAINER | LYS_LIST |
- LYS_CHOICE | LYS_CASE));
+ LYS_CONTAINER | LYS_LIST | LYS_CHOICE | LYS_CASE));
+ if (!CHECK_FLAG(sn->nodetype, LYS_CHOICE | LYS_CASE))
+ count++;
+ }
/* create our arrays */
darr_append_n(ys->schema_path, count);
darr_append_n(ys->query_tokens, count);
darr_append_nz(ys->non_specific_predicate, count);
- for (sn = nblast->snode; sn; sn = sn->parent)
- ys->schema_path[--count] = sn;
+ for (sn = nblast->snode; sn; sn = sn->parent) {
+ if (!CHECK_FLAG(sn->nodetype, LYS_CHOICE | LYS_CASE))
+ ys->schema_path[--count] = sn;
+ }
/*
* Now tokenize the query string and get pointers to each token
@@ -1737,50 +1746,42 @@ static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys,
int nlen = strlen(name);
int mnlen = 0;
- /*
- * Technically the query_token for choice/case should probably be pointing at
- * the child (leaf) rather than the parent (container), however,
- * we only use these for processing list nodes so KISS.
- */
- if (CHECK_FLAG(ys->schema_path[i]->nodetype,
- LYS_CASE | LYS_CHOICE)) {
- ys->query_tokens[i] = ys->query_tokens[i - 1];
- continue;
- }
-
+ s2 = s;
while (true) {
- s2 = strstr(s, name);
+ /* skip past any module name prefix */
+ s2 = strstr(s2, name);
if (!s2)
goto error;
- if (s2[-1] == ':') {
+ if (s2 > s && s2[-1] == ':') {
mnlen = strlen(modname) + 1;
- if (ys->query_tokstr > s2 - mnlen ||
- strncmp(s2 - mnlen, modname, mnlen - 1))
- goto error;
+ if (s2 - s < mnlen || strncmp(s2 - mnlen, modname, mnlen - 1)) {
+ /* No match of module prefix, advance and try again */
+ s2 += strlen(name);
+ continue;
+ }
s2 -= mnlen;
nlen += mnlen;
}
- s = s2;
- if ((i == 0 || s[-1] == '/') &&
- (s[nlen] == 0 || s[nlen] == '[' || s[nlen] == '/'))
+ if ((i == 0 || s2[-1] == '/') &&
+ (s2[nlen] == 0 || s2[nlen] == '[' || s2[nlen] == '/')) {
+ s = s2;
break;
- /*
- * Advance past the incorrect match, must have been
- * part of previous predicate.
- */
- s += nlen;
+ }
+ /* No exact match at end, advance and try again */
+ s2 += strlen(name);
}
/* NUL terminate previous token and save this one */
- if (i > 0)
+ if (i > 0) {
+ assert(s[-1] == '/');
s[-1] = 0;
+ }
ys->query_tokens[i] = s;
s += nlen;
}
- /* NOTE: need to subtract choice/case nodes when these are supported */
ys->query_base_level = darr_lasti(ys->schema_path);
return NB_OK;
diff --git a/ospfd/ospf_lsa.c b/ospfd/ospf_lsa.c
index 1d5c0be5c4..15068ec820 100644
--- a/ospfd/ospf_lsa.c
+++ b/ospfd/ospf_lsa.c
@@ -87,16 +87,6 @@ bool ospf_check_dna_lsa(const struct ospf_lsa *lsa)
: false);
}
-struct timeval int2tv(int a)
-{
- struct timeval ret;
-
- ret.tv_sec = a;
- ret.tv_usec = 0;
-
- return ret;
-}
-
struct timeval msec2tv(int a)
{
struct timeval ret;
diff --git a/ospfd/ospf_lsa.h b/ospfd/ospf_lsa.h
index f58bbde07a..309d506ea6 100644
--- a/ospfd/ospf_lsa.h
+++ b/ospfd/ospf_lsa.h
@@ -224,7 +224,6 @@ enum lsid_status { LSID_AVAILABLE = 0, LSID_CHANGE, LSID_NOT_AVAILABLE };
/* Prototypes. */
/* XXX: Eek, time functions, similar are in lib/thread.c */
-extern struct timeval int2tv(int);
extern struct timeval msec2tv(int a);
extern int tv2msec(struct timeval tv);
diff --git a/pimd/pim_autorp.c b/pimd/pim_autorp.c
index dc077dbbd6..d3f3517efd 100644
--- a/pimd/pim_autorp.c
+++ b/pimd/pim_autorp.c
@@ -967,6 +967,7 @@ err:
static bool pim_autorp_socket_enable(struct pim_autorp *autorp)
{
int fd;
+ struct interface *ifp;
/* Return early if socket is already enabled */
if (autorp->sock != -1)
@@ -980,6 +981,13 @@ static bool pim_autorp_socket_enable(struct pim_autorp *autorp)
return false;
}
+ if (vrf_bind(autorp->pim->vrf->vrf_id, fd, NULL)) {
+ zlog_warn("Could not bind autorp socket to vrf fd=%d: vrf_id=%d: errno=%d: %s",
+ fd, autorp->pim->vrf->vrf_id, errno, safe_strerror(errno));
+ close(fd);
+ return false;
+ }
+
if (!pim_autorp_setup(fd)) {
zlog_warn("Could not setup autorp socket fd=%d: errno=%d: %s", fd, errno,
safe_strerror(errno));
@@ -990,6 +998,11 @@ static bool pim_autorp_socket_enable(struct pim_autorp *autorp)
autorp->sock = fd;
+ /* Join autorp groups on all pim enabled interfaces in the VRF */
+ FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) {
+ pim_autorp_add_ifp(ifp);
+ }
+
if (PIM_DEBUG_AUTORP)
zlog_debug("%s: AutoRP socket enabled (fd=%u)", __func__, fd);
@@ -1002,6 +1015,7 @@ static bool pim_autorp_socket_disable(struct pim_autorp *autorp)
if (autorp->sock == -1)
return true;
+ /* No need to leave the autorp groups explicitly, they are left when the socket is closed */
if (close(autorp->sock)) {
zlog_warn("Failure closing autorp socket: fd=%d errno=%d: %s", autorp->sock, errno,
safe_strerror(errno));
@@ -1428,10 +1442,10 @@ void pim_autorp_add_ifp(struct interface *ifp)
{
/* Add a new interface for autorp
* When autorp is enabled, we must join the autorp groups on all
- * pim/multicast interfaces. When autorp first starts, if finds all
- * current multicast interfaces and joins on them. If a new interface
- * comes up or is configured for multicast after autorp is running, then
- * this method will add it for autorp->
+ * pim/multicast interfaces. When autorp becomes enabled, it finds all
+ * current pim enabled interfaces and joins the autorp groups on them.
+ * Any new interfaces added after autorp is enabled will use this function
+ * to join the autorp groups
* This is called even when adding a new pim interface that is not yet
* active, so make sure the check, it'll call in again once the interface is up.
*/
@@ -1441,7 +1455,8 @@ void pim_autorp_add_ifp(struct interface *ifp)
pim_ifp = ifp->info;
if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE) && pim_ifp && pim_ifp->pim_enable) {
pim = pim_ifp->pim;
- if (pim && pim->autorp && pim->autorp->do_discovery) {
+ if (pim && pim->autorp &&
+ (pim->autorp->do_discovery || pim->autorp->send_rp_discovery)) {
if (PIM_DEBUG_AUTORP)
zlog_debug("%s: Adding interface %s to AutoRP, joining AutoRP groups",
__func__, ifp->name);
@@ -1477,44 +1492,37 @@ void pim_autorp_rm_ifp(struct interface *ifp)
void pim_autorp_start_discovery(struct pim_instance *pim)
{
- struct interface *ifp;
struct pim_autorp *autorp = pim->autorp;
+ if (autorp->do_discovery)
+ return;
+
+ autorp->do_discovery = true;
+
/* Make sure the socket is open and ready */
if (!pim_autorp_socket_enable(autorp)) {
zlog_err("%s: AutoRP failed to open socket", __func__);
return;
}
- if (!autorp->do_discovery) {
- autorp->do_discovery = true;
- autorp_read_on(autorp);
-
- FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) {
- pim_autorp_add_ifp(ifp);
- }
+ autorp_read_on(autorp);
- if (PIM_DEBUG_AUTORP)
- zlog_debug("%s: AutoRP Discovery started", __func__);
- }
+ if (PIM_DEBUG_AUTORP)
+ zlog_debug("%s: AutoRP Discovery started", __func__);
}
void pim_autorp_stop_discovery(struct pim_instance *pim)
{
- struct interface *ifp;
struct pim_autorp *autorp = pim->autorp;
- if (autorp->do_discovery) {
- FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) {
- pim_autorp_rm_ifp(ifp);
- }
+ if (!autorp->do_discovery)
+ return;
- autorp->do_discovery = false;
- autorp_read_off(autorp);
+ autorp->do_discovery = false;
+ autorp_read_off(autorp);
- if (PIM_DEBUG_AUTORP)
- zlog_debug("%s: AutoRP Discovery stopped", __func__);
- }
+ if (PIM_DEBUG_AUTORP)
+ zlog_debug("%s: AutoRP Discovery stopped", __func__);
/* Close the socket if we need to */
if (pim_autorp_should_close(autorp) && !pim_autorp_socket_disable(autorp))
@@ -1549,7 +1557,10 @@ void pim_autorp_init(struct pim_instance *pim)
if (PIM_DEBUG_AUTORP)
zlog_debug("%s: AutoRP Initialized", __func__);
+}
+void pim_autorp_enable(struct pim_instance *pim)
+{
/* Start AutoRP discovery by default on startup */
pim_autorp_start_discovery(pim);
}
diff --git a/pimd/pim_autorp.h b/pimd/pim_autorp.h
index e4c6530109..88aebe5b7d 100644
--- a/pimd/pim_autorp.h
+++ b/pimd/pim_autorp.h
@@ -173,6 +173,7 @@ void pim_autorp_rm_ifp(struct interface *ifp);
void pim_autorp_start_discovery(struct pim_instance *pim);
void pim_autorp_stop_discovery(struct pim_instance *pim);
void pim_autorp_init(struct pim_instance *pim);
+void pim_autorp_enable(struct pim_instance *pim);
void pim_autorp_finish(struct pim_instance *pim);
int pim_autorp_config_write(struct pim_instance *pim, struct vty *vty);
void pim_autorp_show_autorp(struct vty *vty, struct pim_instance *pim, const char *component,
diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c
index 3408574cae..e0b157b8f6 100644
--- a/pimd/pim_iface.c
+++ b/pimd/pim_iface.c
@@ -1901,9 +1901,7 @@ static int pim_ifp_up(struct interface *ifp)
}
#if PIM_IPV == 4
- if (pim->autorp && pim->autorp->do_discovery && pim_ifp &&
- pim_ifp->pim_enable)
- pim_autorp_add_ifp(ifp);
+ pim_autorp_add_ifp(ifp);
#endif
pim_cand_addrs_changed();
@@ -2020,8 +2018,7 @@ void pim_pim_interface_delete(struct interface *ifp)
return;
#if PIM_IPV == 4
- if (pim_ifp->pim_enable)
- pim_autorp_rm_ifp(ifp);
+ pim_autorp_rm_ifp(ifp);
#endif
pim_ifp->pim_enable = false;
diff --git a/pimd/pim_instance.c b/pimd/pim_instance.c
index 3945c5923d..f64b02e44d 100644
--- a/pimd/pim_instance.c
+++ b/pimd/pim_instance.c
@@ -181,8 +181,15 @@ static int pim_vrf_enable(struct vrf *vrf)
zlog_debug("%s: for %s %u", __func__, vrf->name, vrf->vrf_id);
+ if (vrf_bind(vrf->vrf_id, pim->reg_sock, NULL) < 0)
+ zlog_warn("Failed to bind register socket to VRF %s", vrf->name);
+
pim_mroute_socket_enable(pim);
+#if PIM_IPV == 4
+ pim_autorp_enable(pim);
+#endif
+
FOR_ALL_INTERFACES (vrf, ifp) {
if (!ifp->info)
continue;
diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c
index 6c13e1324f..30daa3a929 100644
--- a/pimd/pim_mroute.c
+++ b/pimd/pim_mroute.c
@@ -876,17 +876,11 @@ int pim_mroute_socket_enable(struct pim_instance *pim)
pim->vrf->name);
#endif
-#ifdef SO_BINDTODEVICE
- if (pim->vrf->vrf_id != VRF_DEFAULT
- && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
- pim->vrf->name, strlen(pim->vrf->name))) {
- zlog_warn("Could not setsockopt SO_BINDTODEVICE: %s",
- safe_strerror(errno));
+ if (vrf_bind(pim->vrf->vrf_id, fd, NULL)) {
+ zlog_warn("Could not bind to vrf: %s", safe_strerror(errno));
close(fd);
return -3;
}
-#endif
-
}
pim->mroute_socket = fd;
diff --git a/ripd/rip_cli.c b/ripd/rip_cli.c
index 5712a0b825..0bafc6f342 100644
--- a/ripd/rip_cli.c
+++ b/ripd/rip_cli.c
@@ -681,9 +681,9 @@ DEFPY_YANG (ip_rip_split_horizon,
{
const char *value;
- if (no)
+ if (no && poisoned_reverse == NULL)
value = "disabled";
- else if (poisoned_reverse)
+ else if (poisoned_reverse && no == NULL)
value = "poison-reverse";
else
value = "simple";
diff --git a/tests/lib/northbound/test_oper_data.in b/tests/lib/northbound/test_oper_data.in
index 0053148953..94fcdc1e1c 100644
--- a/tests/lib/northbound/test_oper_data.in
+++ b/tests/lib/northbound/test_oper_data.in
@@ -2,4 +2,8 @@ show yang operational-data /frr-test-module:frr-test-module
show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[2]
show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[3]/interface
show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[10]
+show yang operational-data /frr-test-module:frr-test-module/c1value
+show yang operational-data /frr-test-module:frr-test-module/c2cont
+show yang operational-data /frr-test-module:frr-test-module/c2cont/
+show yang operational-data /frr-test-module:frr-test-module/c2cont/c2value
test rpc
diff --git a/tests/lib/northbound/test_oper_data.refout b/tests/lib/northbound/test_oper_data.refout
index 2536e0306b..57061d0371 100644
--- a/tests/lib/northbound/test_oper_data.refout
+++ b/tests/lib/northbound/test_oper_data.refout
@@ -174,6 +174,36 @@ test# show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name=
}
test# show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[10]
{}
+test# show yang operational-data /frr-test-module:frr-test-module/c1value
+{
+ "frr-test-module:frr-test-module": {
+ "c1value": 21
+ }
+}
+test# show yang operational-data /frr-test-module:frr-test-module/c2cont
+{
+ "frr-test-module:frr-test-module": {
+ "c2cont": {
+ "c2value": 2868969987
+ }
+ }
+}
+test# show yang operational-data /frr-test-module:frr-test-module/c2cont/
+{
+ "frr-test-module:frr-test-module": {
+ "c2cont": {
+ "c2value": 2868969987
+ }
+ }
+}
+test# show yang operational-data /frr-test-module:frr-test-module/c2cont/c2value
+{
+ "frr-test-module:frr-test-module": {
+ "c2cont": {
+ "c2value": 2868969987
+ }
+ }
+}
test# test rpc
vrf testname data testdata
test#
diff --git a/tests/topotests/bgp_l3vpn_hidden/__init__.py b/tests/topotests/bgp_l3vpn_hidden/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/__init__.py
diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/frr.conf b/tests/topotests/bgp_l3vpn_hidden/ce1/frr.conf
new file mode 100644
index 0000000000..95d84d5e6b
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/ce1/frr.conf
@@ -0,0 +1,52 @@
+debug bgp neighbor-events
+!
+ip prefix-list PLIST-LAN seq 10 permit 172.20.0.0/16 le 24
+!
+ipv6 prefix-list PLIST-LAN6 seq 10 permit 2001:db8::/32 le 64
+!
+route-map RMAP-LAN permit 10
+ match ip address prefix-list PLIST-LAN
+exit
+!
+route-map RMAP-LAN6 permit 10
+ match ipv6 address prefix-list PLIST-LAN6
+exit
+!
+interface eth-lan
+ ip address 172.20.1.1/24
+ ipv6 address 2001:db8:1::ff/64
+exit
+!
+interface eth-pe1
+ ip address 172.16.1.254/24
+ ipv6 address 3fff:1::ff/64
+exit
+!
+router bgp 65501
+ bgp router-id 172.16.1.254
+ no bgp ebgp-requires-policy
+ bgp bestpath compare-routerid
+ neighbor 172.16.1.1 remote-as external
+ neighbor 172.16.1.1 bfd profile BGP
+ neighbor 3fff:1::1 remote-as external
+ neighbor 3fff:1::1 bfd profile BGP
+ !
+ address-family ipv4 unicast
+ redistribute connected route-map RMAP-LAN
+ neighbor 172.16.1.1 next-hop-self
+ no neighbor 3fff:1::1 activate
+ exit-address-family
+!
+ address-family ipv6 unicast
+ redistribute connected route-map RMAP-LAN6
+ neighbor 3fff:1::1 activate
+ neighbor 3fff:1::1 next-hop-self
+ exit-address-family
+exit
+bfd
+ profile BGP
+ transmit-interval 2000
+ receive-interval 2000
+ exit
+ !
+exit
diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast.json
new file mode 100644
index 0000000000..e9741a2fbd
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast.json
@@ -0,0 +1,24 @@
+{
+ "vrfName": "default",
+ "routerId": "172.16.1.254",
+ "localAS": 65501,
+ "routes": {
+ "172.20.1.0/24": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "peerId": "(unspec)",
+ "path": "",
+ "nexthops": [
+ {
+ "ip": "0.0.0.0",
+ "hostname": "ce1",
+ "used": true
+ }
+ ]
+ }
+ ]
+ },
+ "totalRoutes": 1,
+ "totalPaths": 1
+}
diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast_step1.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast_step1.json
new file mode 120000
index 0000000000..0d02eacc65
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast_step1.json
@@ -0,0 +1 @@
+show_bgp_ipv4_unicast.json \ No newline at end of file
diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_vpn_step1.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_vpn_step1.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast.json
new file mode 100644
index 0000000000..1120d30edd
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast.json
@@ -0,0 +1,25 @@
+{
+ "vrfName": "default",
+ "routerId": "172.16.1.254",
+ "localAS": 65501,
+ "routes": {
+ "2001:db8:1::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "peerId": "(unspec)",
+ "path": "",
+ "nexthops": [
+ {
+ "ip": "::",
+ "hostname": "ce1",
+ "scope": "global",
+ "used": true
+ }
+ ]
+ }
+ ]
+ },
+ "totalRoutes": 1,
+ "totalPaths": 1
+}
diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast_step1.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast_step1.json
new file mode 120000
index 0000000000..94f8f5bba9
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast_step1.json
@@ -0,0 +1 @@
+show_bgp_ipv6_unicast.json \ No newline at end of file
diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_vpn_step1.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_vpn_step1.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_summary.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_summary.json
new file mode 100644
index 0000000000..5a0699ed9b
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_summary.json
@@ -0,0 +1,24 @@
+{
+ "default": {
+ "ipv4Unicast": {
+ "routerId": "172.16.1.254",
+ "peers": {
+ "172.16.1.1": {
+ "hostname": "pe1",
+ "state": "Established"
+ }
+ },
+ "totalPeers": 1
+ },
+ "ipv6Unicast": {
+ "routerId": "172.16.1.254",
+ "peers": {
+ "3fff:1::1": {
+ "hostname": "pe1",
+ "state": "Established"
+ }
+ },
+ "totalPeers": 1
+ }
+ }
+}
diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/frr.conf b/tests/topotests/bgp_l3vpn_hidden/pe1/frr.conf
new file mode 100644
index 0000000000..95a7262b32
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/pe1/frr.conf
@@ -0,0 +1,107 @@
+mpls label dynamic-block 1000 1048575
+!
+interface lo
+ ip address 192.168.0.1/32
+ ipv6 address 3fff::192:168:0:1/128
+!
+interface eth-rr1
+ ip address 10.0.1.1/24
+!
+interface eth-ce1
+ ip address 172.16.1.1/24
+ ipv6 address 3fff:1::1/64
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp ebgp-requires-policy
+ no bgp default ipv4-unicast
+ neighbor 192.168.0.101 remote-as 65000
+ neighbor 192.168.0.101 bfd profile BGP
+ neighbor 192.168.0.101 update-source 192.168.0.1
+ neighbor 3fff::192:168:0:101 remote-as 65000
+ neighbor 3fff::192:168:0:101 bfd profile BGP
+ neighbor 3fff::192:168:0:101 update-source 3fff::192:168:0:1
+
+!
+ address-family ipv4 unicast
+ no neighbor 192.168.0.101 activate
+ exit-address-family
+!
+ address-family ipv4 vpn
+ neighbor 192.168.0.101 activate
+ neighbor 192.168.0.101 soft-reconfiguration inbound
+ exit-address-family
+!
+ address-family ipv6 vpn
+ neighbor 3fff::192:168:0:101 activate
+ neighbor 3fff::192:168:0:101 soft-reconfiguration inbound
+ exit-address-family
+!
+router bgp 65000 vrf RED
+ bgp router-id 192.168.0.1
+ no bgp ebgp-requires-policy
+ bgp bestpath compare-routerid
+ neighbor 172.16.1.254 remote-as external
+ neighbor 172.16.1.254 bfd profile BGP
+ neighbor 3fff:1::ff remote-as external
+ neighbor 3fff:1::ff bfd profile BGP
+ !
+ address-family ipv4 unicast
+ label vpn export 100
+ rd vpn export 65000:100
+ rt vpn both 65000:100
+ export vpn
+ import vpn
+ neighbor 172.16.1.254 next-hop-self
+ no neighbor 3fff:1::ff activate
+ exit-address-family
+!
+ address-family ipv6 unicast
+ label vpn export 200
+ rd vpn export 65000:100
+ rt vpn both 65000:100
+ export vpn
+ import vpn
+ neighbor 3fff:1::ff activate
+ neighbor 3fff:1::ff next-hop-self
+ exit-address-family
+exit
+!
+interface lo
+ ip router isis 1
+ isis hello-interval 2
+ ipv6 router isis 1
+!
+interface eth-rr1
+ ip router isis 1
+ isis hello-interval 2
+ ipv6 router isis 1
+!
+router isis 1
+ lsp-gen-interval 2
+ net 10.0000.0000.0000.0000.0000.0000.0000.0000.0001.00
+ metric-style wide
+ exit
+!
+mpls ldp
+ router-id 192.168.0.1
+ !
+ address-family ipv4
+ discovery transport-address 192.168.0.1
+ !
+ interface eth-rr1
+ !
+ address-family ipv6
+ discovery transport-address 3fff::192:168:0:1
+ !
+ interface eth-rr1
+ !
+ !
+!
+bfd
+ profile BGP
+ transmit-interval 2000
+ receive-interval 2000
+ exit
+ !
+exit
diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn.json b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn.json
new file mode 100644
index 0000000000..d21dd89291
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn.json
@@ -0,0 +1,29 @@
+{
+ "vrfName": "default",
+ "routerId": "192.168.0.1",
+ "localAS": 65000,
+ "routes": {
+ "routeDistinguishers": {
+ "65000:100": {
+ "172.20.1.0/24": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "peerId": "(unspec)",
+ "path": "65501",
+ "nhVrfName": "RED",
+ "nexthops": [
+ {
+ "ip": "172.16.1.254",
+ "hostname": "pe1",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "totalRoutes": 1,
+ "totalPaths": 1
+}
diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn_step1.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn_step1.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn.json b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn.json
new file mode 100644
index 0000000000..b42656f44f
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn.json
@@ -0,0 +1,29 @@
+{
+ "vrfName": "default",
+ "routerId": "192.168.0.1",
+ "localAS": 65000,
+ "routes": {
+ "routeDistinguishers": {
+ "65000:100": {
+ "2001:db8:1::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "peerId": "(unspec)",
+ "path": "65501",
+ "nhVrfName": "RED",
+ "nexthops": [
+ {
+ "ip": "3fff:1::ff",
+ "hostname": "pe1",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "totalRoutes": 1,
+ "totalPaths": 1
+}
diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn_step1.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn_step1.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_summary.json b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_summary.json
new file mode 100644
index 0000000000..b414d2e4ae
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_summary.json
@@ -0,0 +1,46 @@
+{
+ "default": {
+ "ipv4Vpn": {
+ "routerId": "192.168.0.1",
+ "peers": {
+ "192.168.0.101": {
+ "hostname": "rr1",
+ "state": "Established"
+ }
+ },
+ "totalPeers": 1
+ },
+ "ipv6Vpn": {
+ "routerId": "192.168.0.1",
+ "peers": {
+ "3fff::192:168:0:101": {
+ "hostname": "rr1",
+ "state": "Established"
+ }
+ },
+ "totalPeers": 1
+ }
+ },
+ "RED": {
+ "ipv4Unicast": {
+ "routerId": "192.168.0.1",
+ "peers": {
+ "172.16.1.254": {
+ "hostname": "ce1",
+ "state": "Established"
+ }
+ },
+ "totalPeers": 1
+ },
+ "ipv6Unicast": {
+ "routerId": "192.168.0.1",
+ "peers": {
+ "3fff:1::ff": {
+ "hostname": "ce1",
+ "state": "Established"
+ }
+ },
+ "totalPeers": 1
+ }
+ }
+}
diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/frr.conf b/tests/topotests/bgp_l3vpn_hidden/rr1/frr.conf
new file mode 100644
index 0000000000..b25a7a336b
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/rr1/frr.conf
@@ -0,0 +1,79 @@
+mpls label dynamic-block 1000 1048575
+!
+interface lo
+ ip address 192.168.0.101/32
+ ipv6 address 3fff::192:168:0:101/128
+!
+interface eth-pe1
+ ip address 10.0.1.101/24
+!
+router bgp 65000
+ bgp router-id 192.168.0.101
+ bgp cluster-id 192.168.0.101
+ bgp log-neighbor-changes
+ no bgp default ipv4-unicast
+ neighbor PE peer-group
+ neighbor PE remote-as 65000
+ neighbor PE bfd profile BGP
+ neighbor PE update-source 192.168.0.101
+ neighbor PE6 peer-group
+ neighbor PE6 remote-as 65000
+ neighbor PE6 bfd profile BGP
+ neighbor PE6 update-source 3fff::192:168:0:101
+ neighbor 192.168.0.1 peer-group PE
+ neighbor 3fff::192:168:0:1 peer-group PE6
+!
+ address-family ipv4 unicast
+ no neighbor PE activate
+ exit-address-family
+!
+ address-family ipv4 vpn
+ neighbor PE activate
+ neighbor PE route-reflector-client
+ neighbor PE soft-reconfiguration inbound
+ exit-address-family
+!
+ address-family ipv6 vpn
+ neighbor PE6 activate
+ neighbor PE6 route-reflector-client
+ neighbor PE6 soft-reconfiguration inbound
+ exit-address-family
+!
+!
+interface lo
+ ip router isis 1
+ isis hello-interval 2
+ ipv6 router isis 1
+!
+interface eth-pe1
+ ip router isis 1
+ isis hello-interval 2
+ ipv6 router isis 1
+!
+!
+router isis 1
+ lsp-gen-interval 2
+ net 10.0000.0000.0000.0000.0000.0000.0000.0000.0100.00
+ metric-style wide
+ exit
+!
+mpls ldp
+ router-id 192.168.0.101
+ !
+ address-family ipv4
+ discovery transport-address 192.168.0.101
+ !
+ interface eth-pe1
+ !
+ address-family ipv6
+ discovery transport-address 3fff::192:168:0:101
+ !
+ interface eth-pe1
+!
+bfd
+ profile BGP
+ transmit-interval 2000
+ receive-interval 2000
+ exit
+ !
+exit
diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn.json b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn.json
new file mode 100644
index 0000000000..3fd451640c
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn.json
@@ -0,0 +1,28 @@
+{
+ "vrfName": "default",
+ "routerId": "192.168.0.101",
+ "localAS": 65000,
+ "routes": {
+ "routeDistinguishers": {
+ "65000:100": {
+ "172.20.1.0/24": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "peerId": "192.168.0.1",
+ "path": "65501",
+ "nexthops": [
+ {
+ "ip": "192.168.0.1",
+ "hostname": "pe1",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "totalRoutes": 1,
+ "totalPaths": 1
+}
diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn_step1.json
new file mode 100644
index 0000000000..88d1ac5e6e
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn_step1.json
@@ -0,0 +1,12 @@
+{
+ "vrfName": "default",
+ "routerId": "192.168.0.101",
+ "localAS": 65000,
+ "routes": {
+ "routeDistinguishers": {
+ "65000:100": {}
+ }
+ },
+ "totalRoutes": 0,
+ "totalPaths": 0
+}
diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn.json b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn.json
new file mode 100644
index 0000000000..c3ecd71c46
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn.json
@@ -0,0 +1,28 @@
+{
+ "vrfName": "default",
+ "routerId": "192.168.0.101",
+ "localAS": 65000,
+ "routes": {
+ "routeDistinguishers": {
+ "65000:100": {
+ "2001:db8:1::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "peerId": "3fff::192:168:0:1",
+ "path": "65501",
+ "nexthops": [
+ {
+ "ip": "3fff::192:168:0:1",
+ "hostname": "pe1",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "totalRoutes": 1,
+ "totalPaths": 1
+}
diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn_step1.json
new file mode 100644
index 0000000000..88d1ac5e6e
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn_step1.json
@@ -0,0 +1,12 @@
+{
+ "vrfName": "default",
+ "routerId": "192.168.0.101",
+ "localAS": 65000,
+ "routes": {
+ "routeDistinguishers": {
+ "65000:100": {}
+ }
+ },
+ "totalRoutes": 0,
+ "totalPaths": 0
+}
diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_summary.json b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_summary.json
new file mode 100644
index 0000000000..8d6c15526e
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_summary.json
@@ -0,0 +1,24 @@
+{
+ "default": {
+ "ipv4Vpn": {
+ "routerId": "192.168.0.101",
+ "peers": {
+ "192.168.0.1": {
+ "hostname": "pe1",
+ "state": "Established"
+ }
+ },
+ "totalPeers": 1
+ },
+ "ipv6Vpn": {
+ "routerId": "192.168.0.101",
+ "peers": {
+ "3fff::192:168:0:1": {
+ "hostname": "pe1",
+ "state": "Established"
+ }
+ },
+ "totalPeers": 1
+ }
+ }
+}
diff --git a/tests/topotests/bgp_l3vpn_hidden/test_bgp_l3vpn_hidden.py b/tests/topotests/bgp_l3vpn_hidden/test_bgp_l3vpn_hidden.py
new file mode 100644
index 0000000000..04a8482518
--- /dev/null
+++ b/tests/topotests/bgp_l3vpn_hidden/test_bgp_l3vpn_hidden.py
@@ -0,0 +1,287 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+
+"""
+Test BGP hidden
+See https://github.com/FRRouting/frr/commit/4d0e7a49cf8d4311a485281fa50bbff6ee8ca6cc
+"""
+
+import os
+import sys
+import re
+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.topolog import logger
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.common_config import step
+
+
+pytestmark = [pytest.mark.bgpd, pytest.mark.bfdd, pytest.mark.isisd, pytest.mark.ldpd]
+
+
+def build_topo(tgen):
+ """
+ +---+ +---+ +---+
+ |ce1|---|pe1|---|rr1|
+ +---+ +---+ +---+"""
+
+ def connect_routers(tgen, left, right):
+ for rname in [left, right]:
+ if rname not in tgen.routers().keys():
+ tgen.add_router(rname)
+
+ switch = tgen.add_switch("s-{}-{}".format(left, right))
+ switch.add_link(tgen.gears[left], nodeif="eth-{}".format(right))
+ switch.add_link(tgen.gears[right], nodeif="eth-{}".format(left))
+ if "ce" not in right and "ce" not in left:
+ tgen.gears[left].cmd(f"sysctl net.mpls.conf.eth-{right}.input=1")
+ tgen.gears[right].cmd(f"sysctl net.mpls.conf.eth-{left}.input=1")
+
+ def connect_switchs(tgen, rname, switch):
+ if rname not in tgen.routers().keys():
+ tgen.add_router(rname)
+
+ switch.add_link(tgen.gears[rname], nodeif="eth-{}".format(switch.name))
+
+ def connect_lan(tgen, rname):
+ if rname not in tgen.routers().keys():
+ tgen.add_router(rname)
+
+ # Extra LAN interfaces. Not used for communication with hosts, just to
+ # hold an address we use to inject routes
+ switch = tgen.add_switch("s-{}".format(rname))
+ switch.add_link(tgen.gears[rname], nodeif="eth-lan")
+
+ # directly connected without switch routers
+ connect_routers(tgen, "rr1", "pe1")
+ connect_routers(tgen, "pe1", "ce1")
+ connect_lan(tgen, "ce1")
+
+
+def setup_module(mod):
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ pe1 = tgen.gears["pe1"]
+ pe1.cmd(
+ f"""
+ip link add RED type vrf table 100
+ip link set RED up
+ip link set eth-ce1 master RED
+"""
+ )
+
+ router_list = tgen.routers()
+
+ for _, (rname, router) in enumerate(router_list.items(), 1):
+ router.load_frr_config(
+ os.path.join(CWD, "{}/frr.conf".format(rname)),
+ [
+ (TopoRouter.RD_ZEBRA, None),
+ (TopoRouter.RD_MGMTD, None),
+ (TopoRouter.RD_BFD, None),
+ (TopoRouter.RD_LDP, None),
+ (TopoRouter.RD_ISIS, None),
+ (TopoRouter.RD_BGP, None),
+ ],
+ )
+
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def check_bgp_convergence(step=None):
+ """
+ out was generated using
+
+ FRRGIT=/path/git/frr
+
+ vtysh -c 'show bgp vrf all summary json' | jq . | egrep -v 'ersion|idType|connections|peerState|pfx|outq|inq|msg|As|rib|Count|Memory|Uptime|vrf|\"as|failedPeers|displayedPeers|dynamicPeers' | awk '/ "bestPath": {/ {c=3; next} c-- > 0 {next} 1' | sed -E 's|"totalPeers": (.+),|"totalPeers": \1|g;s|"Established",|"Established"|g' | jq . >$FRRGIT/tests/topotests/bgp_l3vpn_hidden/$HOSTNAME/show_bgp_summary.json
+
+ vtysh -c 'show bgp ipv4 vpn json' | jq . | egrep -v 'selectionReason|pathFrom|prefix|locPrf|ersion|weight|origin|vrfId|afi|defaultLocPrf|network|nhVrfId|announceNexthopSelf|metric|multipath|linkLocalOnly|length' | jq . >$FRRGIT/tests/topotests/bgp_l3vpn_hidden/$HOSTNAME/show_bgp_ipv4_vpn_step1.json
+ vtysh -c 'show bgp ipv6 vpn json' | jq . | egrep -v 'selectionReason|pathFrom|prefix|locPrf|ersion|weight|origin|vrfId|afi|defaultLocPrf|network|fe80|nhVrfId|announceNexthopSelf|metric|multipath|linkLocalOnly|length' | jq . >$FRRGIT/tests/topotests/bgp_l3vpn_hidden/$HOSTNAME/show_bgp_ipv6_vpn_step1.json
+
+ vtysh -c 'show bgp ipv4 unicast json' | jq . | egrep -v 'selectionReason|pathFrom|prefix|locPrf|ersion|weight|origin|vrfId|afi|defaultLocPrf|network|nhVrfId|announceNexthopSelf|metric|multipath|linkLocalOnly|length' | jq . >$FRRGIT/tests/topotests/bgp_l3vpn_hidden/$HOSTNAME/show_bgp_ipv4_unicast.json
+ vtysh -c 'show bgp ipv6 unicast json' | jq . | egrep -v 'selectionReason|pathFrom|prefix|locPrf|ersion|weight|origin|vrfId|afi|defaultLocPrf|network|fe80|nhVrfId|announceNexthopSelf|metric|multipath|linkLocalOnly|length' | jq . >$FRRGIT/tests/topotests/bgp_l3vpn_hidden/$HOSTNAME/show_bgp_ipv6_unicast.json
+ """
+ tgen = get_topogen()
+
+ logger.info("waiting for bgp convergence")
+
+ step_suffix = f"_step{step}" if step else ""
+
+ if not step:
+ logger.info("Check BGP summary")
+ for rname, router in tgen.routers().items():
+ reffile = os.path.join(CWD, f"{rname}/show_bgp_summary.json")
+ expected = json.loads(open(reffile).read())
+ cmd = "show bgp vrf all summary json"
+ test_func = functools.partial(
+ topotest.router_json_cmp, router, cmd, expected
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=60, wait=1)
+ assertmsg = f"BGP did not converge. Error on {rname} {cmd}"
+ assert res is None, assertmsg
+
+ logger.info("Check BGP IPv4/6 unicast/VPN table")
+ for rname, router in tgen.routers().items():
+ for ipv in [4, 6]:
+ logger.info(f"Check BGP IPv4/6 unicast/VPN table: {rname} IPv{ipv}")
+ safi = "unicast" if "ce" in rname else "vpn"
+ reffile = os.path.join(
+ CWD, f"{rname}/show_bgp_ipv{ipv}_{safi}{step_suffix}.json"
+ )
+ expected = json.loads(open(reffile).read())
+ exact = not expected # exact match if json is void (ie. {})
+ cmd = f"show bgp ipv{ipv} {safi} json"
+ test_func = functools.partial(
+ topotest.router_json_cmp,
+ router,
+ cmd,
+ expected,
+ exact=exact,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=120, wait=1)
+ assertmsg = f"BGP did not converge. Error on {rname} {cmd}"
+ assert res is None, assertmsg
+
+
+def configure_bgp(vrf=None, router_name="all", activate=False):
+ tgen = get_topogen()
+
+ vrf_suffix = f" vrf {vrf}" if vrf else ""
+ as_pattern = re.compile(rf"^router bgp (\d+){vrf_suffix}$")
+
+ for rname, router in tgen.routers().items():
+ if router_name != "all" and router_name != rname:
+ continue
+
+ if "ce" in rname:
+ continue
+
+ as_number = ""
+ cmds = []
+ router_bgp = False
+ with open(os.path.join(CWD, f"{rname}/frr.conf"), "r") as f:
+ for line in f:
+ line = line.strip()
+ if "router bgp" in line:
+ match = as_pattern.match(line)
+ if match:
+ as_number = match.group(1)
+ router_bgp = True
+ continue
+ if router_bgp:
+ # If we already hit "router bgp <as_number>" once,
+ # and see another "router bgp" line, break.
+ break
+ if not router_bgp:
+ # Only capture lines after we've matched "router bgp"
+ continue
+ cmds.append(line)
+
+ cfg = "configure terminal\n"
+ if activate:
+ cfg += f"router bgp {as_number}{vrf_suffix}\n"
+ for cmd in cmds:
+ cfg += f"{cmd}\n"
+ else:
+ cfg += f"no router bgp {as_number}{vrf_suffix}\n"
+
+ router.vtysh_cmd(cfg)
+
+
+def test_bgp_convergence():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ check_bgp_convergence()
+
+
+def test_bgp_l3vpn_hidden_step1():
+ """
+ Remove pe1 router bgp blocks
+ The Default BGP instance becomes hidden
+ """
+
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ for vrf in ["RED", None]:
+ configure_bgp(router_name="pe1", vrf=vrf, activate=False)
+
+ check_bgp_convergence(step=1)
+
+
+def test_bgp_l3vpn_hidden_step2():
+ """
+ Restore pe1 router bgp blocks
+ """
+
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ for vrf in [None, "RED"]:
+ configure_bgp(router_name="pe1", vrf=vrf, activate=True)
+
+ # identical to the intitial step
+ check_bgp_convergence(step=None)
+
+
+def test_bgp_l3vpn_hidden_step3():
+ """
+ Remove pe1 router bgp blocks
+ The Default BGP instance becomes hidden
+ """
+
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ for vrf in ["RED", None]:
+ configure_bgp(router_name="pe1", vrf=vrf, activate=False)
+
+ # identical to the intitial step 1
+ check_bgp_convergence(step=1)
+
+
+def test_bgp_l3vpn_hidden_step4():
+ """
+ Restore pe1 router bgp blocks
+ Reconfigure VRF block first
+ """
+
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ for vrf in [None, "RED"]:
+ configure_bgp(router_name="pe1", vrf=vrf, activate=True)
+
+ # identical to the intitial step
+ check_bgp_convergence(step=None)
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf b/tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf
index 397f7938d2..8363e2bc99 100644
--- a/tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf
+++ b/tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf
@@ -1,12 +1,5 @@
hostname r1
-router bgp 99
- no bgp ebgp-requires-policy
- address-family ipv4 unicast
- redistribute connected
- import vrf DONNA
- !
-!
router bgp 99 vrf DONNA
no bgp ebgp-requires-policy
address-family ipv4 unicast
@@ -31,3 +24,10 @@ router bgp 99 vrf ZITA
network 172.16.101.0/24
!
!
+router bgp 99
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ import vrf DONNA
+ !
+!
diff --git a/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py b/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py
index 3bc36862bf..7a407761d3 100644
--- a/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py
+++ b/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py
@@ -64,6 +64,16 @@ def teardown_module(mod):
tgen.stop_topology()
+def test_router_bgp_as_pretty():
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ output = tgen.gears["r1"].vtysh_cmd("show run")
+ assert "router bgp 99\n" in output, "router bgp 99 not found in show run"
+
+
def test_vrf_route_leak_donna():
logger.info("Ensure that routes are leaked back and forth")
tgen = get_topogen()
diff --git a/tests/topotests/lib/fe_client.py b/tests/topotests/lib/fe_client.py
index 078df8cb33..4868c1018e 100755
--- a/tests/topotests/lib/fe_client.py
+++ b/tests/topotests/lib/fe_client.py
@@ -8,6 +8,7 @@
#
# noqa: E501
#
+"""A MGMTD front-end client."""
import argparse
import logging
import os
@@ -34,7 +35,7 @@ except Exception as error:
try:
sys.path[0:0] = "."
- import mgmt_pb2
+ import mgmt_pb2 # pylint: disable=E0401
except Exception as error:
logging.error("can't import proto definition modules %s", error)
raise
@@ -124,16 +125,22 @@ MSG_FORMAT_LYB = 3
def cstr(mdata):
+ """Convert a null-term byte array into a string, excluding the null terminator."""
assert mdata[-1] == 0
return mdata[:-1]
class FEClientError(Exception):
+ """Base class for frontend client errors."""
+
pass
class PBMessageError(FEClientError):
+ """Exception for errors related to protobuf messages."""
+
def __init__(self, msg, errstr):
+ """Initialize PBMessageError with message and error string."""
self.msg = msg
# self.sess_id = mhdr[HDR_FIELD_SESS_ID]
# self.req_id = mhdr[HDR_FIELD_REQ_ID]
@@ -143,7 +150,10 @@ class PBMessageError(FEClientError):
class NativeMessageError(FEClientError):
+ """Exception for errors related to native messages."""
+
def __init__(self, mhdr, mfixed, mdata):
+ """Initialize NativeMessageError with message header, fixed fields, and data."""
self.mhdr = mhdr
self.sess_id = mhdr[HDR_FIELD_SESS_ID]
self.req_id = mhdr[HDR_FIELD_REQ_ID]
@@ -173,6 +183,7 @@ def recv_wait(sock, size):
def recv_msg(sock):
+ """Receive a message from the socket, ensuring it has a valid marker."""
marker = recv_wait(sock, 4)
assert marker in (MGMT_MSG_MARKER_PROTOBUF, MGMT_MSG_MARKER_NATIVE)
@@ -197,15 +208,18 @@ class Session:
client_id = 1
def __init__(self, sock, use_protobuf):
+ """Initialize a session with the mgmtd server."""
self.sock = sock
self.next_req_id = 1
if use_protobuf:
+ # Register the client
req = mgmt_pb2.FeMessage()
req.register_req.client_name = "test-client"
self.send_pb_msg(req)
logging.debug("Sent FeRegisterReq: %s", req)
+ # Create a session
req = mgmt_pb2.FeMessage()
req.session_req.create = 1
req.session_req.client_conn_id = Session.client_id
@@ -219,11 +233,11 @@ class Session:
assert reply.session_reply.success
self.sess_id = reply.session_reply.session_id
else:
+ # Establish a native session
self.sess_id = 0
mdata, _ = self.get_native_msg_header(MSG_CODE_SESSION_REQ)
mdata += struct.pack(MSG_SESSION_REQ_FMT)
mdata += "test-client".encode("utf-8") + b"\x00"
-
self.send_native_msg(mdata)
logging.debug("Sent native SESSION-REQ")
@@ -236,6 +250,7 @@ class Session:
self.sess_id = mhdr[HDR_FIELD_SESS_ID]
def close(self, clean=True):
+ """Close the session."""
if clean:
req = mgmt_pb2.FeMessage()
req.session_req.create = 0
@@ -245,6 +260,7 @@ class Session:
self.sock = None
def get_next_req_id(self):
+ """Generate the next request ID for a new session."""
req_id = self.next_req_id
self.next_req_id += 1
return req_id
@@ -301,10 +317,11 @@ class Session:
return mhdr, mfixed, mdata
def send_native_msg(self, mdata):
- """Send a native message."""
+ """Send a native message to the mgmtd server."""
return send_msg(self.sock, MGMT_MSG_MARKER_NATIVE, mdata)
def get_native_msg_header(self, msg_code):
+ """Generate a native message header for a given message code."""
req_id = self.get_next_req_id()
hdata = struct.pack(MSG_HDR_FMT, msg_code, 0, self.sess_id, req_id)
return hdata, req_id
@@ -314,6 +331,19 @@ class Session:
# -----------------------
def lock(self, lock=True, ds_id=mgmt_pb2.CANDIDATE_DS):
+ """Lock or unlock a datastore.
+
+ Args:
+ lock (bool, optional): Whether to lock (True) or unlock (False) the
+ datastore. Defaults to True.
+ ds_id (int, optional): The datastore ID. Defaults to mgmt_pb2.CANDIDATE_DS.
+
+ Returns:
+ None
+
+ Raises:
+ AssertionError: If the lock request fails.
+ """
req = mgmt_pb2.FeMessage()
req.lockds_req.session_id = self.sess_id
req.lockds_req.req_id = self.get_next_req_id()
@@ -327,7 +357,20 @@ class Session:
assert reply.lockds_reply.success
def get_data(self, query, data=True, config=False):
- # Create the message
+ """Retrieve data from the mgmtd server based on an XPath query.
+
+ Args:
+ query (str): The XPath query string.
+ data (bool, optional): Whether to retrieve state data. Defaults to True.
+ config (bool, optional): Whether to retrieve configuration data.
+ Defaults to False.
+
+ Returns:
+ str: The retrieved data in JSON format.
+
+ Raises:
+ AssertionError: If the response data is not properly formatted.
+ """
mdata, _ = self.get_native_msg_header(MSG_CODE_GET_DATA)
flags = GET_DATA_FLAG_STATE if data else 0
flags |= GET_DATA_FLAG_CONFIG if config else 0
@@ -345,7 +388,12 @@ class Session:
return result
def add_notify_select(self, replace, notif_xpaths):
- # Create the message
+ """Send a request to add notification subscriptions to the given XPaths.
+
+ Args:
+ replace (bool): Whether to replace existing notification subscriptions.
+ notif_xpaths (list of str): List of XPaths to subscribe to notifications on.
+ """
mdata, _ = self.get_native_msg_header(MSG_CODE_NOTIFY_SELECT)
mdata += struct.pack(MSG_NOTIFY_SELECT_FMT, replace)
@@ -356,6 +404,18 @@ class Session:
logging.debug("Sent NOTIFY_SELECT")
def recv_notify(self, xpaths=None):
+ """Receive a notification message, optionally setting up XPath filters first.
+
+ Args:
+ xpaths (list of str, optional): List of XPaths to filter notifications.
+
+ Returns:
+ tuple: (result_type, operation, xpath, message data)
+
+ Raises:
+ TimeoutError: If no notification is received within the timeout period.
+ Exception: If a non-notification message is received.
+ """
if xpaths:
self.add_notify_select(True, xpaths)
@@ -379,6 +439,7 @@ class Session:
def __parse_args():
+ """Parse command-line arguments for the mgmtd client."""
MPATH = "/var/run/frr/mgmtd_fe.sock"
parser = argparse.ArgumentParser()
parser.add_argument(
@@ -416,6 +477,14 @@ def __parse_args():
def __server_connect(spath):
+ """Establish a connection to the mgmtd server over a Unix socket.
+
+ Args:
+ spath (str): Path to the Unix domain socket.
+
+ Returns:
+ socket: A connected Unix socket.
+ """
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
logging.debug("Connecting to server on %s", spath)
while ec := sock.connect_ex(str(spath)):
@@ -428,6 +497,7 @@ def __server_connect(spath):
def __main():
+ """Process client commands and handle queries or notifications."""
args = __parse_args()
sock = __server_connect(Path(args.server))
sess = Session(sock, use_protobuf=args.use_protobuf)
@@ -473,6 +543,7 @@ def __main():
def main():
+ """Entry point for the mgmtd client application."""
try:
__main()
except KeyboardInterrupt:
diff --git a/tests/topotests/pim_autorp/test_pim_autorp.py b/tests/topotests/pim_autorp/test_pim_autorp.py
index 61cf8ebbc5..5edf1db6af 100644
--- a/tests/topotests/pim_autorp/test_pim_autorp.py
+++ b/tests/topotests/pim_autorp/test_pim_autorp.py
@@ -158,6 +158,103 @@ def test_pim_autorp_init(request):
)
+def test_pim_autorp_disable_enable(request):
+ "Test PIM AutoRP disable and re-enable works properly"
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ step("Ensure AutoRP groups are joined on all routers")
+ for rtr in ["r1", "r2", "r3", "r4"]:
+ expected = {
+ f"{rtr}-eth0": {
+ "name": f"{rtr}-eth0",
+ "224.0.1.39": "*",
+ "224.0.1.40": "*",
+ },
+ f"{rtr}-eth1": {
+ "name": f"{rtr}-eth1",
+ "224.0.1.39": "*",
+ "224.0.1.40": "*",
+ },
+ }
+
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears[rtr],
+ "show ip igmp sources json",
+ expected,
+ )
+ _, result = topotest.run_and_expect(test_func, None)
+ assert result is None, "{} does not have correct autorp groups joined".format(
+ rtr
+ )
+
+ step("Disable AutoRP on all routers")
+ for rtr in ["r1", "r2", "r3", "r4"]:
+ tgen.routers()[rtr].vtysh_cmd(
+ """
+ conf
+ router pim
+ no autorp discovery
+ """
+ )
+
+ step("Ensure AutoRP groups are no longer joined on all routers")
+ for rtr in ["r1", "r2", "r3", "r4"]:
+ expected = {f"{rtr}-eth0": None, f"{rtr}-eth1": None}
+
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears[rtr],
+ "show ip igmp sources json",
+ expected,
+ )
+ _, result = topotest.run_and_expect(test_func, None)
+ assert result is None, "{} does not have correct autorp groups joined".format(
+ rtr
+ )
+
+ step("Re-enable AutoRP on all routers")
+ for rtr in ["r1", "r2", "r3", "r4"]:
+ tgen.routers()[rtr].vtysh_cmd(
+ """
+ conf
+ router pim
+ autorp discovery
+ """
+ )
+
+ step("Ensure AutoRP groups are re-joined on all routers")
+ for rtr in ["r1", "r2", "r3", "r4"]:
+ expected = {
+ f"{rtr}-eth0": {
+ "name": f"{rtr}-eth0",
+ "224.0.1.39": "*",
+ "224.0.1.40": "*",
+ },
+ f"{rtr}-eth1": {
+ "name": f"{rtr}-eth1",
+ "224.0.1.39": "*",
+ "224.0.1.40": "*",
+ },
+ }
+
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears[rtr],
+ "show ip igmp sources json",
+ expected,
+ )
+ _, result = topotest.run_and_expect(test_func, None)
+ assert result is None, "{} does not have correct autorp groups joined".format(
+ rtr
+ )
+
+
def test_pim_autorp_no_mapping_agent_rp(request):
"Test PIM AutoRP candidate with no mapping agent"
tgen = get_topogen()
diff --git a/tests/topotests/zebra_operational_data/r1/frr.conf b/tests/topotests/zebra_operational_data/r1/frr.conf
new file mode 100644
index 0000000000..72a67bf020
--- /dev/null
+++ b/tests/topotests/zebra_operational_data/r1/frr.conf
@@ -0,0 +1,41 @@
+log timestamp precision 6
+log file frr.log
+
+no debug memstats-at-exit
+
+debug northbound notifications
+debug northbound libyang
+debug northbound events
+debug northbound callbacks
+
+debug mgmt backend datastore frontend transaction
+debug mgmt client frontend
+debug mgmt client backend
+
+interface r1-eth0
+ ip address 1.1.1.1/24
+ ipv6 address 2001:1111::1/64
+exit
+
+interface r1-eth1
+ ip address 2.2.2.1/24
+ ipv6 address 2002:2222::1/64
+exit
+
+interface r1-eth2 vrf red
+ ip address 3.3.3.1/24
+ ipv6 address 2003:333::1/64
+exit
+
+interface r1-eth3 vrf red
+ ip address 4.4.4.1/24
+ ipv6 address 2004:4444::1/64
+exit
+
+ip route 11.0.0.0/8 Null0
+ip route 11.11.11.11/32 1.1.1.2
+ip route 12.12.12.12/32 2.2.2.2
+
+ip route 13.0.0.0/8 Null0 vrf red
+ip route 13.13.13.13/32 3.3.3.2 vrf red
+ip route 14.14.14.14/32 4.4.4.2 vrf red \ No newline at end of file
diff --git a/tests/topotests/zebra_operational_data/test_zebra_operational.py b/tests/topotests/zebra_operational_data/test_zebra_operational.py
new file mode 100644
index 0000000000..c4ab5be973
--- /dev/null
+++ b/tests/topotests/zebra_operational_data/test_zebra_operational.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2025 Nvidia Inc.
+# Donald Sharp
+#
+"""
+Test zebra operational values
+"""
+
+import pytest
+import json
+from lib.topogen import Topogen
+from lib.topolog import logger
+
+
+pytestmark = [pytest.mark.mgmtd]
+
+
+@pytest.fixture(scope="module")
+def tgen(request):
+ "Setup/Teardown the environment and provide tgen argument to tests"
+
+ topodef = {"s1": ("r1",), "s2": ("r1",), "s3": ("r1",), "s4": ("r1",)}
+
+ tgen = Topogen(topodef, request.module.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+ for rname, router in router_list.items():
+ # Setup VRF red
+ router.net.add_l3vrf("red", 10)
+ router.net.add_loop("lo-red")
+ router.net.attach_iface_to_l3vrf("lo-red", "red")
+ router.net.attach_iface_to_l3vrf(rname + "-eth2", "red")
+ router.net.attach_iface_to_l3vrf(rname + "-eth3", "red")
+ router.load_frr_config("frr.conf")
+
+ tgen.start_router()
+ yield tgen
+ tgen.stop_topology()
+
+
+def test_zebra_operationalr(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+
+ output = json.loads(r1.vtysh_cmd("show mgmt get-data /frr-zebra:zebra"))
+
+ logger.info("Output")
+ logger.info(output)
+
+ logger.info("Ensuring that the max-multipath value is returned")
+ assert "max-multipath" in output["frr-zebra:zebra"].keys()
+
+
+if __name__ == "__main__":
+ # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli
+ # retval = pytest.main(["-s", "--tb=no"])
+ retval = pytest.main(["-s"])
+ sys.exit(retval)
diff --git a/yang/frr-zebra.yang b/yang/frr-zebra.yang
index a3c066c56c..5f1b711b97 100644
--- a/yang/frr-zebra.yang
+++ b/yang/frr-zebra.yang
@@ -2842,6 +2842,17 @@ module frr-zebra {
container zebra {
description
"Data model for the Zebra daemon.";
+ leaf max-multipath {
+ config false;
+ type uint16 {
+ range "1..65535";
+ }
+ description
+ "The maximum number of nexthops for a route. At this point it
+ is unlikely that a multipath number will ever get larger then
+ 1024 but to allow for future expansions, the yang returns a
+ 16 bit number";
+ }
leaf ip-forwarding {
type boolean;
description
diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c
index 9c252cc63c..4344a8d79a 100644
--- a/zebra/zebra_dplane.c
+++ b/zebra/zebra_dplane.c
@@ -6210,8 +6210,7 @@ int dplane_show_provs_helper(struct vty *vty, bool detailed)
in_max = atomic_load_explicit(&prov->dp_in_max,
memory_order_relaxed);
- out = atomic_load_explicit(&prov->dp_out_counter,
- memory_order_relaxed);
+ out = dplane_provider_out_ctx_queue_len(prov);
out_max = atomic_load_explicit(&prov->dp_out_max,
memory_order_relaxed);
diff --git a/zebra/zebra_nb.c b/zebra/zebra_nb.c
index 6b41993a95..81fe2ab6a0 100644
--- a/zebra/zebra_nb.c
+++ b/zebra/zebra_nb.c
@@ -26,6 +26,12 @@ const struct frr_yang_module_info frr_zebra_info = {
.features = features,
.nodes = {
{
+ .xpath = "/frr-zebra:zebra/max-multipath",
+ .cbs = {
+ .get_elem = zebra_max_multipath_get_elem,
+ }
+ },
+ {
.xpath = "/frr-zebra:zebra/ip-forwarding",
.cbs = {
.modify = zebra_ip_forwarding_modify,
diff --git a/zebra/zebra_nb.h b/zebra/zebra_nb.h
index 785291bc68..426ad4b7de 100644
--- a/zebra/zebra_nb.h
+++ b/zebra/zebra_nb.h
@@ -14,6 +14,7 @@ extern "C" {
extern const struct frr_yang_module_info frr_zebra_info;
/* prototypes */
+struct yang_data *zebra_max_multipath_get_elem(struct nb_cb_get_elem_args *args);
int get_route_information_rpc(struct nb_cb_rpc_args *args);
int get_v6_mroute_info_rpc(struct nb_cb_rpc_args *args);
int get_vrf_info_rpc(struct nb_cb_rpc_args *args);
diff --git a/zebra/zebra_nb_state.c b/zebra/zebra_nb_state.c
index 6ed11f75f1..a7091821b5 100644
--- a/zebra/zebra_nb_state.c
+++ b/zebra/zebra_nb_state.c
@@ -1157,3 +1157,12 @@ lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_weight_get_elem(
return NULL;
}
+
+/*
+ * XPath:
+ * /frr-zebra:zebra/max-multipath
+ */
+struct yang_data *zebra_max_multipath_get_elem(struct nb_cb_get_elem_args *args)
+{
+ return yang_data_new_uint16(args->xpath, zrouter.multipath_num);
+}