summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format1
-rw-r--r--bgpd/bgp_mac.c4
-rw-r--r--bgpd/bgp_routemap.c79
-rw-r--r--bgpd/bgp_routemap_nb.c7
-rw-r--r--bgpd/bgp_routemap_nb.h4
-rw-r--r--bgpd/bgp_routemap_nb_config.c54
-rw-r--r--doc/developer/workflow.rst3
-rw-r--r--doc/user/bgp.rst7
-rw-r--r--doc/user/ripd.rst5
-rw-r--r--doc/user/routemap.rst2
-rw-r--r--lib/routemap.h2
-rw-r--r--lib/routemap_cli.c7
-rw-r--r--ospfd/ospf_abr.c6
-rw-r--r--ospfd/ospf_apiserver.c26
-rw-r--r--ospfd/ospf_interface.c3
-rw-r--r--ospfd/ospf_lsa.c3
-rw-r--r--ospfd/ospf_opaque.c16
-rw-r--r--ospfd/ospf_packet.c2
-rw-r--r--ospfd/ospf_ti_lfa.c1
-rw-r--r--ripd/rip_cli.c40
-rw-r--r--ripd/rip_nb_config.c8
-rw-r--r--ripd/rip_zebra.c11
-rw-r--r--ripd/ripd.c62
-rw-r--r--ripd/ripd.h4
-rw-r--r--tests/topotests/bgp_route_map_match_source_protocol/__init__.py0
-rw-r--r--tests/topotests/bgp_route_map_match_source_protocol/r1/frr.conf32
-rw-r--r--tests/topotests/bgp_route_map_match_source_protocol/r2/frr.conf10
-rw-r--r--tests/topotests/bgp_route_map_match_source_protocol/r3/frr.conf10
-rw-r--r--tests/topotests/bgp_route_map_match_source_protocol/test_bgp_route_map_match_source_protocol.py115
-rw-r--r--tests/topotests/lib/micronet_compat.py2
-rw-r--r--tests/topotests/lib/ospf.py19
-rw-r--r--tests/topotests/munet/base.py10
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py18
-rw-r--r--tests/topotests/ospfapi/test_ospf_clientapi.py195
-rw-r--r--tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py6
-rw-r--r--tests/topotests/rip_allow_ecmp/r4/frr.conf13
-rw-r--r--tests/topotests/rip_allow_ecmp/r5/frr.conf13
-rw-r--r--tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py34
-rw-r--r--yang/example/ripd.json2
-rw-r--r--yang/example/ripd.xml2
-rw-r--r--yang/frr-bgp-route-map.yang17
-rw-r--r--yang/frr-ripd.yang4
42 files changed, 793 insertions, 66 deletions
diff --git a/.clang-format b/.clang-format
index 1b18323348..c51e0b3cec 100644
--- a/.clang-format
+++ b/.clang-format
@@ -11,6 +11,7 @@ AllowShortFunctionsOnASingleLine: false
IndentCaseLabels: false
AlignEscapedNewlinesLeft: false
AlignTrailingComments: true
+AlignConsecutiveMacros: AcrossComments
AllowAllParametersOfDeclarationOnNextLine: false
AlignAfterOpenBracket: true
SpaceAfterCStyleCast: false
diff --git a/bgpd/bgp_mac.c b/bgpd/bgp_mac.c
index 980f351303..6272bdb884 100644
--- a/bgpd/bgp_mac.c
+++ b/bgpd/bgp_mac.c
@@ -360,7 +360,7 @@ bool bgp_mac_exist(const struct ethaddr *mac)
return true;
}
-/* This API checks EVPN type-2 prefix and comapares
+/* This API checks EVPN type-2 prefix and compares
* mac against any of local assigned (SVIs) MAC
* address.
*/
@@ -375,8 +375,6 @@ bool bgp_mac_entry_exists(const struct prefix *p)
return false;
return bgp_mac_exist(&p->u.prefix_evpn.macip_addr.mac);
-
- return true;
}
static void bgp_mac_show_mac_entry(struct hash_bucket *bucket, void *arg)
diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c
index 10fc3ecda4..7db110be93 100644
--- a/bgpd/bgp_routemap.c
+++ b/bgpd/bgp_routemap.c
@@ -872,6 +872,46 @@ static const struct route_map_rule_cmd
route_match_ip_next_hop_type_free
};
+/* `match source-protocol` */
+static enum route_map_cmd_result_t
+route_match_source_protocol(void *rule, const struct prefix *prefix,
+ void *object)
+{
+ struct bgp_path_info *path = object;
+ int *protocol = rule;
+
+ if (!path)
+ return RMAP_NOMATCH;
+
+ if (path->type == *protocol)
+ return RMAP_MATCH;
+
+ return RMAP_NOMATCH;
+}
+
+static void *route_match_source_protocol_compile(const char *arg)
+{
+ int *protocol;
+
+ protocol = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*protocol));
+ *protocol = proto_name2num(arg);
+
+ return protocol;
+}
+
+static void route_match_source_protocol_free(void *rule)
+{
+ XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+static const struct route_map_rule_cmd route_match_source_protocol_cmd = {
+ "source-protocol",
+ route_match_source_protocol,
+ route_match_source_protocol_compile,
+ route_match_source_protocol_free
+};
+
+
/* `match ip route-source prefix-list PREFIX_LIST' */
static enum route_map_cmd_result_t
@@ -7177,6 +7217,42 @@ DEFPY_YANG (match_rpki_extcommunity,
return nb_cli_apply_changes(vty, NULL);
}
+DEFPY_YANG (match_source_protocol,
+ match_source_protocol_cmd,
+ "match source-protocol " FRR_REDIST_STR_ZEBRA "$proto",
+ MATCH_STR
+ "Match protocol via which the route was learnt\n"
+ FRR_REDIST_HELP_STR_ZEBRA)
+{
+ const char *xpath =
+ "./match-condition[condition='frr-bgp-route-map:source-protocol']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/frr-bgp-route-map:source-protocol",
+ xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, proto);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG (no_match_source_protocol,
+ no_match_source_protocol_cmd,
+ "no match source-protocol [" FRR_REDIST_STR_ZEBRA "]",
+ NO_STR
+ MATCH_STR
+ "Match protocol via which the route was learnt\n"
+ FRR_REDIST_HELP_STR_ZEBRA)
+{
+ const char *xpath =
+ "./match-condition[condition='frr-bgp-route-map:source-protocol']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
/* Initialization of route map. */
void bgp_route_map_init(void)
{
@@ -7252,6 +7328,7 @@ void bgp_route_map_init(void)
route_map_install_match(&route_match_ip_address_prefix_list_cmd);
route_map_install_match(&route_match_ip_next_hop_prefix_list_cmd);
route_map_install_match(&route_match_ip_next_hop_type_cmd);
+ route_map_install_match(&route_match_source_protocol_cmd);
route_map_install_match(&route_match_ip_route_source_prefix_list_cmd);
route_map_install_match(&route_match_aspath_cmd);
route_map_install_match(&route_match_community_cmd);
@@ -7441,6 +7518,8 @@ void bgp_route_map_init(void)
install_element(RMAP_NODE, &set_ipv6_nexthop_peer_cmd);
install_element(RMAP_NODE, &no_set_ipv6_nexthop_peer_cmd);
install_element(RMAP_NODE, &match_rpki_extcommunity_cmd);
+ install_element(RMAP_NODE, &match_source_protocol_cmd);
+ install_element(RMAP_NODE, &no_match_source_protocol_cmd);
#ifdef HAVE_SCRIPTING
install_element(RMAP_NODE, &match_script_cmd);
#endif
diff --git a/bgpd/bgp_routemap_nb.c b/bgpd/bgp_routemap_nb.c
index 6e8439cc26..282ebe9116 100644
--- a/bgpd/bgp_routemap_nb.c
+++ b/bgpd/bgp_routemap_nb.c
@@ -74,6 +74,13 @@ const struct frr_yang_module_info frr_bgp_route_map_info = {
.destroy = lib_route_map_entry_match_condition_rmap_match_condition_source_vrf_destroy,
}
},
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:source-protocol",
+ .cbs = {
+ .modify = lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_modify,
+ .destroy = lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_destroy,
+ }
+ },
{
.xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:peer-ipv4-address",
.cbs = {
diff --git a/bgpd/bgp_routemap_nb.h b/bgpd/bgp_routemap_nb.h
index bcd1e837e8..7066fdb419 100644
--- a/bgpd/bgp_routemap_nb.h
+++ b/bgpd/bgp_routemap_nb.h
@@ -30,6 +30,10 @@ int lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_m
struct nb_cb_modify_args *args);
int lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_destroy(
struct nb_cb_destroy_args *args);
+int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_modify(
+ struct nb_cb_modify_args *args);
+int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_destroy(
+ struct nb_cb_destroy_args *args);
int lib_route_map_entry_match_condition_rmap_match_condition_probability_modify(struct nb_cb_modify_args *args);
int lib_route_map_entry_match_condition_rmap_match_condition_probability_destroy(struct nb_cb_destroy_args *args);
int lib_route_map_entry_match_condition_rmap_match_condition_source_vrf_modify(struct nb_cb_modify_args *args);
diff --git a/bgpd/bgp_routemap_nb_config.c b/bgpd/bgp_routemap_nb_config.c
index 938a5ec31b..02564b0004 100644
--- a/bgpd/bgp_routemap_nb_config.c
+++ b/bgpd/bgp_routemap_nb_config.c
@@ -369,6 +369,60 @@ lib_route_map_entry_match_condition_rmap_match_condition_rpki_destroy(
/*
* XPath:
+ * /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:source-protocol
+ */
+int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ enum rmap_compile_rets ret;
+ const char *proto;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ /* Add configuration. */
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ proto = yang_dnode_get_string(args->dnode, NULL);
+
+ /* Set destroy information. */
+ rhc->rhc_mhook = bgp_route_match_delete;
+ rhc->rhc_rule = "source-protocol";
+ rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
+
+ ret = bgp_route_match_add(rhc->rhc_rmi, "source-protocol",
+ proto, RMAP_EVENT_MATCH_ADDED,
+ args->errmsg, args->errmsg_len);
+
+ if (ret != RMAP_COMPILE_SUCCESS) {
+ rhc->rhc_mhook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+ }
+
+ return NB_OK;
+}
+
+int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ return lib_route_map_entry_match_destroy(args);
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
* /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:rpki-extcommunity
*/
int lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_modify(
diff --git a/doc/developer/workflow.rst b/doc/developer/workflow.rst
index f11fff5dee..28cf5d0ab1 100644
--- a/doc/developer/workflow.rst
+++ b/doc/developer/workflow.rst
@@ -537,7 +537,8 @@ Programming Languages, Tools and Libraries
==========================================
The core of FRR is written in C (gcc or clang supported) and makes
-use of GNU compiler extensions. A few non-essential scripts are
+use of GNU compiler extensions. Additionally, the CLI generation
+tool, `clippy`, requires Python. A few other non-essential scripts are
implemented in Perl and Python. FRR requires the following tools
to build distribution packages: automake, autoconf, texinfo, libtool and
gawk and various libraries (i.e. libpam and libjson-c).
diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst
index 394a6987fc..f045ca239e 100644
--- a/doc/user/bgp.rst
+++ b/doc/user/bgp.rst
@@ -2032,6 +2032,13 @@ Capability Negotiation
Disabled by default.
+.. clicmd:: neighbor PEER aigp
+
+ Send and receive AIGP attribute for this neighbor. This is valid only for
+ eBGP neighbors.
+
+ Disabled by default. iBGP neighbors have this option enabled implicitly.
+
.. _bgp-as-path-access-lists:
AS Path Access Lists
diff --git a/doc/user/ripd.rst b/doc/user/ripd.rst
index 67323e61f3..f9c7724302 100644
--- a/doc/user/ripd.rst
+++ b/doc/user/ripd.rst
@@ -159,6 +159,11 @@ RIP Configuration
If `poisoned-reverse` is also set, the router sends the poisoned routes
with highest metric back to the sending router.
+.. clicmd:: allow-ecmp [1-MULTIPATH_NUM]
+
+ Control how many ECMP paths RIP can inject for the same prefix. If specified
+ without a number, a maximum is taken (compiled with ``--enable-multipath``).
+
.. _rip-version-control:
RIP Version Control
diff --git a/doc/user/routemap.rst b/doc/user/routemap.rst
index b7f5336564..bd19ae88e2 100644
--- a/doc/user/routemap.rst
+++ b/doc/user/routemap.rst
@@ -212,7 +212,7 @@ Route Map Match Command
.. clicmd:: match source-protocol PROTOCOL_NAME
- This is a ZEBRA specific match command. Matches the
+ This is a ZEBRA and BGP specific match command. Matches the
originating protocol specified.
.. clicmd:: match source-instance NUMBER
diff --git a/lib/routemap.h b/lib/routemap.h
index 9b2e18b4a7..7277744dc5 100644
--- a/lib/routemap.h
+++ b/lib/routemap.h
@@ -259,6 +259,8 @@ DECLARE_QOBJ_TYPE(route_map);
(strmatch(C, "frr-zebra-route-map:ipv4-next-hop-prefix-length"))
#define IS_MATCH_SRC_PROTO(C) \
(strmatch(C, "frr-zebra-route-map:source-protocol"))
+#define IS_MATCH_BGP_SRC_PROTO(C) \
+ (strmatch(C, "frr-bgp-route-map:source-protocol"))
#define IS_MATCH_SRC_INSTANCE(C) \
(strmatch(C, "frr-zebra-route-map:source-instance"))
/* BGP route-map match conditions */
diff --git a/lib/routemap_cli.c b/lib/routemap_cli.c
index 419086c4c6..0ccc78e838 100644
--- a/lib/routemap_cli.c
+++ b/lib/routemap_cli.c
@@ -599,11 +599,14 @@ void route_map_condition_show(struct vty *vty, const struct lyd_node *dnode,
yang_dnode_get_string(
dnode,
"./rmap-match-condition/frr-zebra-route-map:ipv4-prefix-length"));
- } else if (IS_MATCH_SRC_PROTO(condition)) {
+ } else if (IS_MATCH_SRC_PROTO(condition) ||
+ IS_MATCH_BGP_SRC_PROTO(condition)) {
vty_out(vty, " match source-protocol %s\n",
yang_dnode_get_string(
dnode,
- "./rmap-match-condition/frr-zebra-route-map:source-protocol"));
+ IS_MATCH_SRC_PROTO(condition)
+ ? "./rmap-match-condition/frr-zebra-route-map:source-protocol"
+ : "./rmap-match-condition/frr-bgp-route-map:source-protocol"));
} else if (IS_MATCH_SRC_INSTANCE(condition)) {
vty_out(vty, " match source-instance %s\n",
yang_dnode_get_string(
diff --git a/ospfd/ospf_abr.c b/ospfd/ospf_abr.c
index ded520889f..28d526870b 100644
--- a/ospfd/ospf_abr.c
+++ b/ospfd/ospf_abr.c
@@ -66,9 +66,11 @@ static void ospf_area_range_add(struct ospf_area *area,
apply_mask_ipv4(&p);
rn = route_node_get(ranges, (struct prefix *)&p);
- if (rn->info)
+ if (rn->info) {
route_unlock_node(rn);
- else
+ ospf_area_range_free(rn->info);
+ rn->info = range;
+ } else
rn->info = range;
}
diff --git a/ospfd/ospf_apiserver.c b/ospfd/ospf_apiserver.c
index 1aacc341aa..34e59c545b 100644
--- a/ospfd/ospf_apiserver.c
+++ b/ospfd/ospf_apiserver.c
@@ -1764,6 +1764,12 @@ int ospf_apiserver_originate1(struct ospf_lsa *lsa, struct ospf_lsa *old)
* session. Dump it, but increment past it's seqnum.
*/
assert(!ospf_opaque_is_owned(old));
+ if (IS_DEBUG_OSPF_CLIENT_API)
+ zlog_debug(
+ "LSA[Type%d:%pI4]: OSPF API Server Originate LSA Old Seq: 0x%x Age: %d",
+ old->data->type, &old->data->id,
+ ntohl(old->data->ls_seqnum),
+ ntohl(old->data->ls_age));
if (IS_LSA_MAX_SEQ(old)) {
flog_warn(EC_OSPF_LSA_INSTALL_FAILURE,
"%s: old LSA at maxseq", __func__);
@@ -1772,6 +1778,11 @@ int ospf_apiserver_originate1(struct ospf_lsa *lsa, struct ospf_lsa *old)
lsa->data->ls_seqnum = lsa_seqnum_increment(old);
ospf_discard_from_db(ospf, old->lsdb, old);
}
+ if (IS_DEBUG_OSPF_CLIENT_API)
+ zlog_debug(
+ "LSA[Type%d:%pI4]: OSPF API Server Originate LSA New Seq: 0x%x Age: %d",
+ lsa->data->type, &lsa->data->id,
+ ntohl(lsa->data->ls_seqnum), ntohl(lsa->data->ls_age));
/* Install this LSA into LSDB. */
if (ospf_lsa_install(ospf, lsa->oi, lsa) == NULL) {
@@ -1846,6 +1857,11 @@ struct ospf_lsa *ospf_apiserver_lsa_refresher(struct ospf_lsa *lsa)
ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT);
assert(ospf);
+ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) {
+ zlog_debug("LSA[Type%d:%pI4]: OSPF API Server LSA Refresher",
+ lsa->data->type, &lsa->data->id);
+ }
+
apiserv = lookup_apiserver_by_lsa(lsa);
if (!apiserv) {
zlog_warn("%s: LSA[%s]: No apiserver?", __func__,
@@ -1855,14 +1871,14 @@ struct ospf_lsa *ospf_apiserver_lsa_refresher(struct ospf_lsa *lsa)
goto out;
}
- if (IS_LSA_MAXAGE(lsa)) {
- ospf_opaque_lsa_flush_schedule(lsa);
- goto out;
- }
-
/* Check if updated version of LSA instance has already prepared. */
new = ospf_lsdb_lookup(&apiserv->reserve, lsa);
if (!new) {
+ if (IS_LSA_MAXAGE(lsa)) {
+ ospf_opaque_lsa_flush_schedule(lsa);
+ goto out;
+ }
+
/* This is a periodic refresh, driven by core OSPF mechanism. */
new = ospf_apiserver_opaque_lsa_new(lsa->area, lsa->oi,
lsa->data);
diff --git a/ospfd/ospf_interface.c b/ospfd/ospf_interface.c
index 5742ece1f7..f18aa399a4 100644
--- a/ospfd/ospf_interface.c
+++ b/ospfd/ospf_interface.c
@@ -101,6 +101,9 @@ int ospf_if_get_output_cost(struct ospf_interface *oi)
cost = 1;
else if (cost > 65535)
cost = 65535;
+
+ if (if_is_loopback(oi->ifp))
+ cost = 0;
}
return cost;
diff --git a/ospfd/ospf_lsa.c b/ospfd/ospf_lsa.c
index 452a3ba374..67f1faf8a9 100644
--- a/ospfd/ospf_lsa.c
+++ b/ospfd/ospf_lsa.c
@@ -608,7 +608,8 @@ static int lsa_link_loopback_set(struct stream **s, struct ospf_interface *oi)
mask.s_addr = 0xffffffff;
id.s_addr = oi->address->u.prefix4.s_addr;
- return link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, 0);
+ return link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0,
+ oi->output_cost);
}
/* Describe Virtual Link. */
diff --git a/ospfd/ospf_opaque.c b/ospfd/ospf_opaque.c
index c2b40af1c4..6894c6a009 100644
--- a/ospfd/ospf_opaque.c
+++ b/ospfd/ospf_opaque.c
@@ -2120,14 +2120,21 @@ void ospf_opaque_self_originated_lsa_received(struct ospf_neighbor *nbr,
lsa->data->type, &lsa->data->id);
/*
- * Since these LSA entries are not yet installed into corresponding
- * LSDB, just flush them without calling ospf_ls_maxage() afterward.
+ * Install the stale LSA into the Link State Database, add it to the
+ * MaxAge list, and flush it from the OSPF routing domain. For other
+ * LSA types, the installation is done in the refresh function. It is
+ * done inline here since the opaque refresh function is dynamically
+ * registered when opaque LSAs are originated (which is not the case
+ * for stale LSAs).
*/
lsa->data->ls_age = htons(OSPF_LSA_MAXAGE);
+ ospf_lsa_install(
+ top, (lsa->data->type == OSPF_OPAQUE_LINK_LSA) ? nbr->oi : NULL,
+ lsa);
+ ospf_lsa_maxage(top, lsa);
+
switch (lsa->data->type) {
case OSPF_OPAQUE_LINK_LSA:
- ospf_flood_through_area(nbr->oi->area, NULL /*inbr*/, lsa);
- break;
case OSPF_OPAQUE_AREA_LSA:
ospf_flood_through_area(nbr->oi->area, NULL /*inbr*/, lsa);
break;
@@ -2139,7 +2146,6 @@ void ospf_opaque_self_originated_lsa_received(struct ospf_neighbor *nbr,
__func__, lsa->data->type);
return;
}
- ospf_lsa_discard(lsa); /* List "lsas" will be deleted by caller. */
}
/*------------------------------------------------------------------------*
diff --git a/ospfd/ospf_packet.c b/ospfd/ospf_packet.c
index 552acfd6d3..fe94c34e4e 100644
--- a/ospfd/ospf_packet.c
+++ b/ospfd/ospf_packet.c
@@ -2031,7 +2031,7 @@ static void ospf_ls_upd(struct ospf *ospf, struct ip *iph,
if (current == NULL) {
if (IS_DEBUG_OSPF_EVENT)
zlog_debug(
- "LSA[%s]: Previously originated Opaque-LSA,not found in the LSDB.",
+ "LSA[%s]: Previously originated Opaque-LSA, not found in the LSDB.",
dump_lsa_key(lsa));
SET_FLAG(lsa->flags, OSPF_LSA_SELF);
diff --git a/ospfd/ospf_ti_lfa.c b/ospfd/ospf_ti_lfa.c
index da9428aba6..07fc503947 100644
--- a/ospfd/ospf_ti_lfa.c
+++ b/ospfd/ospf_ti_lfa.c
@@ -1078,6 +1078,7 @@ void ospf_ti_lfa_free_p_spaces(struct ospf_area *area)
q_spaces_fini(p_space->q_spaces);
XFREE(MTYPE_OSPF_Q_SPACE, p_space->q_spaces);
+ XFREE(MTYPE_OSPF_P_SPACE, p_space);
}
p_spaces_fini(area->p_spaces);
diff --git a/ripd/rip_cli.c b/ripd/rip_cli.c
index ac9fc4b1a8..6f45bb5d9e 100644
--- a/ripd/rip_cli.c
+++ b/ripd/rip_cli.c
@@ -85,14 +85,33 @@ void cli_show_router_rip(struct vty *vty, const struct lyd_node *dnode,
/*
* XPath: /frr-ripd:ripd/instance/allow-ecmp
*/
-DEFPY_YANG (rip_allow_ecmp,
+DEFUN_YANG (rip_allow_ecmp,
rip_allow_ecmp_cmd,
- "[no] allow-ecmp",
+ "allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]",
+ "Allow Equal Cost MultiPath\n"
+ "Number of paths\n")
+{
+ int idx_number = 1;
+ char mpaths[3] = {};
+ uint32_t paths = MULTIPATH_NUM;
+
+ if (argv[idx_number])
+ paths = strtol(argv[idx_number]->arg, NULL, 10);
+ snprintf(mpaths, sizeof(mpaths), "%u", paths);
+
+ nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, mpaths);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFUN_YANG (no_rip_allow_ecmp,
+ no_rip_allow_ecmp_cmd,
+ "no allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]",
NO_STR
- "Allow Equal Cost MultiPath\n")
+ "Allow Equal Cost MultiPath\n"
+ "Number of paths\n")
{
- nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY,
- no ? "false" : "true");
+ nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, 0);
return nb_cli_apply_changes(vty, NULL);
}
@@ -100,10 +119,14 @@ DEFPY_YANG (rip_allow_ecmp,
void cli_show_rip_allow_ecmp(struct vty *vty, const struct lyd_node *dnode,
bool show_defaults)
{
- if (!yang_dnode_get_bool(dnode, NULL))
- vty_out(vty, " no");
+ uint8_t paths;
+
+ paths = yang_dnode_get_uint8(dnode, NULL);
- vty_out(vty, " allow-ecmp\n");
+ if (!paths)
+ vty_out(vty, " no allow-ecmp\n");
+ else
+ vty_out(vty, " allow-ecmp %d\n", paths);
}
/*
@@ -1156,6 +1179,7 @@ void rip_cli_init(void)
install_element(RIP_NODE, &rip_no_distribute_list_cmd);
install_element(RIP_NODE, &rip_allow_ecmp_cmd);
+ install_element(RIP_NODE, &no_rip_allow_ecmp_cmd);
install_element(RIP_NODE, &rip_default_information_originate_cmd);
install_element(RIP_NODE, &rip_default_metric_cmd);
install_element(RIP_NODE, &no_rip_default_metric_cmd);
diff --git a/ripd/rip_nb_config.c b/ripd/rip_nb_config.c
index 5c7bd0fb86..1117ec9ff7 100644
--- a/ripd/rip_nb_config.c
+++ b/ripd/rip_nb_config.c
@@ -99,9 +99,13 @@ int ripd_instance_allow_ecmp_modify(struct nb_cb_modify_args *args)
return NB_OK;
rip = nb_running_get_entry(args->dnode, NULL, true);
- rip->ecmp = yang_dnode_get_bool(args->dnode, NULL);
- if (!rip->ecmp)
+ rip->ecmp = yang_dnode_get_uint8(args->dnode, NULL);
+ if (!rip->ecmp) {
rip_ecmp_disable(rip);
+ return NB_OK;
+ }
+
+ rip_ecmp_change(rip);
return NB_OK;
}
diff --git a/ripd/rip_zebra.c b/ripd/rip_zebra.c
index 698dcb982d..834c7d2d65 100644
--- a/ripd/rip_zebra.c
+++ b/ripd/rip_zebra.c
@@ -20,6 +20,7 @@
/* All information about zebra. */
struct zclient *zclient = NULL;
+uint32_t zebra_ecmp_count = MULTIPATH_NUM;
/* Send ECMP routes to zebra. */
static void rip_zebra_ipv4_send(struct rip *rip, struct route_node *rp,
@@ -30,7 +31,7 @@ static void rip_zebra_ipv4_send(struct rip *rip, struct route_node *rp,
struct zapi_nexthop *api_nh;
struct listnode *listnode = NULL;
struct rip_info *rinfo = NULL;
- int count = 0;
+ uint32_t count = 0;
memset(&api, 0, sizeof(api));
api.vrf_id = rip->vrf->vrf_id;
@@ -39,7 +40,7 @@ static void rip_zebra_ipv4_send(struct rip *rip, struct route_node *rp,
SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) {
- if (count >= MULTIPATH_NUM)
+ if (count >= zebra_ecmp_count)
break;
api_nh = &api.nexthops[count];
api_nh->vrf_id = rip->vrf->vrf_id;
@@ -227,6 +228,11 @@ zclient_handler *const rip_handlers[] = {
[ZEBRA_REDISTRIBUTE_ROUTE_DEL] = rip_zebra_read_route,
};
+static void rip_zebra_capabilities(struct zclient_capabilities *cap)
+{
+ zebra_ecmp_count = MIN(cap->ecmp, zebra_ecmp_count);
+}
+
void rip_zclient_init(struct event_loop *master)
{
/* Set default value to the zebra client structure. */
@@ -234,6 +240,7 @@ void rip_zclient_init(struct event_loop *master)
array_size(rip_handlers));
zclient_init(zclient, ZEBRA_ROUTE_RIP, 0, &ripd_privs);
zclient->zebra_connected = rip_zebra_connected;
+ zclient->zebra_capabilities = rip_zebra_capabilities;
}
void rip_zclient_stop(void)
diff --git a/ripd/ripd.c b/ripd/ripd.c
index 339b033fb1..04a8cad560 100644
--- a/ripd/ripd.c
+++ b/ripd/ripd.c
@@ -156,7 +156,10 @@ struct rip_info *rip_ecmp_add(struct rip *rip, struct rip_info *rinfo_new)
{
struct route_node *rp = rinfo_new->rp;
struct rip_info *rinfo = NULL;
+ struct rip_info *rinfo_exist = NULL;
struct list *list = NULL;
+ struct listnode *node = NULL;
+ struct listnode *nnode = NULL;
if (rp->info == NULL)
rp->info = list_new();
@@ -167,6 +170,33 @@ struct rip_info *rip_ecmp_add(struct rip *rip, struct rip_info *rinfo_new)
if (listcount(list) && !rip->ecmp)
return NULL;
+ /* Add or replace an existing ECMP path with lower neighbor IP */
+ if (listcount(list) && listcount(list) >= rip->ecmp) {
+ struct rip_info *from_highest = NULL;
+
+ /* Find the rip_info struct that has the highest nexthop IP */
+ for (ALL_LIST_ELEMENTS(list, node, nnode, rinfo_exist))
+ if (!from_highest ||
+ (from_highest &&
+ IPV4_ADDR_CMP(&rinfo_exist->from,
+ &from_highest->from) > 0)) {
+ from_highest = rinfo_exist;
+ }
+
+ /* If we have a route in ECMP group, delete the old
+ * one that has a higher next-hop address. Lower IP is
+ * preferred.
+ */
+ if (rip->ecmp > 1 && from_highest &&
+ IPV4_ADDR_CMP(&from_highest->from, &rinfo_new->from) > 0) {
+ rip_ecmp_delete(rip, from_highest);
+ goto add_or_replace;
+ }
+
+ return NULL;
+ }
+
+add_or_replace:
rinfo = rip_info_new();
memcpy(rinfo, rinfo_new, sizeof(struct rip_info));
listnode_add(list, rinfo);
@@ -2632,6 +2662,36 @@ struct rip *rip_lookup_by_vrf_name(const char *vrf_name)
return RB_FIND(rip_instance_head, &rip_instances, &rip);
}
+/* Update ECMP routes to zebra when `allow-ecmp` changed. */
+void rip_ecmp_change(struct rip *rip)
+{
+ struct route_node *rp;
+ struct rip_info *rinfo;
+ struct list *list;
+ struct listnode *node, *nextnode;
+
+ for (rp = route_top(rip->table); rp; rp = route_next(rp)) {
+ list = rp->info;
+ if (list && listcount(list) > 1) {
+ while (listcount(list) > rip->ecmp) {
+ struct rip_info *from_highest = NULL;
+
+ for (ALL_LIST_ELEMENTS(list, node, nextnode,
+ rinfo)) {
+ if (!from_highest ||
+ (from_highest &&
+ IPV4_ADDR_CMP(
+ &rinfo->from,
+ &from_highest->from) > 0))
+ from_highest = rinfo;
+ }
+
+ rip_ecmp_delete(rip, from_highest);
+ }
+ }
+ }
+}
+
/* Create new RIP instance and set it to global variable. */
struct rip *rip_create(const char *vrf_name, struct vrf *vrf, int socket)
{
@@ -2641,7 +2701,7 @@ struct rip *rip_create(const char *vrf_name, struct vrf *vrf, int socket)
rip->vrf_name = XSTRDUP(MTYPE_RIP_VRF_NAME, vrf_name);
/* Set initial value. */
- rip->ecmp = yang_get_default_bool("%s/allow-ecmp", RIP_INSTANCE);
+ rip->ecmp = yang_get_default_uint8("%s/allow-ecmp", RIP_INSTANCE);
rip->default_metric =
yang_get_default_uint8("%s/default-metric", RIP_INSTANCE);
rip->distance =
diff --git a/ripd/ripd.h b/ripd/ripd.h
index bba3c28069..2db1e5a6b0 100644
--- a/ripd/ripd.h
+++ b/ripd/ripd.h
@@ -141,7 +141,7 @@ struct rip {
struct route_table *distance_table;
/* RIP ECMP flag */
- bool ecmp;
+ uint8_t ecmp;
/* Are we in passive-interface default mode? */
bool passive_default;
@@ -537,4 +537,6 @@ extern struct event_loop *master;
DECLARE_HOOK(rip_ifaddr_add, (struct connected * ifc), (ifc));
DECLARE_HOOK(rip_ifaddr_del, (struct connected * ifc), (ifc));
+extern void rip_ecmp_change(struct rip *rip);
+
#endif /* _ZEBRA_RIP_H */
diff --git a/tests/topotests/bgp_route_map_match_source_protocol/__init__.py b/tests/topotests/bgp_route_map_match_source_protocol/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/topotests/bgp_route_map_match_source_protocol/__init__.py
diff --git a/tests/topotests/bgp_route_map_match_source_protocol/r1/frr.conf b/tests/topotests/bgp_route_map_match_source_protocol/r1/frr.conf
new file mode 100644
index 0000000000..7c3efeea4b
--- /dev/null
+++ b/tests/topotests/bgp_route_map_match_source_protocol/r1/frr.conf
@@ -0,0 +1,32 @@
+!
+interface lo
+ ip address 172.16.255.1/32
+!
+interface r1-eth0
+ ip address 192.168.1.1/24
+!
+interface r1-eth1
+ ip address 192.168.2.1/24
+!
+ip route 10.10.10.10/32 192.168.2.2
+!
+router bgp 65001
+ no bgp ebgp-requires-policy
+ neighbor 192.168.1.2 remote-as external
+ neighbor 192.168.1.2 timers 1 3
+ neighbor 192.168.1.2 timers connect 1
+ neighbor 192.168.2.2 remote-as external
+ neighbor 192.168.2.2 timers 1 3
+ neighbor 192.168.2.2 timers connect 1
+ address-family ipv4
+ redistribute connected
+ redistribute static
+ neighbor 192.168.1.2 route-map r2 out
+ neighbor 192.168.2.2 route-map r3 out
+ exit-address-family
+!
+route-map r2 permit 10
+ match source-protocol static
+route-map r3 permit 10
+ match source-protocol connected
+!
diff --git a/tests/topotests/bgp_route_map_match_source_protocol/r2/frr.conf b/tests/topotests/bgp_route_map_match_source_protocol/r2/frr.conf
new file mode 100644
index 0000000000..7213975cc0
--- /dev/null
+++ b/tests/topotests/bgp_route_map_match_source_protocol/r2/frr.conf
@@ -0,0 +1,10 @@
+!
+interface r2-eth0
+ ip address 192.168.1.2/24
+!
+router bgp 65002
+ no bgp ebgp-requires-policy
+ neighbor 192.168.1.1 remote-as external
+ neighbor 192.168.1.1 timers 1 3
+ neighbor 192.168.1.1 timers connect 1
+!
diff --git a/tests/topotests/bgp_route_map_match_source_protocol/r3/frr.conf b/tests/topotests/bgp_route_map_match_source_protocol/r3/frr.conf
new file mode 100644
index 0000000000..4a1d830b0a
--- /dev/null
+++ b/tests/topotests/bgp_route_map_match_source_protocol/r3/frr.conf
@@ -0,0 +1,10 @@
+!
+interface r3-eth0
+ ip address 192.168.2.2/24
+!
+router bgp 65003
+ no bgp ebgp-requires-policy
+ neighbor 192.168.2.1 remote-as external
+ neighbor 192.168.2.1 timers 1 3
+ neighbor 192.168.2.1 timers connect 1
+!
diff --git a/tests/topotests/bgp_route_map_match_source_protocol/test_bgp_route_map_match_source_protocol.py b/tests/topotests/bgp_route_map_match_source_protocol/test_bgp_route_map_match_source_protocol.py
new file mode 100644
index 0000000000..2828796405
--- /dev/null
+++ b/tests/topotests/bgp_route_map_match_source_protocol/test_bgp_route_map_match_source_protocol.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# Copyright (c) 2023 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+"""
+Test if r1 can announce only static routes to r2, and only connected
+routes to r3 using `match source-protocol` with route-maps.
+"""
+
+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
+
+pytestmark = [pytest.mark.bgpd]
+
+
+def build_topo(tgen):
+ for routern in range(1, 4):
+ tgen.add_router("r{}".format(routern))
+
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["r2"])
+
+ switch = tgen.add_switch("s2")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["r3"])
+
+
+def setup_module(mod):
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+
+ for i, (rname, router) in enumerate(router_list.items(), 1):
+ router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
+
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_bgp_route_map_match_source_protocol():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ def _bgp_check_advertised_routes_r2():
+ output = json.loads(
+ tgen.gears["r1"].vtysh_cmd(
+ "show bgp ipv4 unicast neighbors 192.168.1.2 advertised-routes json"
+ )
+ )
+ expected = {
+ "advertisedRoutes": {
+ "10.10.10.10/32": {
+ "valid": True,
+ }
+ },
+ "totalPrefixCounter": 1,
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_bgp_check_advertised_routes_r2)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Failed to filter routes by source-protocol for r2"
+
+ def _bgp_check_advertised_routes_r3():
+ output = json.loads(
+ tgen.gears["r1"].vtysh_cmd(
+ "show bgp ipv4 unicast neighbors 192.168.2.2 advertised-routes json"
+ )
+ )
+ expected = {
+ "advertisedRoutes": {
+ "192.168.1.0/24": {
+ "valid": True,
+ },
+ "192.168.2.0/24": {
+ "valid": True,
+ },
+ "172.16.255.1/32": {
+ "valid": True,
+ },
+ },
+ "totalPrefixCounter": 3,
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_bgp_check_advertised_routes_r3)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Failed to filter routes by source-protocol for r3"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/lib/micronet_compat.py b/tests/topotests/lib/micronet_compat.py
index c5c2adc545..d648a120ab 100644
--- a/tests/topotests/lib/micronet_compat.py
+++ b/tests/topotests/lib/micronet_compat.py
@@ -273,7 +273,7 @@ ff02::2\tip6-allrouters
shellopt = self.cfgopt.get_option_list("--shell")
if "all" in shellopt or "." in shellopt:
- self.run_in_window("bash")
+ self.run_in_window("bash", title="munet")
# This is expected by newer munet CLI code
self.config_dirname = ""
diff --git a/tests/topotests/lib/ospf.py b/tests/topotests/lib/ospf.py
index dad87440bc..ffe81fbd99 100644
--- a/tests/topotests/lib/ospf.py
+++ b/tests/topotests/lib/ospf.py
@@ -302,7 +302,6 @@ def __create_ospf_global(tgen, input_dict, router, build, load_config, ospf):
# ospf gr information
gr_data = ospf_data.setdefault("graceful-restart", {})
if gr_data:
-
if "opaque" in gr_data and gr_data["opaque"]:
cmd = "capability opaque"
if gr_data.setdefault("delete", False):
@@ -710,6 +709,7 @@ def verify_ospf_neighbor(
else:
data_ip = topo["routers"][ospf_nbr]["links"]
data_rid = topo["routers"][ospf_nbr]["ospf"]["router_id"]
+ logger.info("ospf neighbor %s: router-id: %s", router, data_rid)
if ospf_nbr in data_ip:
nbr_details = nbr_data[ospf_nbr]
elif lan:
@@ -728,8 +728,10 @@ def verify_ospf_neighbor(
try:
nh_state = show_ospf_json[nbr_rid][0]["nbrState"].split("/")[0]
except KeyError:
- errormsg = "[DUT: {}] OSPF peer {} missing,from " "{} ".format(
- router, nbr_rid, ospf_nbr
+ errormsg = (
+ "[DUT: {}] missing OSPF neighbor {} with router-id {}".format(
+ router, ospf_nbr, nbr_rid
+ )
)
return errormsg
@@ -843,7 +845,6 @@ def verify_ospf6_neighbor(tgen, topo=None, dut=None, input_dict=None, lan=False)
return errormsg
for ospf_nbr, nbr_data in ospf_nbr_list.items():
-
try:
data_ip = data_rid = topo["routers"][ospf_nbr]["ospf6"]["router_id"]
except KeyError:
@@ -914,7 +915,6 @@ def verify_ospf6_neighbor(tgen, topo=None, dut=None, input_dict=None, lan=False)
return errormsg
continue
else:
-
for router, rnode in tgen.routers().items():
if "ospf6" not in topo["routers"][router]:
continue
@@ -945,7 +945,7 @@ def verify_ospf6_neighbor(tgen, topo=None, dut=None, input_dict=None, lan=False)
data_ip = data_rid = topo["routers"][nbr_data["nbr"]]["ospf6"][
"router_id"
]
-
+ logger.info("ospf neighbor %s: router-id: %s", ospf_nbr, data_rid)
if ospf_nbr in data_ip:
nbr_details = nbr_data[ospf_nbr]
elif lan:
@@ -968,8 +968,10 @@ def verify_ospf6_neighbor(tgen, topo=None, dut=None, input_dict=None, lan=False)
nh_state = get_index_val.get(neighbor_ip)["state"]
intf_state = get_index_val.get(neighbor_ip)["ifState"]
except TypeError:
- errormsg = "[DUT: {}] OSPF peer {} missing,from " "{} ".format(
- router, nbr_rid, ospf_nbr
+ errormsg = (
+ "[DUT: {}] missing OSPF neighbor {} with router-id {}".format(
+ router, ospf_nbr, nbr_rid
+ )
)
return errormsg
@@ -1761,7 +1763,6 @@ def verify_ospf6_rib(
continue
if st_rt in ospf_rib_json:
-
st_found = True
found_routes.append(st_rt)
diff --git a/tests/topotests/munet/base.py b/tests/topotests/munet/base.py
index eb4b088442..c6ae70e09b 100644
--- a/tests/topotests/munet/base.py
+++ b/tests/topotests/munet/base.py
@@ -1241,9 +1241,13 @@ class Commander: # pylint: disable=R0904
# XXX not appropriate for ssh
cmd = ["sudo", "-Eu", os.environ["SUDO_USER"]] + cmd
- if not isinstance(nscmd, str):
- nscmd = shlex.join(nscmd)
- cmd.append(nscmd)
+ if title:
+ cmd.append("-t")
+ cmd.append(title)
+
+ if isinstance(nscmd, str):
+ nscmd = shlex.split(nscmd)
+ cmd.extend(nscmd)
elif "DISPLAY" in os.environ:
cmd = [get_exec_path_host("xterm")]
if "SUDO_USER" in os.environ:
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py b/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py
index 9d7a15833c..cf7d95b65a 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py
@@ -84,8 +84,8 @@ SUMMARY = {"ipv4": ["11.0.0.0/8", "12.0.0.0/8", "11.0.0.0/24"]}
"""
TOPOOLOGY =
Please view in a fixed-width font such as Courier.
- +---+ A0 +---+
- +R1 +------------+R2 |
+ +---+ A0 +---+
+ |R1 +------------+R2 |
+-+-+- +--++
| -- -- |
| -- A0 -- |
@@ -94,8 +94,8 @@ TOPOOLOGY =
| -- -- |
| -- -- |
+-+-+- +-+-+
- +R0 +-------------+R3 |
- +---+ A0 +---+
+ |R0 +-------------+R3 |
+ +---+ A0 +---+
TESTCASES =
1. OSPF summarisation functionality.
@@ -977,7 +977,7 @@ def test_ospf_type5_summary_tc42_p0(request):
ip = topo["routers"]["r0"]["links"]["r3"]["ipv4"]
- ip_net = str(ipaddress.ip_interface(u"{}".format(ip)).network)
+ ip_net = str(ipaddress.ip_interface("{}".format(ip)).network)
ospf_summ_r1 = {
"r0": {
"ospf": {"summary-address": [{"prefix": ip_net.split("/")[0], "mask": "8"}]}
@@ -1519,7 +1519,7 @@ def test_ospf_type5_summary_tc45_p0(request):
step("Repeat steps 1 to 10 of summarisation in non Back bone area.")
reset_config_on_routers(tgen)
- step("Change the area id on the interface on R0")
+ step("Change the area id on the interface on R0 to R1 from 0.0.0.0 to 0.0.0.1")
input_dict = {
"r0": {
"links": {
@@ -1549,7 +1549,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = create_interfaces_cfg(tgen, input_dict)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Change the area id on the interface ")
+ step("Change the area id on the interface on R1 to R0 from 0.0.0.0 to 0.0.0.1")
input_dict = {
"r1": {
"links": {
@@ -1579,6 +1579,10 @@ def test_ospf_type5_summary_tc45_p0(request):
result = create_interfaces_cfg(tgen, input_dict)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ # clear neighbor state on both routers to avoid stale state
+ tgen.net["r0"].cmd("clear ip ospf neighbor")
+ tgen.net["r1"].cmd("clear ip ospf neighbor")
+
ospf_covergence = verify_ospf_neighbor(tgen, topo)
assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
diff --git a/tests/topotests/ospfapi/test_ospf_clientapi.py b/tests/topotests/ospfapi/test_ospf_clientapi.py
index b01c96226e..2e8bea2651 100644
--- a/tests/topotests/ospfapi/test_ospf_clientapi.py
+++ b/tests/topotests/ospfapi/test_ospf_clientapi.py
@@ -20,7 +20,15 @@ from datetime import datetime, timedelta
import pytest
-from lib.common_config import retry, run_frr_cmd, step
+from lib.common_config import (
+ retry,
+ run_frr_cmd,
+ step,
+ kill_router_daemons,
+ start_router_daemons,
+ shutdown_bringup_interface,
+)
+
from lib.micronet import Timeout, comm_error
from lib.topogen import Topogen, TopoRouter
from lib.topotest import interface_set_status, json_cmp
@@ -936,6 +944,191 @@ def test_ospf_opaque_delete_data3(tgen):
_test_opaque_add_del(tgen, apibin)
+def _test_opaque_add_restart_add(tgen, apibin):
+ "Test adding an opaque LSA and then restarting ospfd"
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+
+ p = None
+ pread = None
+ # Log to our stdin, stderr
+ pout = open(os.path.join(r1.net.logdir, "r1/add-del.log"), "a+")
+ try:
+ step("reachable: check for add notification")
+ pread = r2.popen(
+ ["/usr/bin/timeout", "120", apibin, "-v", "--logtag=READER", "wait,120"],
+ encoding=None, # don't buffer
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
+ p = r1.popen(
+ [
+ apibin,
+ "-v",
+ "add,10,1.2.3.4,231,1",
+ "add,10,1.2.3.4,231,1,feedaceebeef",
+ "wait, 5",
+ "add,10,1.2.3.4,231,1,feedaceedeadbeef",
+ "wait, 5",
+ "add,10,1.2.3.4,231,1,feedaceebaddbeef",
+ "wait, 5",
+ ]
+ )
+ add_input_dict = {
+ "areas": {
+ "1.2.3.4": {
+ "areaLocalOpaqueLsa": [
+ {
+ "lsId": "231.0.0.1",
+ "advertisedRouter": "1.0.0.0",
+ "sequenceNumber": "80000004",
+ "checksum": "3128",
+ },
+ ],
+ "areaLocalOpaqueLsaCount": 1,
+ },
+ },
+ }
+ step("Check for add LSAs")
+ json_cmd = "show ip ospf da json"
+ assert verify_ospf_database(tgen, r1, add_input_dict, json_cmd) is None
+ assert verify_ospf_database(tgen, r2, add_input_dict, json_cmd) is None
+
+ step("Shutdown the interface on r1 to isolate it for r2")
+ shutdown_bringup_interface(tgen, "r1", "r1-eth0", False)
+
+ time.sleep(2)
+ step("Reset the client")
+ p.send_signal(signal.SIGINT)
+ time.sleep(2)
+ p.wait()
+ p = None
+
+ step("Kill ospfd on R1")
+ kill_router_daemons(tgen, "r1", ["ospfd"])
+ time.sleep(2)
+
+ step("Bring ospfd on R1 back up")
+ start_router_daemons(tgen, "r1", ["ospfd"])
+
+ p = r1.popen(
+ [
+ apibin,
+ "-v",
+ "add,10,1.2.3.4,231,1",
+ "add,10,1.2.3.4,231,1,feedaceecafebeef",
+ "wait, 5",
+ ]
+ )
+
+ step("Bring the interface on r1 back up for connection to r2")
+ shutdown_bringup_interface(tgen, "r1", "r1-eth0", True)
+
+ step("Verify area opaque LSA refresh")
+ json_cmd = "show ip ospf da opaque-area json"
+ add_detail_input_dict = {
+ "areaLocalOpaqueLsa": {
+ "areas": {
+ "1.2.3.4": [
+ {
+ "linkStateId": "231.0.0.1",
+ "advertisingRouter": "1.0.0.0",
+ "lsaSeqNumber": "80000005",
+ "checksum": "a87e",
+ "length": 28,
+ "opaqueDataLength": 8,
+ },
+ ],
+ },
+ },
+ }
+ assert verify_ospf_database(tgen, r1, add_detail_input_dict, json_cmd) is None
+ assert verify_ospf_database(tgen, r2, add_detail_input_dict, json_cmd) is None
+
+ step("Shutdown the interface on r1 to isolate it for r2")
+ shutdown_bringup_interface(tgen, "r1", "r1-eth0", False)
+
+ time.sleep(2)
+ step("Reset the client")
+ p.send_signal(signal.SIGINT)
+ time.sleep(2)
+ p.wait()
+ p = None
+
+ step("Kill ospfd on R1")
+ kill_router_daemons(tgen, "r1", ["ospfd"])
+ time.sleep(2)
+
+ step("Bring ospfd on R1 back up")
+ start_router_daemons(tgen, "r1", ["ospfd"])
+
+ step("Bring the interface on r1 back up for connection to r2")
+ shutdown_bringup_interface(tgen, "r1", "r1-eth0", True)
+
+ step("Verify area opaque LSA Purging")
+ json_cmd = "show ip ospf da opaque-area json"
+ add_detail_input_dict = {
+ "areaLocalOpaqueLsa": {
+ "areas": {
+ "1.2.3.4": [
+ {
+ "lsaAge": 3600,
+ "linkStateId": "231.0.0.1",
+ "advertisingRouter": "1.0.0.0",
+ "lsaSeqNumber": "80000005",
+ "checksum": "a87e",
+ "length": 28,
+ "opaqueDataLength": 8,
+ },
+ ],
+ },
+ },
+ }
+ assert verify_ospf_database(tgen, r1, add_detail_input_dict, json_cmd) is None
+ assert verify_ospf_database(tgen, r2, add_detail_input_dict, json_cmd) is None
+ step("Verify Area Opaque LSA removal after timeout (60 seconds)")
+ time.sleep(60)
+ json_cmd = "show ip ospf da opaque-area json"
+ timeout_detail_input_dict = {
+ "areaLocalOpaqueLsa": {
+ "areas": {
+ "1.2.3.4": [],
+ },
+ },
+ }
+ assert (
+ verify_ospf_database(tgen, r1, timeout_detail_input_dict, json_cmd) is None
+ )
+ assert (
+ verify_ospf_database(tgen, r2, timeout_detail_input_dict, json_cmd) is None
+ )
+
+ except Exception:
+ if p:
+ p.terminate()
+ if p.wait():
+ comm_error(p)
+ p = None
+ raise
+ finally:
+ if pread:
+ pread.terminate()
+ pread.wait()
+ if p:
+ p.terminate()
+ p.wait()
+
+
+@pytest.mark.parametrize("tgen", [2], indirect=True)
+def test_ospf_opaque_restart(tgen):
+ apibin = os.path.join(CLIENTDIR, "ospfclient.py")
+ rc, o, e = tgen.gears["r2"].net.cmd_status([apibin, "--help"])
+ logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", apibin, rc, o, e)
+ _test_opaque_add_restart_add(tgen, apibin)
+
+
if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py
index b0e56e619a..8cc0ed6090 100644
--- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py
+++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py
@@ -565,7 +565,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
ip = topo["routers"]["r0"]["links"]["r3"]["ipv6"]
- ip_net = str(ipaddress.ip_interface(u"{}".format(ip)).network)
+ ip_net = str(ipaddress.ip_interface("{}".format(ip)).network)
ospf_summ_r1 = {
"r0": {
"ospf6": {
@@ -1428,6 +1428,10 @@ def ospfv3_type5_summary_tc45_p0(request):
result = create_interfaces_cfg(tgen, input_dict)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ # restart interface state machine on both routers to avoid stale state
+ tgen.net["r0"].cmd("clear ipv6 ospf6 interface")
+ tgen.net["r1"].cmd("clear ipv6 ospf6 interface")
+
ospf_covergence = verify_ospf6_neighbor(tgen, topo)
assert ospf_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf_covergence
diff --git a/tests/topotests/rip_allow_ecmp/r4/frr.conf b/tests/topotests/rip_allow_ecmp/r4/frr.conf
new file mode 100644
index 0000000000..995c2beca9
--- /dev/null
+++ b/tests/topotests/rip_allow_ecmp/r4/frr.conf
@@ -0,0 +1,13 @@
+!
+int lo
+ ip address 10.10.10.1/32
+!
+int r4-eth0
+ ip address 192.168.1.4/24
+!
+router rip
+ network 192.168.1.0/24
+ network 10.10.10.1/32
+ timers basic 5 15 10
+exit
+
diff --git a/tests/topotests/rip_allow_ecmp/r5/frr.conf b/tests/topotests/rip_allow_ecmp/r5/frr.conf
new file mode 100644
index 0000000000..57a06ec0e3
--- /dev/null
+++ b/tests/topotests/rip_allow_ecmp/r5/frr.conf
@@ -0,0 +1,13 @@
+!
+int lo
+ ip address 10.10.10.1/32
+!
+int r5-eth0
+ ip address 192.168.1.5/24
+!
+router rip
+ network 192.168.1.0/24
+ network 10.10.10.1/32
+ timers basic 5 15 10
+exit
+
diff --git a/tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py b/tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py
index acc0aea9e8..7d958fd496 100644
--- a/tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py
+++ b/tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py
@@ -21,12 +21,13 @@ sys.path.append(os.path.join(CWD, "../"))
# pylint: disable=C0413
from lib import topotest
from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.common_config import step
pytestmark = [pytest.mark.ripd]
def setup_module(mod):
- topodef = {"s1": ("r1", "r2", "r3")}
+ topodef = {"s1": ("r1", "r2", "r3", "r4", "r5")}
tgen = Topogen(topodef, mod.__name__)
tgen.start_topology()
@@ -102,11 +103,13 @@ def test_rip_allow_ecmp():
_, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
assert result is None, "Can't see 10.10.10.1/32 as multipath in `show ip rip`"
- def _show_routes():
+ def _show_routes(nh_num):
output = json.loads(r1.vtysh_cmd("show ip route json"))
expected = {
"10.10.10.1/32": [
{
+ "internalNextHopNum": nh_num,
+ "internalNextHopActiveNum": nh_num,
"nexthops": [
{
"ip": "192.168.1.2",
@@ -116,15 +119,36 @@ def test_rip_allow_ecmp():
"ip": "192.168.1.3",
"active": True,
},
- ]
+ ],
}
]
}
return topotest.json_cmp(output, expected)
- test_func = functools.partial(_show_routes)
+ test_func = functools.partial(_show_routes, 4)
_, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
- assert result is None, "Can't see 10.10.10.1/32 as multipath in `show ip route`"
+ assert result is None, "Can't see 10.10.10.1/32 as multipath (4) in `show ip route`"
+
+ step(
+ "Configure allow-ecmp 2, ECMP group routes SHOULD have next-hops with the lowest IPs"
+ )
+ r1.vtysh_cmd(
+ """
+ configure terminal
+ router rip
+ allow-ecmp 2
+ """
+ )
+
+ test_func = functools.partial(_show_rip_routes)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+ assert (
+ result is None
+ ), "Can't see 10.10.10.1/32 as ECMP with the lowest next-hop IPs"
+
+ test_func = functools.partial(_show_routes, 2)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+ assert result is None, "Can't see 10.10.10.1/32 as multipath (2) in `show ip route`"
if __name__ == "__main__":
diff --git a/yang/example/ripd.json b/yang/example/ripd.json
index 00040622e5..799f46a6dc 100644
--- a/yang/example/ripd.json
+++ b/yang/example/ripd.json
@@ -23,7 +23,7 @@
"instance": [
{
"vrf": "default",
- "allow-ecmp": "true",
+ "allow-ecmp": 1,
"distance": {
"source": [
{
diff --git a/yang/example/ripd.xml b/yang/example/ripd.xml
index 2feddde2d8..dad83619ce 100644
--- a/yang/example/ripd.xml
+++ b/yang/example/ripd.xml
@@ -19,7 +19,7 @@
<ripd xmlns="http://frrouting.org/yang/ripd">
<instance>
<vrf>default</vrf>
- <allow-ecmp>true</allow-ecmp>
+ <allow-ecmp>1</allow-ecmp>
<static-route>10.0.1.0/24</static-route>
<distance>
<source>
diff --git a/yang/frr-bgp-route-map.yang b/yang/frr-bgp-route-map.yang
index 23e5b0227c..b557cabb22 100644
--- a/yang/frr-bgp-route-map.yang
+++ b/yang/frr-bgp-route-map.yang
@@ -23,6 +23,10 @@ module frr-bgp-route-map {
prefix rt-types;
}
+ import frr-route-types {
+ prefix frr-route-types;
+ }
+
organization
"Free Range Routing";
contact
@@ -168,6 +172,12 @@ module frr-bgp-route-map {
"Match IPv6 next hop address";
}
+ identity source-protocol {
+ base frr-route-map:rmap-match-type;
+ description
+ "Match protocol via which the route was learnt";
+ }
+
identity distance {
base frr-route-map:rmap-set-type;
description
@@ -759,6 +769,13 @@ module frr-bgp-route-map {
"IPv6 address";
}
}
+
+ case source-protocol {
+ when "derived-from-or-self(../frr-route-map:condition, 'frr-bgp-route-map:source-protocol')";
+ leaf source-protocol {
+ type frr-route-types:frr-route-types;
+ }
+ }
}
augment "/frr-route-map:lib/frr-route-map:route-map/frr-route-map:entry/frr-route-map:set-action/frr-route-map:rmap-set-action/frr-route-map:set-action" {
diff --git a/yang/frr-ripd.yang b/yang/frr-ripd.yang
index a4bf50d958..5f85a4cabc 100644
--- a/yang/frr-ripd.yang
+++ b/yang/frr-ripd.yang
@@ -119,8 +119,8 @@ module frr-ripd {
"VRF name.";
}
leaf allow-ecmp {
- type boolean;
- default "false";
+ type uint8;
+ default 0;
description
"Allow equal-cost multi-path.";
}