summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss White <russ@riw.us>2023-06-20 09:08:28 -0400
committerGitHub <noreply@github.com>2023-06-20 09:08:28 -0400
commit56a10caa0376f587828f143d56c7c49c68b73b7d (patch)
treeb181df2cb414718578c137fd716205fc0463dc78
parent53a9aee61897028c80a2eb1e235e66d7c0e9ad3f (diff)
parent08a3439d5192af036a6f637106089d9e6521a982 (diff)
Merge pull request #12971 from taspelund/trey/mac_vrf_soo_upstream
bgpd: Add MAC-VRF Site-of-Origin support
-rw-r--r--bgpd/bgp_attr.c15
-rw-r--r--bgpd/bgp_attr.h2
-rw-r--r--bgpd/bgp_ecommunity.c15
-rw-r--r--bgpd/bgp_ecommunity.h2
-rw-r--r--bgpd/bgp_evpn.c504
-rw-r--r--bgpd/bgp_evpn.h11
-rw-r--r--bgpd/bgp_evpn_private.h9
-rw-r--r--bgpd/bgp_evpn_vty.c200
-rw-r--r--bgpd/bgp_mac.c19
-rw-r--r--bgpd/bgp_nexthop.h5
-rw-r--r--bgpd/bgp_packet.c1
-rw-r--r--bgpd/bgp_zebra.c6
-rw-r--r--bgpd/bgpd.c17
-rw-r--r--bgpd/bgpd.h20
-rw-r--r--doc/user/bgp.rst71
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/bgpd.conf1
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/ospfd.conf13
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/zebra.conf7
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgp.l2vpn.evpn.vni.json19
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgpd.conf18
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/evpn.vni.json17
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/ospfd.conf9
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/zebra.conf8
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgp.l2vpn.evpn.vni.json19
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgpd.conf18
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/evpn.vni.json16
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/ospfd.conf9
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/zebra.conf6
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/bgpd.conf1
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/ospfd.conf1
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/zebra.conf3
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/bgpd.conf1
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/ospfd.conf1
-rw-r--r--tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/zebra.conf3
-rwxr-xr-xtests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/test_bgp_evpn_vxlan_macvrf_soo.py839
35 files changed, 1784 insertions, 122 deletions
diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c
index ec9f12d61a..221605d985 100644
--- a/bgpd/bgp_attr.c
+++ b/bgpd/bgp_attr.c
@@ -5189,3 +5189,18 @@ enum bgp_attr_parse_ret bgp_attr_ignore(struct peer *peer, uint8_t type)
return withdraw ? BGP_ATTR_PARSE_WITHDRAW : BGP_ATTR_PARSE_PROCEED;
}
+
+bool route_matches_soo(struct bgp_path_info *pi, struct ecommunity *soo)
+{
+ struct attr *attr = pi->attr;
+ struct ecommunity *ecom;
+
+ if (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)))
+ return false;
+
+ ecom = attr->ecommunity;
+ if (!ecom || !ecom->size)
+ return false;
+
+ return soo_in_ecom(ecom, soo);
+}
diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h
index 1c7e88a4f9..6cd34d301c 100644
--- a/bgpd/bgp_attr.h
+++ b/bgpd/bgp_attr.h
@@ -637,4 +637,6 @@ bgp_attr_set_vnc_subtlvs(struct attr *attr,
#endif
}
+extern bool route_matches_soo(struct bgp_path_info *pi, struct ecommunity *soo);
+
#endif /* _QUAGGA_BGP_ATTR_H */
diff --git a/bgpd/bgp_ecommunity.c b/bgpd/bgp_ecommunity.c
index a555930137..29b2250747 100644
--- a/bgpd/bgp_ecommunity.c
+++ b/bgpd/bgp_ecommunity.c
@@ -1765,3 +1765,18 @@ struct ecommunity *ecommunity_replace_linkbw(as_t as, struct ecommunity *ecom,
return new;
}
+
+bool soo_in_ecom(struct ecommunity *ecom, struct ecommunity *soo)
+{
+ if (ecom && soo) {
+ if ((ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_AS,
+ ECOMMUNITY_SITE_ORIGIN) ||
+ ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_AS4,
+ ECOMMUNITY_SITE_ORIGIN) ||
+ ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_IP,
+ ECOMMUNITY_SITE_ORIGIN)) &&
+ ecommunity_include(ecom, soo))
+ return true;
+ }
+ return false;
+}
diff --git a/bgpd/bgp_ecommunity.h b/bgpd/bgp_ecommunity.h
index 94a178bbb6..d62dc2e84c 100644
--- a/bgpd/bgp_ecommunity.h
+++ b/bgpd/bgp_ecommunity.h
@@ -360,6 +360,8 @@ extern struct ecommunity *ecommunity_replace_linkbw(as_t as,
uint64_t cum_bw,
bool disable_ieee_floating);
+extern bool soo_in_ecom(struct ecommunity *ecom, struct ecommunity *soo);
+
static inline void ecommunity_strip_rts(struct ecommunity *ecom)
{
uint8_t subtype = ECOMMUNITY_ROUTE_TARGET;
diff --git a/bgpd/bgp_evpn.c b/bgpd/bgp_evpn.c
index fc8889c175..ef1817b479 100644
--- a/bgpd/bgp_evpn.c
+++ b/bgpd/bgp_evpn.c
@@ -41,6 +41,7 @@
#include "bgpd/bgp_nht.h"
#include "bgpd/bgp_trace.h"
#include "bgpd/bgp_mpath.h"
+#include "bgpd/bgp_packet.h"
/*
* Definitions and external declarations.
@@ -48,6 +49,7 @@
DEFINE_QOBJ_TYPE(bgpevpn);
DEFINE_QOBJ_TYPE(bgp_evpn_es);
+DEFINE_MTYPE_STATIC(BGPD, BGP_EVPN_INFO, "BGP EVPN instance information");
DEFINE_MTYPE_STATIC(BGPD, VRF_ROUTE_TARGET, "L3 Route Target");
/*
@@ -1050,7 +1052,8 @@ static void build_evpn_type5_route_extcomm(struct bgp *bgp_vrf,
* type-2 routes.
*/
static void build_evpn_route_extcomm(struct bgpevpn *vpn, struct attr *attr,
- int add_l3_ecomm)
+ int add_l3_ecomm,
+ struct ecommunity *macvrf_soo)
{
struct ecommunity ecom_encap;
struct ecommunity ecom_sticky;
@@ -1147,6 +1150,11 @@ static void build_evpn_route_extcomm(struct bgpevpn *vpn, struct attr *attr,
attr, ecommunity_merge(bgp_attr_get_ecommunity(attr),
&ecom_na));
}
+
+ /* Add MAC-VRF SoO, if configured */
+ if (macvrf_soo)
+ bgp_attr_set_ecommunity(
+ attr, ecommunity_merge(attr->ecommunity, macvrf_soo));
}
/*
@@ -2068,6 +2076,7 @@ static int update_evpn_route(struct bgp *bgp, struct bgpevpn *vpn,
int route_change;
bool old_is_sync = false;
bool mac_only = false;
+ struct ecommunity *macvrf_soo = NULL;
memset(&attr, 0, sizeof(attr));
@@ -2125,8 +2134,11 @@ static int update_evpn_route(struct bgp *bgp, struct bgpevpn *vpn,
add_l3_ecomm = bgp_evpn_route_add_l3_ecomm_ok(
vpn, p, (attr.es_flags & ATTR_ES_IS_LOCAL) ? &attr.esi : NULL);
+ if (bgp->evpn_info)
+ macvrf_soo = bgp->evpn_info->soo;
+
/* Set up extended community. */
- build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm);
+ build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm, macvrf_soo);
/* First, create (or fetch) route node within the VNI.
* NOTE: There is no RD here.
@@ -2333,6 +2345,7 @@ void bgp_evpn_update_type2_route_entry(struct bgp *bgp, struct bgpevpn *vpn,
struct prefix_evpn evp;
int route_change;
bool old_is_sync = false;
+ struct ecommunity *macvrf_soo = NULL;
if (CHECK_FLAG(local_pi->flags, BGP_PATH_REMOVED))
return;
@@ -2380,8 +2393,11 @@ void bgp_evpn_update_type2_route_entry(struct bgp *bgp, struct bgpevpn *vpn,
vpn, &evp,
(attr.es_flags & ATTR_ES_IS_LOCAL) ? &attr.esi : NULL);
+ if (bgp->evpn_info)
+ macvrf_soo = bgp->evpn_info->soo;
+
/* Set up extended community. */
- build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm);
+ build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm, macvrf_soo);
seq = mac_mobility_seqnum(local_pi->attr);
if (bgp_debug_zebra(NULL)) {
@@ -2673,6 +2689,21 @@ int update_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn)
return 0;
}
+/* Update Type-2/3 Routes for L2VNI.
+ * Called by hash_iterate()
+ */
+static void update_routes_for_vni_hash(struct hash_bucket *bucket,
+ struct bgp *bgp)
+{
+ struct bgpevpn *vpn;
+
+ if (!bucket)
+ return;
+
+ vpn = (struct bgpevpn *)bucket->data;
+ update_routes_for_vni(bgp, vpn);
+}
+
/*
* Delete (and withdraw) local routes for specified VNI from the global
* table and per-VNI table. After this, remove all other routes from
@@ -2720,43 +2751,60 @@ static int bgp_evpn_mcast_grp_change(struct bgp *bgp, struct bgpevpn *vpn,
}
/*
- * There is a tunnel endpoint IP address change for this VNI, delete
- * prior type-3 route (if needed) and update.
+ * If there is a tunnel endpoint IP address (VTEP-IP) change for this VNI.
+ - Deletes tip_hash entry for old VTEP-IP
+ - Adds tip_hash entry/refcount for new VTEP-IP
+ - Deletes prior type-3 route for L2VNI (if needed)
+ - Updates originator_ip
* Note: Route re-advertisement happens elsewhere after other processing
* other changes.
*/
-static void handle_tunnel_ip_change(struct bgp *bgp, struct bgpevpn *vpn,
+static void handle_tunnel_ip_change(struct bgp *bgp_vrf, struct bgp *bgp_evpn,
+ struct bgpevpn *vpn,
struct in_addr originator_ip)
{
struct prefix_evpn p;
+ struct in_addr old_vtep_ip;
+
+ if (bgp_vrf) /* L3VNI */
+ old_vtep_ip = bgp_vrf->originator_ip;
+ else /* L2VNI */
+ old_vtep_ip = vpn->originator_ip;
- if (IPV4_ADDR_SAME(&vpn->originator_ip, &originator_ip))
+ /* TIP didn't change, nothing to do */
+ if (IPV4_ADDR_SAME(&old_vtep_ip, &originator_ip))
return;
- /* If VNI is not live, we only need to update the originator ip */
- if (!is_vni_live(vpn)) {
+ /* If L2VNI is not live, we only need to update the originator_ip.
+ * L3VNIs are updated immediately, so we can't bail out early.
+ */
+ if (!bgp_vrf && !is_vni_live(vpn)) {
vpn->originator_ip = originator_ip;
return;
}
/* Update the tunnel-ip hash */
- bgp_tip_del(bgp, &vpn->originator_ip);
- if (bgp_tip_add(bgp, &originator_ip))
+ bgp_tip_del(bgp_evpn, &old_vtep_ip);
+ if (bgp_tip_add(bgp_evpn, &originator_ip))
/* The originator_ip was not already present in the
* bgp martian next-hop table as a tunnel-ip, so we
* need to go back and filter routes matching the new
* martian next-hop.
*/
- bgp_filter_evpn_routes_upon_martian_nh_change(bgp);
+ bgp_filter_evpn_routes_upon_martian_change(bgp_evpn,
+ BGP_MARTIAN_TUN_IP);
- /* Need to withdraw type-3 route as the originator IP is part
- * of the key.
- */
- build_evpn_type3_prefix(&p, vpn->originator_ip);
- delete_evpn_route(bgp, vpn, &p);
+ if (!bgp_vrf) {
+ /* Need to withdraw type-3 route as the originator IP is part
+ * of the key.
+ */
+ build_evpn_type3_prefix(&p, vpn->originator_ip);
+ delete_evpn_route(bgp_evpn, vpn, &p);
+
+ vpn->originator_ip = originator_ip;
+ } else
+ bgp_vrf->originator_ip = originator_ip;
- /* Update the tunnel IP and re-advertise all routes for this VNI. */
- vpn->originator_ip = originator_ip;
return;
}
@@ -3366,7 +3414,7 @@ static int uninstall_evpn_route_entry(struct bgp *bgp, struct bgpevpn *vpn,
/*
* Given a route entry and a VRF, see if this route entry should be
- * imported into the VRF i.e., RTs match.
+ * imported into the VRF i.e., RTs match + Site-of-Origin check passes.
*/
static int is_route_matching_for_vrf(struct bgp *bgp_vrf,
struct bgp_path_info *pi)
@@ -3498,6 +3546,41 @@ static int is_route_matching_for_vni(struct bgp *bgp, struct bgpevpn *vpn,
return 0;
}
+static bool bgp_evpn_route_matches_macvrf_soo(struct bgp_path_info *pi,
+ const struct prefix_evpn *evp)
+{
+ struct bgp *bgp_evpn = bgp_get_evpn();
+ struct ecommunity *macvrf_soo;
+ bool ret = false;
+
+ if (!bgp_evpn->evpn_info)
+ return false;
+
+ /* We only stamp the mac-vrf soo on routes from our local L2VNI.
+ * No need to filter additional EVPN routes that originated outside
+ * the MAC-VRF/L2VNI.
+ */
+ if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE &&
+ evp->prefix.route_type != BGP_EVPN_IMET_ROUTE)
+ return false;
+
+ macvrf_soo = bgp_evpn->evpn_info->soo;
+ ret = route_matches_soo(pi, macvrf_soo);
+
+ if (ret && bgp_debug_zebra(NULL)) {
+ char *ecom_str;
+
+ ecom_str = ecommunity_ecom2str(macvrf_soo,
+ ECOMMUNITY_FORMAT_ROUTE_MAP, 0);
+ zlog_debug(
+ "import of evpn prefix %pFX skipped, local mac-vrf soo %s",
+ evp, ecom_str);
+ ecommunity_strfree(&ecom_str);
+ }
+
+ return ret;
+}
+
/* This API will scan evpn routes for checking attribute's rmac
* macthes with bgp instance router mac. It avoid installing
* route into bgp vrf table and remote rmac in bridge table.
@@ -3583,8 +3666,9 @@ int bgp_evpn_route_entry_install_if_vrf_match(struct bgp *bgp_vrf,
return 0;
/* don't import hosts that are locally attached */
- if (install && bgp_evpn_skip_vrf_import_of_local_es(
- bgp_vrf, evp, pi, install))
+ if (install && (bgp_evpn_skip_vrf_import_of_local_es(
+ bgp_vrf, evp, pi, install) ||
+ bgp_evpn_route_matches_macvrf_soo(pi, evp)))
return 0;
if (install)
@@ -3713,30 +3797,35 @@ static int install_uninstall_routes_for_vni(struct bgp *bgp,
&& pi->sub_type == BGP_ROUTE_NORMAL))
continue;
- if (is_route_matching_for_vni(bgp, vpn, pi)) {
- if (install)
- ret = install_evpn_route_entry(
- bgp, vpn, evp, pi);
- else
- ret = uninstall_evpn_route_entry(
- bgp, vpn, evp, pi);
-
- if (ret) {
- flog_err(
- EC_BGP_EVPN_FAIL,
- "%u: Failed to %s EVPN %s route in VNI %u",
- bgp->vrf_id,
- install ? "install"
- : "uninstall",
- rtype == BGP_EVPN_MAC_IP_ROUTE
- ? "MACIP"
- : "IMET",
- vpn->vni);
-
- bgp_dest_unlock_node(rd_dest);
- bgp_dest_unlock_node(dest);
- return ret;
- }
+ if (!is_route_matching_for_vni(bgp, vpn, pi))
+ continue;
+
+ if (install) {
+ if (bgp_evpn_route_matches_macvrf_soo(
+ pi, evp))
+ continue;
+
+ ret = install_evpn_route_entry(bgp, vpn,
+ evp, pi);
+ } else
+ ret = uninstall_evpn_route_entry(
+ bgp, vpn, evp, pi);
+
+ if (ret) {
+ flog_err(
+ EC_BGP_EVPN_FAIL,
+ "%u: Failed to %s EVPN %s route in VNI %u",
+ bgp->vrf_id,
+ install ? "install"
+ : "uninstall",
+ rtype == BGP_EVPN_MAC_IP_ROUTE
+ ? "MACIP"
+ : "IMET",
+ vpn->vni);
+
+ bgp_dest_unlock_node(rd_dest);
+ bgp_dest_unlock_node(dest);
+ return ret;
}
}
}
@@ -3942,6 +4031,12 @@ static int bgp_evpn_install_uninstall_table(struct bgp *bgp, afi_t afi,
if (!ecom || !ecom->size)
return -1;
+ /* Filter routes carrying a Site-of-Origin that matches our
+ * local MAC-VRF SoO.
+ */
+ if (import && bgp_evpn_route_matches_macvrf_soo(pi, evp))
+ return 0;
+
/* An EVPN route belongs to a VNI or a VRF or an ESI based on the RTs
* attached to the route */
for (i = 0; i < ecom->size; i++) {
@@ -5489,6 +5584,46 @@ void bgp_evpn_handle_rd_change(struct bgp *bgp, struct bgpevpn *vpn,
update_advertise_vni_routes(bgp, vpn);
}
+/* "mac-vrf soo" vty handler
+ * Handle change to the global MAC-VRF Site-of-Origin:
+ * - Unimport routes with new SoO from VNI/VRF
+ * - Import routes with old SoO into VNI/VRF
+ * - Update SoO on local VNI routes + re-advertise
+ */
+void bgp_evpn_handle_global_macvrf_soo_change(struct bgp *bgp,
+ struct ecommunity *new_soo)
+{
+ struct ecommunity *old_soo;
+
+ old_soo = bgp->evpn_info->soo;
+
+ /* cleanup and bail out if old_soo == new_soo */
+ if (ecommunity_match(old_soo, new_soo)) {
+ ecommunity_free(&new_soo);
+ return;
+ }
+
+ /* set new_soo */
+ bgp->evpn_info->soo = new_soo;
+
+ /* Unimport routes matching the new_soo */
+ bgp_filter_evpn_routes_upon_martian_change(bgp, BGP_MARTIAN_SOO);
+
+ /* Reimport routes with old_soo and !new_soo.
+ */
+ bgp_reimport_evpn_routes_upon_martian_change(
+ bgp, BGP_MARTIAN_SOO, (void *)old_soo, (void *)new_soo);
+
+ /* Update locally originated routes for all L2VNIs */
+ hash_iterate(bgp->vnihash,
+ (void (*)(struct hash_bucket *,
+ void *))update_routes_for_vni_hash,
+ bgp);
+
+ /* clear old_soo */
+ ecommunity_free(&old_soo);
+}
+
/*
* Install routes for this VNI. Invoked upon change to Import RT.
*/
@@ -6056,8 +6191,12 @@ int bgp_evpn_unimport_route(struct bgp *bgp, afi_t afi, safi_t safi,
return install_uninstall_evpn_route(bgp, afi, safi, p, pi, 0);
}
-/* filter routes which have martian next hops */
-int bgp_filter_evpn_routes_upon_martian_nh_change(struct bgp *bgp)
+/* Refresh previously-discarded EVPN routes carrying "self" MAC-VRF SoO.
+ * Walk global EVPN rib + import remote routes with old_soo && !new_soo.
+ */
+void bgp_reimport_evpn_routes_upon_macvrf_soo_change(struct bgp *bgp,
+ struct ecommunity *old_soo,
+ struct ecommunity *new_soo)
{
afi_t afi;
safi_t safi;
@@ -6068,12 +6207,9 @@ int bgp_filter_evpn_routes_upon_martian_nh_change(struct bgp *bgp)
afi = AFI_L2VPN;
safi = SAFI_EVPN;
- /* Walk entire global routing table and evaluate routes which could be
- * imported into this VPN. Note that we cannot just look at the routes
- * for the VNI's RD -
- * remote routes applicable for this VNI could have any RD.
+ /* EVPN routes are a 2-level table: outer=prefix_rd, inner=prefix_evpn.
+ * A remote route could have any RD, so we need to walk them all.
*/
- /* EVPN routes are a 2-level table. */
for (rd_dest = bgp_table_top(bgp->rib[afi][safi]); rd_dest;
rd_dest = bgp_route_next(rd_dest)) {
table = bgp_dest_get_bgp_table_info(rd_dest);
@@ -6082,21 +6218,132 @@ int bgp_filter_evpn_routes_upon_martian_nh_change(struct bgp *bgp)
for (dest = bgp_table_top(table); dest;
dest = bgp_route_next(dest)) {
+ const struct prefix *p;
+ struct prefix_evpn *evp;
+
+ p = bgp_dest_get_prefix(dest);
+ evp = (struct prefix_evpn *)p;
+
+ /* On export we only add MAC-VRF SoO to RT-2/3, so we
+ * can skip evaluation of other RTs.
+ */
+ if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE &&
+ evp->prefix.route_type != BGP_EVPN_IMET_ROUTE)
+ continue;
for (pi = bgp_dest_get_bgp_path_info(dest); pi;
pi = pi->next) {
+ bool old_soo_fnd = false;
+ bool new_soo_fnd = false;
- /* Consider "valid" remote routes applicable for
- * this VNI. */
+ /* Only consider routes learned from peers */
+ if (!(pi->type == ZEBRA_ROUTE_BGP &&
+ pi->sub_type == BGP_ROUTE_NORMAL))
+ continue;
+
+ if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID))
+ continue;
+
+ old_soo_fnd = route_matches_soo(pi, old_soo);
+ new_soo_fnd = route_matches_soo(pi, new_soo);
+
+ if (old_soo_fnd && !new_soo_fnd) {
+ if (bgp_debug_update(pi->peer, p, NULL,
+ 1)) {
+ char attr_str[BUFSIZ] = {0};
+
+ bgp_dump_attr(pi->attr,
+ attr_str, BUFSIZ);
+
+ zlog_debug(
+ "mac-vrf soo changed: evaluating reimport of prefix %pBD with attr %s",
+ dest, attr_str);
+ }
+
+ bgp_evpn_import_route(bgp, afi, safi, p,
+ pi);
+ }
+ }
+ }
+ }
+}
+
+/* Filter learned (!local) EVPN routes carrying "self" attributes.
+ * Walk the Global EVPN loc-rib unimporting martian routes from the appropriate
+ * L2VNIs (MAC-VRFs) / L3VNIs (IP-VRFs), and deleting them from the Global
+ * loc-rib when applicable (based on martian_type).
+ * This function is the handler for new martian entries, which is triggered by
+ * events occurring on the local system,
+ * e.g.
+ * - New VTEP-IP
+ * + bgp_zebra_process_local_vni
+ * + bgp_zebra_process_local_l3vni
+ * - New MAC-VRF Site-of-Origin
+ * + bgp_evpn_handle_global_macvrf_soo_change
+ * This will likely be extended in the future to cover these events too:
+ * - New Interface IP
+ * + bgp_interface_address_add
+ * - New Interface MAC
+ * + bgp_ifp_up
+ * + bgp_ifp_create
+ * - New RMAC
+ * + bgp_zebra_process_local_l3vni
+ */
+void bgp_filter_evpn_routes_upon_martian_change(
+ struct bgp *bgp, enum bgp_martian_type martian_type)
+{
+ afi_t afi;
+ safi_t safi;
+ struct bgp_dest *rd_dest, *dest;
+ struct bgp_table *table;
+ struct bgp_path_info *pi;
+ struct ecommunity *macvrf_soo;
+
+ afi = AFI_L2VPN;
+ safi = SAFI_EVPN;
+ macvrf_soo = bgp->evpn_info->soo;
+
+ /* EVPN routes are a 2-level table: outer=prefix_rd, inner=prefix_evpn.
+ * A remote route could have any RD, so we need to walk them all.
+ */
+ for (rd_dest = bgp_table_top(bgp->rib[afi][safi]); rd_dest;
+ rd_dest = bgp_route_next(rd_dest)) {
+ table = bgp_dest_get_bgp_table_info(rd_dest);
+ if (!table)
+ continue;
+
+ for (dest = bgp_table_top(table); dest;
+ dest = bgp_route_next(dest)) {
+
+ for (pi = bgp_dest_get_bgp_path_info(dest); pi;
+ pi = pi->next) {
+ bool affected = false;
+ const struct prefix *p;
+
+ /* Only consider routes learned from peers */
if (!(pi->type == ZEBRA_ROUTE_BGP
&& pi->sub_type == BGP_ROUTE_NORMAL))
continue;
- if (bgp_nexthop_self(bgp, afi, pi->type,
- pi->sub_type, pi->attr,
- dest)) {
- const struct prefix *p =
- bgp_dest_get_prefix(dest);
+ p = bgp_dest_get_prefix(dest);
+
+ switch (martian_type) {
+ case BGP_MARTIAN_TUN_IP:
+ affected = bgp_nexthop_self(
+ bgp, afi, pi->type,
+ pi->sub_type, pi->attr, dest);
+ break;
+ case BGP_MARTIAN_SOO:
+ affected = route_matches_soo(
+ pi, macvrf_soo);
+ break;
+ case BGP_MARTIAN_IF_IP:
+ case BGP_MARTIAN_IF_MAC:
+ case BGP_MARTIAN_RMAC:
+ break;
+ }
+
+ if (affected) {
if (bgp_debug_update(pi->peer, p, NULL,
1)) {
char attr_str[BUFSIZ] = {0};
@@ -6106,21 +6353,116 @@ int bgp_filter_evpn_routes_upon_martian_nh_change(struct bgp *bgp)
sizeof(attr_str));
zlog_debug(
- "%u: prefix %pBD with attr %s - DENIED due to martian or self nexthop",
+ "%u: prefix %pBD with attr %s - DISCARDED due to Martian/%s",
bgp->vrf_id, dest,
- attr_str);
+ attr_str,
+ bgp_martian_type2str(
+ martian_type));
}
+
+
bgp_evpn_unimport_route(bgp, afi, safi,
p, pi);
- bgp_rib_remove(dest, pi, pi->peer, afi,
- safi);
+ /* For now, retain existing handling of
+ * tip_hash updates: (Self SoO routes
+ * are unimported from L2VNI/VRF but
+ * retained in global loc-rib, but Self
+ * IP/MAC routes are also deleted from
+ * global loc-rib).
+ * TODO: use consistent handling for all
+ * martian types
+ */
+ if (martian_type == BGP_MARTIAN_TUN_IP)
+ bgp_rib_remove(dest, pi,
+ pi->peer, afi,
+ safi);
}
}
}
}
+}
- return 0;
+/* Refresh previously-discarded EVPN routes carrying "self" attributes.
+ * This function is the handler for deleted martian entries, which is triggered
+ * by events occurring on the local system,
+ * e.g.
+ * - Del MAC-VRF Site-of-Origin
+ * + bgp_evpn_handle_global_macvrf_soo_change
+ * This will likely be extended in the future to cover these events too:
+ * - Del VTEP-IP
+ * + bgp_zebra_process_local_vni
+ * + bgp_zebra_process_local_l3vni
+ * - Del Interface IP
+ * + bgp_interface_address_delete
+ * - Del Interface MAC
+ * + bgp_ifp_down
+ * + bgp_ifp_destroy
+ * - Del RMAC
+ * + bgp_zebra_process_local_l3vni
+ */
+void bgp_reimport_evpn_routes_upon_martian_change(
+ struct bgp *bgp, enum bgp_martian_type martian_type, void *old_martian,
+ void *new_martian)
+{
+ struct listnode *node;
+ struct peer *peer;
+ safi_t safi;
+ afi_t afi;
+ struct ecommunity *old_soo, *new_soo;
+
+ afi = AFI_L2VPN;
+ safi = SAFI_EVPN;
+
+ /* Self-SoO routes are held in the global EVPN loc-rib, so we can
+ * reimport routes w/o triggering soft-reconfig/route-refresh.
+ */
+ if (martian_type == BGP_MARTIAN_SOO) {
+ old_soo = (struct ecommunity *)old_martian;
+ new_soo = (struct ecommunity *)new_martian;
+
+ /* If !old_soo, then we can skip the reimport because we
+ * wouldn't have filtered anything via the self-SoO import check
+ */
+ if (old_martian)
+ bgp_reimport_evpn_routes_upon_macvrf_soo_change(
+ bgp, old_soo, new_soo);
+
+ return;
+ }
+
+ /* Self-TIP/IP/MAC/RMAC routes are deleted from the global EVPN
+ * loc-rib, so we need to re-learn the routes via soft-reconfig/
+ * route-refresh.
+ */
+ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) {
+
+ if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP))
+ continue;
+
+ if (peer->status != Established)
+ continue;
+
+ if (CHECK_FLAG(peer->af_flags[afi][safi],
+ PEER_FLAG_SOFT_RECONFIG)) {
+ if (bgp_debug_update(peer, NULL, NULL, 1))
+ zlog_debug(
+ "Processing EVPN Martian/%s change on peer %s (inbound, soft-reconfig)",
+ bgp_martian_type2str(martian_type),
+ peer->host);
+
+ bgp_soft_reconfig_in(peer, afi, safi);
+ } else {
+ if (bgp_debug_update(peer, NULL, NULL, 1))
+ zlog_debug(
+ "Processing EVPN Martian/%s change on peer %s",
+ bgp_martian_type2str(martian_type),
+ peer->host);
+ bgp_route_refresh_send(peer, afi, safi, 0,
+ REFRESH_IMMEDIATE, 0,
+ BGP_ROUTE_REFRESH_NORMAL);
+ }
+ }
}
/*
@@ -6269,10 +6611,14 @@ int bgp_evpn_local_l3vni_add(vni_t l3vni, vrf_id_t vrf_id,
/* associate the vrf with l3vni and related parameters */
bgp_vrf->l3vni = l3vni;
- bgp_vrf->originator_ip = originator_ip;
bgp_vrf->l3vni_svi_ifindex = svi_ifindex;
bgp_vrf->evpn_info->is_anycast_mac = is_anycast_mac;
+ /* Update tip_hash of the EVPN underlay BGP instance (bgp_evpn)
+ * if the VTEP-IP (originator_ip) has changed
+ */
+ handle_tunnel_ip_change(bgp_vrf, bgp_evpn, vpn, originator_ip);
+
/* copy anycast MAC from VRR MAC */
memcpy(&bgp_vrf->rmac, vrr_rmac, ETH_ALEN);
/* copy sys RMAC from SVI MAC */
@@ -6397,6 +6743,11 @@ int bgp_evpn_local_l3vni_del(vni_t l3vni, vrf_id_t vrf_id)
/* delete/withdraw all type-5 routes */
delete_withdraw_vrf_routes(bgp_vrf);
+ /* Tunnel is no longer active.
+ * Delete VTEP-IP from EVPN underlay's tip_hash.
+ */
+ bgp_tip_del(bgp_evpn, &bgp_vrf->originator_ip);
+
/* remove the l3vni from vrf instance */
bgp_vrf->l3vni = 0;
@@ -6461,8 +6812,8 @@ int bgp_evpn_local_vni_del(struct bgp *bgp, vni_t vni)
bgp_evpn_unlink_from_vni_svi_hash(bgp, vpn);
vpn->svi_ifindex = 0;
- /*
- * tunnel is no longer active, del tunnel ip address from tip_hash
+ /* Tunnel is no longer active.
+ * Delete VTEP-IP from EVPN underlay's tip_hash.
*/
bgp_tip_del(bgp, &vpn->originator_ip);
@@ -6486,6 +6837,7 @@ int bgp_evpn_local_vni_add(struct bgp *bgp, vni_t vni,
{
struct bgpevpn *vpn;
struct prefix_evpn p;
+ struct bgp *bgp_evpn = bgp_get_evpn();
/* Lookup VNI. If present and no change, exit. */
vpn = bgp_evpn_lookup_vni(bgp, vni);
@@ -6558,7 +6910,7 @@ int bgp_evpn_local_vni_add(struct bgp *bgp, vni_t vni,
/* If tunnel endpoint IP has changed, update (and delete prior
* type-3 route, if needed.)
*/
- handle_tunnel_ip_change(bgp, vpn, originator_ip);
+ handle_tunnel_ip_change(NULL, bgp, vpn, originator_ip);
/* Update all routes with new endpoint IP and/or export RT
* for VRFs
@@ -6578,14 +6930,17 @@ int bgp_evpn_local_vni_add(struct bgp *bgp, vni_t vni,
/* Mark as "live" */
SET_FLAG(vpn->flags, VNI_FLAG_LIVE);
- /* tunnel is now active, add tunnel-ip to db */
+ /* Tunnel is newly active.
+ * Add TIP to tip_hash of the EVPN underlay instance (bgp_get_evpn()).
+ */
if (bgp_tip_add(bgp, &originator_ip))
/* The originator_ip was not already present in the
* bgp martian next-hop table as a tunnel-ip, so we
* need to go back and filter routes matching the new
* martian next-hop.
*/
- bgp_filter_evpn_routes_upon_martian_nh_change(bgp);
+ bgp_filter_evpn_routes_upon_martian_change(bgp_evpn,
+ BGP_MARTIAN_TUN_IP);
/*
* Create EVPN type-3 route and schedule for processing.
@@ -6679,6 +7034,11 @@ void bgp_evpn_cleanup(struct bgp *bgp)
list_delete(&bgp->vrf_export_rtl);
list_delete(&bgp->l2vnis);
+ if (bgp->evpn_info) {
+ ecommunity_free(&bgp->evpn_info->soo);
+ XFREE(MTYPE_BGP_EVPN_INFO, bgp->evpn_info);
+ }
+
if (bgp->vrf_prd_pretty)
XFREE(MTYPE_BGP, bgp->vrf_prd_pretty);
}
@@ -6712,6 +7072,8 @@ void bgp_evpn_init(struct bgp *bgp)
bgp->vrf_export_rtl->del = evpn_vrf_rt_del;
bgp->l2vnis = list_new();
bgp->l2vnis->cmp = vni_list_cmp;
+ bgp->evpn_info =
+ XCALLOC(MTYPE_BGP_EVPN_INFO, sizeof(struct bgp_evpn_info));
/* By default Duplicate Address Dection is enabled.
* Max-moves (N) 5, detection time (M) 180
* default action is warning-only
diff --git a/bgpd/bgp_evpn.h b/bgpd/bgp_evpn.h
index a034bfbd7e..076248c9f7 100644
--- a/bgpd/bgp_evpn.h
+++ b/bgpd/bgp_evpn.h
@@ -157,7 +157,16 @@ extern int bgp_evpn_import_route(struct bgp *bgp, afi_t afi, safi_t safi,
extern int bgp_evpn_unimport_route(struct bgp *bgp, afi_t afi, safi_t safi,
const struct prefix *p,
struct bgp_path_info *ri);
-extern int bgp_filter_evpn_routes_upon_martian_nh_change(struct bgp *bgp);
+extern void
+bgp_reimport_evpn_routes_upon_macvrf_soo_change(struct bgp *bgp,
+ struct ecommunity *old_soo,
+ struct ecommunity *new_soo);
+extern void bgp_reimport_evpn_routes_upon_martian_change(
+ struct bgp *bgp, enum bgp_martian_type martian_type, void *old_martian,
+ void *new_martian);
+extern void
+bgp_filter_evpn_routes_upon_martian_change(struct bgp *bgp,
+ enum bgp_martian_type martian_type);
extern int bgp_evpn_local_macip_del(struct bgp *bgp, vni_t vni,
struct ethaddr *mac, struct ipaddr *ip,
int state);
diff --git a/bgpd/bgp_evpn_private.h b/bgpd/bgp_evpn_private.h
index fd8d2c118f..8cee048b69 100644
--- a/bgpd/bgp_evpn_private.h
+++ b/bgpd/bgp_evpn_private.h
@@ -162,6 +162,13 @@ struct bgp_evpn_info {
/* EVPN enable - advertise svi macip routes */
int advertise_svi_macip;
+ /* MAC-VRF Site-of-Origin
+ * - added to all routes exported from L2VNI
+ * - Type-2/3 routes with matching SoO not imported into L2VNI
+ * - Type-2/5 routes with matching SoO not imported into L3VNI
+ */
+ struct ecommunity *soo;
+
/* PIP feature knob */
bool advertise_pip;
/* PIP IP (sys ip) */
@@ -680,6 +687,8 @@ extern void bgp_evpn_handle_autort_change(struct bgp *bgp);
extern void bgp_evpn_handle_vrf_rd_change(struct bgp *bgp_vrf, int withdraw);
extern void bgp_evpn_handle_rd_change(struct bgp *bgp, struct bgpevpn *vpn,
int withdraw);
+void bgp_evpn_handle_global_macvrf_soo_change(struct bgp *bgp,
+ struct ecommunity *new_soo);
extern int bgp_evpn_install_routes(struct bgp *bgp, struct bgpevpn *vpn);
extern int bgp_evpn_uninstall_routes(struct bgp *bgp, struct bgpevpn *vpn);
extern void bgp_evpn_map_vrf_to_its_rts(struct bgp *bgp_vrf);
diff --git a/bgpd/bgp_evpn_vty.c b/bgpd/bgp_evpn_vty.c
index 66079cad22..3a5047f152 100644
--- a/bgpd/bgp_evpn_vty.c
+++ b/bgpd/bgp_evpn_vty.c
@@ -362,10 +362,11 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf,
char *ecom_str;
struct listnode *node, *nnode;
struct vrf_route_target *l3rt;
+ struct bgp *bgp_evpn = NULL;
json_object *json_import_rtl = NULL;
json_object *json_export_rtl = NULL;
- char buf2[ETHER_ADDR_STRLEN];
+ bgp_evpn = bgp_get_evpn();
json_import_rtl = json_export_rtl = 0;
if (json) {
@@ -379,19 +380,26 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf,
&bgp_vrf->vrf_prd);
json_object_string_addf(json, "originatorIp", "%pI4",
&bgp_vrf->originator_ip);
+ if (bgp_evpn && bgp_evpn->evpn_info) {
+ ecom_str = ecommunity_ecom2str(
+ bgp_evpn->evpn_info->soo,
+ ECOMMUNITY_FORMAT_ROUTE_MAP, 0);
+ json_object_string_add(json, "siteOfOrigin", ecom_str);
+ ecommunity_strfree(&ecom_str);
+ }
json_object_string_add(json, "advertiseGatewayMacip", "n/a");
json_object_string_add(json, "advertiseSviMacIp", "n/a");
- json_object_string_add(json, "advertisePip",
- bgp_vrf->evpn_info->advertise_pip ?
- "Enabled" : "Disabled");
- json_object_string_addf(json, "sysIP", "%pI4",
- &bgp_vrf->evpn_info->pip_ip);
- json_object_string_add(json, "sysMac",
- prefix_mac2str(&bgp_vrf->evpn_info->pip_rmac,
- buf2, sizeof(buf2)));
- json_object_string_add(json, "rmac",
- prefix_mac2str(&bgp_vrf->rmac,
- buf2, sizeof(buf2)));
+ if (bgp_vrf && bgp_vrf->evpn_info) {
+ json_object_string_add(json, "advertisePip",
+ bgp_vrf->evpn_info->advertise_pip
+ ? "Enabled"
+ : "Disabled");
+ json_object_string_addf(json, "sysIP", "%pI4",
+ &bgp_vrf->evpn_info->pip_ip);
+ json_object_string_addf(json, "sysMac", "%pEA",
+ &bgp_vrf->evpn_info->pip_rmac);
+ }
+ json_object_string_addf(json, "rmac", "%pEA", &bgp_vrf->rmac);
} else {
vty_out(vty, "VNI: %d", bgp_vrf->l3vni);
vty_out(vty, " (known to the kernel)");
@@ -406,18 +414,26 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf,
vty_out(vty, "\n");
vty_out(vty, " Originator IP: %pI4\n",
&bgp_vrf->originator_ip);
+ if (bgp_evpn && bgp_evpn->evpn_info) {
+ ecom_str = ecommunity_ecom2str(
+ bgp_evpn->evpn_info->soo,
+ ECOMMUNITY_FORMAT_ROUTE_MAP, 0);
+ vty_out(vty, " MAC-VRF Site-of-Origin: %s\n",
+ ecom_str);
+ ecommunity_strfree(&ecom_str);
+ }
vty_out(vty, " Advertise-gw-macip : %s\n", "n/a");
vty_out(vty, " Advertise-svi-macip : %s\n", "n/a");
- vty_out(vty, " Advertise-pip: %s\n",
- bgp_vrf->evpn_info->advertise_pip ? "Yes" : "No");
- vty_out(vty, " System-IP: %pI4\n",
- &bgp_vrf->evpn_info->pip_ip);
- vty_out(vty, " System-MAC: %s\n",
- prefix_mac2str(&bgp_vrf->evpn_info->pip_rmac,
- buf2, sizeof(buf2)));
- vty_out(vty, " Router-MAC: %s\n",
- prefix_mac2str(&bgp_vrf->rmac,
- buf2, sizeof(buf2)));
+ if (bgp_vrf && bgp_vrf->evpn_info) {
+ vty_out(vty, " Advertise-pip: %s\n",
+ bgp_vrf->evpn_info->advertise_pip ? "Yes"
+ : "No");
+ vty_out(vty, " System-IP: %pI4\n",
+ &bgp_vrf->evpn_info->pip_ip);
+ vty_out(vty, " System-MAC: %pEA\n",
+ &bgp_vrf->evpn_info->pip_rmac);
+ }
+ vty_out(vty, " Router-MAC: %pEA\n", &bgp_vrf->rmac);
}
if (!json)
@@ -433,7 +449,7 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf,
else
vty_out(vty, " %s\n", ecom_str);
- XFREE(MTYPE_ECOMMUNITY_STR, ecom_str);
+ ecommunity_strfree(&ecom_str);
}
if (json)
@@ -451,7 +467,7 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf,
else
vty_out(vty, " %s\n", ecom_str);
- XFREE(MTYPE_ECOMMUNITY_STR, ecom_str);
+ ecommunity_strfree(&ecom_str);
}
if (json)
@@ -484,6 +500,13 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json)
&vpn->originator_ip);
json_object_string_addf(json, "mcastGroup", "%pI4",
&vpn->mcast_grp);
+ if (bgp_evpn && bgp_evpn->evpn_info) {
+ ecom_str = ecommunity_ecom2str(
+ bgp_evpn->evpn_info->soo,
+ ECOMMUNITY_FORMAT_ROUTE_MAP, 0);
+ json_object_string_add(json, "siteOfOrigin", ecom_str);
+ ecommunity_strfree(&ecom_str);
+ }
/* per vni knob is enabled -- Enabled
* Global knob is enabled -- Active
* default -- Disabled
@@ -499,6 +522,7 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json)
json_object_string_add(json, "advertiseGatewayMacip",
"Disabled");
if (!vpn->advertise_svi_macip && bgp_evpn &&
+ bgp_evpn->evpn_info &&
bgp_evpn->evpn_info->advertise_svi_macip)
json_object_string_add(json, "advertiseSviMacIp",
"Active");
@@ -525,6 +549,14 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json)
vty_out(vty, "\n");
vty_out(vty, " Originator IP: %pI4\n", &vpn->originator_ip);
vty_out(vty, " Mcast group: %pI4\n", &vpn->mcast_grp);
+ if (bgp_evpn && bgp_evpn->evpn_info) {
+ ecom_str = ecommunity_ecom2str(
+ bgp_evpn->evpn_info->soo,
+ ECOMMUNITY_FORMAT_ROUTE_MAP, 0);
+ vty_out(vty, " MAC-VRF Site-of-Origin: %s\n",
+ ecom_str);
+ ecommunity_strfree(&ecom_str);
+ }
if (!vpn->advertise_gw_macip &&
bgp_evpn && bgp_evpn->advertise_gw_macip)
vty_out(vty, " Advertise-gw-macip : %s\n",
@@ -536,6 +568,7 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json)
vty_out(vty, " Advertise-gw-macip : %s\n",
"Disabled");
if (!vpn->advertise_svi_macip && bgp_evpn &&
+ bgp_evpn->evpn_info &&
bgp_evpn->evpn_info->advertise_svi_macip)
vty_out(vty, " Advertise-svi-macip : %s\n",
"Active");
@@ -562,7 +595,7 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json)
else
vty_out(vty, " %s\n", ecom_str);
- XFREE(MTYPE_ECOMMUNITY_STR, ecom_str);
+ ecommunity_strfree(&ecom_str);
}
if (json)
@@ -580,7 +613,7 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json)
else
vty_out(vty, " %s\n", ecom_str);
- XFREE(MTYPE_ECOMMUNITY_STR, ecom_str);
+ ecommunity_strfree(&ecom_str);
}
if (json)
@@ -981,10 +1014,13 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp,
char *ecom_str;
struct listnode *node, *nnode;
struct vrf_route_target *l3rt;
+ struct bgp *bgp_evpn;
if (!bgp->l3vni)
return;
+ bgp_evpn = bgp_get_evpn();
+
if (json) {
json_vni = json_object_new_object();
json_import_rtl = json_object_new_array();
@@ -1041,7 +1077,7 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp,
vty_out(vty, " %-25s", rt_buf);
}
- XFREE(MTYPE_ECOMMUNITY_STR, ecom_str);
+ ecommunity_strfree(&ecom_str);
/* If there are multiple import RTs we break here and show only
* one */
@@ -1069,12 +1105,19 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp,
vty_out(vty, " %-25s", rt_buf);
}
- XFREE(MTYPE_ECOMMUNITY_STR, ecom_str);
+ ecommunity_strfree(&ecom_str);
/* If there are multiple export RTs we break here and show only
* one */
if (!json) {
- vty_out(vty, "%-37s", vrf_id_to_name(bgp->vrf_id));
+ if (bgp_evpn && bgp_evpn->evpn_info) {
+ ecom_str = ecommunity_ecom2str(
+ bgp_evpn->evpn_info->soo,
+ ECOMMUNITY_FORMAT_ROUTE_MAP, 0);
+ vty_out(vty, " %-25s", ecom_str);
+ ecommunity_strfree(&ecom_str);
+ }
+ vty_out(vty, " %-37s", vrf_id_to_name(bgp->vrf_id));
break;
}
}
@@ -1083,11 +1126,18 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp,
char vni_str[VNI_STR_LEN];
json_object_object_add(json_vni, "exportRTs", json_export_rtl);
+ if (bgp_evpn && bgp_evpn->evpn_info) {
+ ecom_str = ecommunity_ecom2str(
+ bgp_evpn->evpn_info->soo,
+ ECOMMUNITY_FORMAT_ROUTE_MAP, 0);
+ json_object_string_add(json_vni, "siteOfOrigin",
+ ecom_str);
+ ecommunity_strfree(&ecom_str);
+ }
snprintf(vni_str, sizeof(vni_str), "%u", bgp->l3vni);
json_object_object_add(json, vni_str, json_vni);
- } else {
+ } else
vty_out(vty, "\n");
- }
}
static void show_vni_entry(struct hash_bucket *bucket, void *args[])
@@ -1213,7 +1263,14 @@ static void show_vni_entry(struct hash_bucket *bucket, void *args[])
/* If there are multiple export RTs we break here and show only
* one */
if (!json) {
- vty_out(vty, "%-37s",
+ if (bgp_evpn && bgp_evpn->evpn_info) {
+ ecom_str = ecommunity_ecom2str(
+ bgp_evpn->evpn_info->soo,
+ ECOMMUNITY_FORMAT_ROUTE_MAP, 0);
+ vty_out(vty, " %-25s", ecom_str);
+ ecommunity_strfree(&ecom_str);
+ }
+ vty_out(vty, " %-37s",
vrf_id_to_name(vpn->tenant_vrf_id));
break;
}
@@ -1223,11 +1280,18 @@ static void show_vni_entry(struct hash_bucket *bucket, void *args[])
char vni_str[VNI_STR_LEN];
json_object_object_add(json_vni, "exportRTs", json_export_rtl);
+ if (bgp_evpn && bgp_evpn->evpn_info) {
+ ecom_str = ecommunity_ecom2str(
+ bgp_evpn->evpn_info->soo,
+ ECOMMUNITY_FORMAT_ROUTE_MAP, 0);
+ json_object_string_add(json_vni, "siteOfOrigin",
+ ecom_str);
+ ecommunity_strfree(&ecom_str);
+ }
snprintf(vni_str, sizeof(vni_str), "%u", vpn->vni);
json_object_object_add(json, vni_str, json_vni);
- } else {
+ } else
vty_out(vty, "\n");
- }
}
static int bgp_show_ethernet_vpn(struct vty *vty, struct prefix_rd *prd,
@@ -3276,8 +3340,9 @@ static void evpn_show_all_vnis(struct vty *vty, struct bgp *bgp,
if (!json) {
vty_out(vty, "Flags: * - Kernel\n");
- vty_out(vty, " %-10s %-4s %-21s %-25s %-25s %-37s\n", "VNI",
- "Type", "RD", "Import RT", "Export RT", "Tenant VRF");
+ vty_out(vty, " %-10s %-4s %-21s %-25s %-25s %-25s %-37s\n",
+ "VNI", "Type", "RD", "Import RT", "Export RT",
+ "MAC-VRF Site-of-Origin", "Tenant VRF");
}
/* print all L2 VNIS */
@@ -3923,6 +3988,58 @@ DEFPY(bgp_evpn_advertise_svi_ip_vni,
return CMD_SUCCESS;
}
+DEFPY(macvrf_soo_global, macvrf_soo_global_cmd,
+ "mac-vrf soo ASN:NN_OR_IP-ADDRESS:NN$soo",
+ "EVPN MAC-VRF\n"
+ "Site-of-Origin extended community\n"
+ "VPN extended community\n")
+{
+ struct bgp *bgp = VTY_GET_CONTEXT(bgp);
+ struct bgp *bgp_evpn = bgp_get_evpn();
+ struct ecommunity *ecomm_soo;
+
+ if (!bgp || !bgp_evpn || !bgp_evpn->evpn_info)
+ return CMD_WARNING;
+
+ if (bgp != bgp_evpn) {
+ vty_out(vty,
+ "%% Please configure MAC-VRF SoO in the EVPN underlay: %s\n",
+ bgp_evpn->name_pretty);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ ecomm_soo = ecommunity_str2com(soo, ECOMMUNITY_SITE_ORIGIN, 0);
+ if (!ecomm_soo) {
+ vty_out(vty, "%% Malformed SoO extended community\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ ecommunity_str(ecomm_soo);
+
+ bgp_evpn_handle_global_macvrf_soo_change(bgp_evpn, ecomm_soo);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(no_macvrf_soo_global, no_macvrf_soo_global_cmd,
+ "no mac-vrf soo [ASN:NN_OR_IP-ADDRESS:NN$soo]",
+ NO_STR
+ "EVPN MAC-VRF\n"
+ "Site-of-Origin extended community\n"
+ "VPN extended community\n")
+{
+ struct bgp *bgp = VTY_GET_CONTEXT(bgp);
+ struct bgp *bgp_evpn = bgp_get_evpn();
+
+ if (!bgp || !bgp_evpn || !bgp_evpn->evpn_info)
+ return CMD_WARNING;
+
+ if (bgp_evpn)
+ bgp_evpn_handle_global_macvrf_soo_change(bgp_evpn,
+ NULL /* new_soo */);
+
+ return CMD_SUCCESS;
+}
+
DEFUN_HIDDEN (bgp_evpn_advertise_vni_subnet,
bgp_evpn_advertise_vni_subnet_cmd,
"advertise-subnet",
@@ -7158,6 +7275,15 @@ void bgp_config_write_evpn_info(struct vty *vty, struct bgp *bgp, afi_t afi,
if (bgp->evpn_info->advertise_svi_macip)
vty_out(vty, " advertise-svi-ip\n");
+ if (bgp->evpn_info->soo) {
+ char *ecom_str;
+
+ ecom_str = ecommunity_ecom2str(bgp->evpn_info->soo,
+ ECOMMUNITY_FORMAT_ROUTE_MAP, 0);
+ vty_out(vty, " mac-vrf soo %s\n", ecom_str);
+ ecommunity_strfree(&ecom_str);
+ }
+
if (bgp->resolve_overlay_index)
vty_out(vty, " enable-resolve-overlay-index\n");
@@ -7390,6 +7516,8 @@ void bgp_ethernetvpn_init(void)
install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_default_gw_cmd);
install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_default_gw_cmd);
install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_svi_ip_cmd);
+ install_element(BGP_EVPN_NODE, &macvrf_soo_global_cmd);
+ install_element(BGP_EVPN_NODE, &no_macvrf_soo_global_cmd);
install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_type5_cmd);
install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_type5_cmd);
install_element(BGP_EVPN_NODE, &bgp_evpn_default_originate_cmd);
diff --git a/bgpd/bgp_mac.c b/bgpd/bgp_mac.c
index 6272bdb884..0398e4e8c1 100644
--- a/bgpd/bgp_mac.c
+++ b/bgpd/bgp_mac.c
@@ -279,15 +279,29 @@ static void bgp_mac_remove_ifp_internal(struct bgp_self_mac *bsm, char *ifname,
}
}
+/* Add/Update entry of the 'bgp mac hash' table.
+ * A rescan of the EVPN tables is only needed if
+ * a new hash bucket is allocated.
+ * Learning an existing mac on a new interface (or
+ * having an existing mac move from one interface to
+ * another) does not result in changes to self mac
+ * state, so we shouldn't trigger a rescan.
+ */
void bgp_mac_add_mac_entry(struct interface *ifp)
{
struct bgp_self_mac lookup;
struct bgp_self_mac *bsm;
struct bgp_self_mac *old_bsm;
char *ifname;
+ bool mac_added = false;
memcpy(&lookup.macaddr, &ifp->hw_addr, ETH_ALEN);
- bsm = hash_get(bm->self_mac_hash, &lookup, bgp_mac_hash_alloc);
+ bsm = hash_lookup(bm->self_mac_hash, &lookup);
+ if (!bsm) {
+ bsm = hash_get(bm->self_mac_hash, &lookup, bgp_mac_hash_alloc);
+ /* mac is new, rescan needs to be triggered */
+ mac_added = true;
+ }
/*
* Does this happen to be a move
@@ -318,7 +332,8 @@ void bgp_mac_add_mac_entry(struct interface *ifp)
listnode_add(bsm->ifp_list, ifname);
}
- bgp_mac_rescan_all_evpn_tables(&bsm->macaddr);
+ if (mac_added)
+ bgp_mac_rescan_all_evpn_tables(&bsm->macaddr);
}
void bgp_mac_del_mac_entry(struct interface *ifp)
diff --git a/bgpd/bgp_nexthop.h b/bgpd/bgp_nexthop.h
index 95e2f9165b..47b6464085 100644
--- a/bgpd/bgp_nexthop.h
+++ b/bgpd/bgp_nexthop.h
@@ -104,11 +104,6 @@ struct tip_addr {
int refcnt;
};
-struct bgp_addrv6 {
- struct in6_addr addrv6;
- struct list *ifp_name_list;
-};
-
/* Forward declaration(s). */
struct peer;
struct update_subgroup;
diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c
index 9469a0778f..0cac58ade1 100644
--- a/bgpd/bgp_packet.c
+++ b/bgpd/bgp_packet.c
@@ -1080,6 +1080,7 @@ void bgp_notify_io_invalid(struct peer *peer, uint8_t code, uint8_t sub_code,
* @param orf_type Outbound Route Filtering type
* @param when_to_refresh Whether to refresh immediately or defer
* @param remove Whether to remove ORF for specified AFI/SAFI
+ * @param subtype BGP enhanced route refresh optional subtypes
*/
void bgp_route_refresh_send(struct peer *peer, afi_t afi, safi_t safi,
uint8_t orf_type, uint8_t when_to_refresh,
diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c
index 1965cd2704..d1a68f3bcd 100644
--- a/bgpd/bgp_zebra.c
+++ b/bgpd/bgp_zebra.c
@@ -2984,9 +2984,9 @@ static int bgp_zebra_process_local_l3vni(ZAPI_CALLBACK_ARGS)
if (BGP_DEBUG(zebra, ZEBRA))
zlog_debug(
- "Rx L3-VNI ADD VRF %s VNI %u RMAC svi-mac %pEA vrr-mac %pEA filter %s svi-if %u",
- vrf_id_to_name(vrf_id), l3vni, &svi_rmac,
- &vrr_rmac,
+ "Rx L3-VNI ADD VRF %s VNI %u Originator-IP %pI4 RMAC svi-mac %pEA vrr-mac %pEA filter %s svi-if %u",
+ vrf_id_to_name(vrf_id), l3vni, &originator_ip,
+ &svi_rmac, &vrr_rmac,
filter ? "prefix-routes-only" : "none",
svi_ifindex);
diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
index ba2985d304..c710501f05 100644
--- a/bgpd/bgpd.c
+++ b/bgpd/bgpd.c
@@ -80,7 +80,6 @@
#include "bgp_trace.h"
DEFINE_MTYPE_STATIC(BGPD, PEER_TX_SHUTDOWN_MSG, "Peer shutdown message (TX)");
-DEFINE_MTYPE_STATIC(BGPD, BGP_EVPN_INFO, "BGP EVPN instance information");
DEFINE_QOBJ_TYPE(bgp_master);
DEFINE_QOBJ_TYPE(bgp);
DEFINE_QOBJ_TYPE(peer);
@@ -3403,8 +3402,6 @@ static struct bgp *bgp_create(as_t *as, const char *name,
/* assign a unique rd id for auto derivation of vrf's RD */
bf_assign_index(bm->rd_idspace, bgp->vrf_rd_id);
- bgp->evpn_info = XCALLOC(MTYPE_BGP_EVPN_INFO,
- sizeof(struct bgp_evpn_info));
bgp_evpn_init(bgp);
bgp_evpn_vrf_es_init(bgp);
bgp_pbr_init(bgp);
@@ -3971,7 +3968,6 @@ void bgp_free(struct bgp *bgp)
bgp_evpn_cleanup(bgp);
bgp_pbr_cleanup(bgp);
bgp_srv6_cleanup(bgp);
- XFREE(MTYPE_BGP_EVPN_INFO, bgp->evpn_info);
for (afi = AFI_IP; afi < AFI_MAX; afi++) {
enum vpn_policy_direction dir;
@@ -8390,3 +8386,16 @@ static ssize_t printfrr_bp(struct fbuf *buf, struct printfrr_eargs *ea,
return bprintfrr(buf, "%s(%s)", peer->host,
peer->hostname ? peer->hostname : "Unknown");
}
+
+const struct message bgp_martian_type_str[] = {
+ {BGP_MARTIAN_IF_IP, "Self Interface IP"},
+ {BGP_MARTIAN_TUN_IP, "Self Tunnel IP"},
+ {BGP_MARTIAN_IF_MAC, "Self Interface MAC"},
+ {BGP_MARTIAN_RMAC, "Self RMAC"},
+ {BGP_MARTIAN_SOO, "Self Site-of-Origin"},
+ {0}};
+
+const char *bgp_martian_type2str(enum bgp_martian_type mt)
+{
+ return lookup_msg(bgp_martian_type_str, mt, "Unknown Martian Type");
+}
diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h
index ecd122fee2..6deb7d466c 100644
--- a/bgpd/bgpd.h
+++ b/bgpd/bgpd.h
@@ -2105,6 +2105,26 @@ enum peer_change_type {
peer_change_reset_out,
};
+/* Enumeration of martian ("self") entry types.
+ * Routes carrying fields that match a self entry are considered martians
+ * and should be handled accordingly, i.e. dropped or import-filtered.
+ * Note:
+ * These "martians" are separate from routes optionally allowed via
+ * 'bgp allow-martian-nexthop'. The optionally allowed martians are
+ * simply prefixes caught by ipv4_martian(), i.e. routes outside
+ * the non-reserved IPv4 Unicast address space.
+ */
+enum bgp_martian_type {
+ BGP_MARTIAN_IF_IP, /* bgp->address_hash */
+ BGP_MARTIAN_TUN_IP, /* bgp->tip_hash */
+ BGP_MARTIAN_IF_MAC, /* bgp->self_mac_hash */
+ BGP_MARTIAN_RMAC, /* bgp->rmac */
+ BGP_MARTIAN_SOO, /* bgp->evpn_info->macvrf_soo */
+};
+
+extern const struct message bgp_martian_type_str[];
+extern const char *bgp_martian_type2str(enum bgp_martian_type mt);
+
extern struct bgp_master *bm;
extern unsigned int multipath_num;
diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst
index a2585f3a57..b3252dffc0 100644
--- a/doc/user/bgp.rst
+++ b/doc/user/bgp.rst
@@ -3229,6 +3229,77 @@ Example configuration:
exit-address-family
!
+.. _bgp-evpn-mac-vrf-site-of-origin:
+
+EVPN MAC-VRF Site-of-Origin
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+In some EVPN deployments it is useful to associate a logical VTEP's Layer 2
+domain (MAC-VRF) with a Site-of-Origin "site" identifier. This provides a
+BGP topology-independent means of marking and import-filtering EVPN routes
+originated from a particular L2 domain. One situation where this is valuable
+is when deploying EVPN using anycast VTEPs, i.e. Active/Active MLAG, as it
+can be used to avoid ownership conflicts between the two control planes
+(EVPN vs MLAG).
+
+Example Use Case (MLAG Anycast VTEPs):
+
+During normal operation, an MLAG VTEP will advertise EVPN routes for attached
+hosts using a shared anycast IP as the BGP next-hop. It is expected for its
+MLAG peer to drop routes originated by the MLAG Peer since they have a Martian
+(self) next-hop. However, prior to the anycast IP being assigned to the local
+system, the anycast BGP next-hop will not be considered a Martian (self) IP.
+This results in a timing window where hosts that are locally attached to the
+MLAG pair's L2 domain can be learned both as "local" (via MLAG) or "remote"
+(via an EVPN route with a non-local next-hop). This can trigger erroneous MAC
+Mobility events, as the host "moves" between one MLAG Peer's Unique VTEP-IP
+and the shared anycast VTEP-IP, which causes unnecessary control plane and
+data plane events to propagate throughout the EVPN domain.
+By associating the MAC-VRF of both MLAG VTEPs with the same site identifier,
+EVPN routes originated by one MLAG VTEP will ignored by its MLAG peer, ensuring
+that only the MLAG control plane attempts to take ownership of local hosts.
+
+The EVPN MAC-VRF Site-of-Origin feature works by influencing two behaviors:
+
+1. All EVPN routes originating from the local MAC-VRF will have a
+ Site-of-Origin extended community added to the route, matching the
+ configured value.
+2. EVPN routes will be subjected to a "self SoO" check during MAC-VRF
+ or IP-VRF import processing. If the EVPN route is found to carry a
+ Site-of-Origin extended community whose value matches the locally
+ configured MAC-VRF Site-of-Origin, the route will be maintained in
+ the global EVPN RIB ("show bgp l2vpn evpn route") but will not be
+ imported into the corresponding MAC-VRF ("show bgp vni") or IP-VRF
+ ("show bgp [vrf <vrfname>] [ipv4 | ipv6 [unicast]]").
+
+The import filtering described in item (2) is constrained just to Type-2
+(MAC-IP) and Type-3 (IMET) EVPN routes.
+
+The EVPN MAC-VRF Site-of-Origin can be configured using a single CLI command
+under ``address-family l2vpn evpn`` of the EVPN underlay BGP instance.
+
+.. clicmd:: [no] mac-vrf soo <site-of-origin-string>
+
+Example configuration:
+
+.. code-block:: frr
+
+ router bgp 100
+ neighbor 192.168.0.1 remote-as 101
+ !
+ address-family ipv4 l2vpn evpn
+ neighbor 192.168.0.1 activate
+ advertise-all-vni
+ mac-vrf soo 100.64.0.0:777
+ exit-address-family
+
+This configuration ensures:
+
+1. EVPN routes originated from a local L2VNI will have a Site-of-Origin
+ extended community with the value ``100.64.0.0:777``
+2. Received EVPN routes carrying a Site-of-Origin extended community with the
+ value ``100.64.0.0:777`` will not be imported into a local MAC-VRF (L2VNI)
+ or IP-VRF (L3VNI).
+
.. _bgp-evpn-mh:
EVPN Multihoming
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/bgpd.conf
new file mode 100644
index 0000000000..cdf4cb4feb
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/bgpd.conf
@@ -0,0 +1 @@
+!
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/ospfd.conf
new file mode 100644
index 0000000000..2db7edb806
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/ospfd.conf
@@ -0,0 +1,13 @@
+!
+router ospf
+ network 10.20.0.0/16 area 0
+ network 10.20.20.20/32 area 0
+!
+int P1-eth0
+ ip ospf hello-interval 2
+ ip ospf dead-interval 10
+!
+int P1-eth1
+ ip ospf hello-interval 2
+ ip ospf dead-interval 10
+!
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/zebra.conf
new file mode 100644
index 0000000000..95b5da8402
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/zebra.conf
@@ -0,0 +1,7 @@
+!
+interface lo
+ ip address 10.20.20.20/32
+interface P1-eth0
+ ip address 10.20.1.2/24
+interface P1-eth1
+ ip address 10.20.2.2/24
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgp.l2vpn.evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgp.l2vpn.evpn.vni.json
new file mode 100644
index 0000000000..9f93635c21
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgp.l2vpn.evpn.vni.json
@@ -0,0 +1,19 @@
+{
+ "vni":101,
+ "type":"L2",
+ "inKernel":"True",
+ "rd":"10.10.10.10:101",
+ "originatorIp":"10.10.10.10",
+ "mcastGroup":"0.0.0.0",
+ "siteOfOrigin":"65000:0",
+ "advertiseGatewayMacip":"Disabled",
+ "advertiseSviMacIp":"Active",
+ "sviInterface":"br101",
+ "importRts":[
+ "65000:101"
+ ],
+ "exportRts":[
+ "65000:101"
+ ]
+}
+
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgpd.conf
new file mode 100644
index 0000000000..f839443025
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgpd.conf
@@ -0,0 +1,18 @@
+router bgp 65000
+ timers 3 9
+ bgp router-id 10.10.10.10
+ no bgp default ipv4-unicast
+ neighbor 10.30.30.30 remote-as 65000
+ neighbor 10.30.30.30 update-source lo
+ neighbor 10.30.30.30 timers 3 10
+ !
+ address-family l2vpn evpn
+ neighbor 10.30.30.30 activate
+ advertise-all-vni
+ advertise-svi-ip
+ vni 101
+ rd 10.10.10.10:101
+ route-target import 65000:101
+ route-target export 65000:101
+ exit-vni
+ advertise-svi-ip
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/evpn.vni.json
new file mode 100644
index 0000000000..4bea8b384f
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/evpn.vni.json
@@ -0,0 +1,17 @@
+{
+ "vni":101,
+ "type":"L2",
+ "tenantVrf":"VRF-A",
+ "vxlanInterface":"vxlan101",
+ "vtepIp":"10.10.10.10",
+ "mcastGroup":"0.0.0.0",
+ "advertiseGatewayMacip":"No",
+ "numRemoteVteps":1,
+ "remoteVteps":[
+ {
+ "ip":"10.30.30.30",
+ "flood":"HER"
+ }
+ ]
+}
+
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/ospfd.conf
new file mode 100644
index 0000000000..f1c2b42dc1
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/ospfd.conf
@@ -0,0 +1,9 @@
+!
+router ospf
+ network 10.20.0.0/16 area 0
+ network 10.10.10.10/32 area 0
+!
+int PE1-eth1
+ ip ospf hello-interval 2
+ ip ospf dead-interval 10
+!
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/zebra.conf
new file mode 100644
index 0000000000..e2699475c9
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/zebra.conf
@@ -0,0 +1,8 @@
+!
+log file zebra.log
+!
+interface lo
+ ip address 10.10.10.10/32
+interface PE1-eth1
+ ip address 10.20.1.1/24
+!
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgp.l2vpn.evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgp.l2vpn.evpn.vni.json
new file mode 100644
index 0000000000..63ac730144
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgp.l2vpn.evpn.vni.json
@@ -0,0 +1,19 @@
+{
+ "vni":101,
+ "type":"L2",
+ "inKernel":"True",
+ "rd":"10.30.30.30:101",
+ "originatorIp":"10.30.30.30",
+ "mcastGroup":"0.0.0.0",
+ "siteOfOrigin":"65000:0",
+ "advertiseGatewayMacip":"Disabled",
+ "advertiseSviMacIp":"Active",
+ "sviInterface":"br101",
+ "importRts":[
+ "65000:101"
+ ],
+ "exportRts":[
+ "65000:101"
+ ]
+}
+
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgpd.conf
new file mode 100644
index 0000000000..9a0830d8a3
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgpd.conf
@@ -0,0 +1,18 @@
+router bgp 65000
+ timers bgp 3 9
+ bgp router-id 10.30.30.30
+ no bgp default ipv4-unicast
+ neighbor 10.10.10.10 remote-as 65000
+ neighbor 10.10.10.10 update-source lo
+ neighbor 10.10.10.10 timers 3 10
+ !
+ address-family l2vpn evpn
+ neighbor 10.10.10.10 activate
+ advertise-all-vni
+ advertise-svi-ip
+ vni 101
+ rd 10.30.30.30:101
+ route-target import 65000:101
+ route-target export 65000:101
+ exit-vni
+ advertise-svi-ip
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/evpn.vni.json
new file mode 100644
index 0000000000..5566fff954
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/evpn.vni.json
@@ -0,0 +1,16 @@
+{
+ "vni":101,
+ "type":"L2",
+ "tenantVrf":"VRF-A",
+ "vxlanInterface":"vxlan101",
+ "vtepIp":"10.30.30.30",
+ "mcastGroup":"0.0.0.0",
+ "advertiseGatewayMacip":"No",
+ "numRemoteVteps":1,
+ "remoteVteps":[
+ {
+ "ip":"10.10.10.10",
+ "flood":"HER"
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/ospfd.conf
new file mode 100644
index 0000000000..065c993303
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/ospfd.conf
@@ -0,0 +1,9 @@
+!
+router ospf
+ network 10.20.0.0/16 area 0
+ network 10.30.30.30/32 area 0
+!
+int PE2-eth0
+ ip ospf hello-interval 2
+ ip ospf dead-interval 10
+!
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/zebra.conf
new file mode 100644
index 0000000000..9738916ab0
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/zebra.conf
@@ -0,0 +1,6 @@
+!
+interface lo
+ ip address 10.30.30.30/32
+interface PE2-eth0
+ ip address 10.20.2.3/24
+!
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/bgpd.conf
new file mode 100644
index 0000000000..cdf4cb4feb
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/bgpd.conf
@@ -0,0 +1 @@
+!
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/ospfd.conf
new file mode 100644
index 0000000000..cdf4cb4feb
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/ospfd.conf
@@ -0,0 +1 @@
+!
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/zebra.conf
new file mode 100644
index 0000000000..91fae9eeba
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/zebra.conf
@@ -0,0 +1,3 @@
+!
+int host1-eth0
+ ip address 10.10.1.55/24
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/bgpd.conf
new file mode 100644
index 0000000000..cdf4cb4feb
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/bgpd.conf
@@ -0,0 +1 @@
+!
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/ospfd.conf
new file mode 100644
index 0000000000..cdf4cb4feb
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/ospfd.conf
@@ -0,0 +1 @@
+!
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/zebra.conf
new file mode 100644
index 0000000000..df9adeb3b5
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/zebra.conf
@@ -0,0 +1,3 @@
+!
+interface host2-eth0
+ ip address 10.10.1.56/24
diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/test_bgp_evpn_vxlan_macvrf_soo.py b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/test_bgp_evpn_vxlan_macvrf_soo.py
new file mode 100755
index 0000000000..558f7379e9
--- /dev/null
+++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/test_bgp_evpn_vxlan_macvrf_soo.py
@@ -0,0 +1,839 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# test_bgp_evpn_vxlan_macvrf_soo.py
+#
+# May 10 2023, Trey Aspelund <taspelund@nvidia.com>
+#
+# Copyright (C) 2023 NVIDIA Corporation
+#
+# Test MAC-VRF Site-of-Origin feature.
+# Ensure:
+# - routes received with SoO are installed w/o "mac-vrf soo" config
+# - invalid "mac-vrf soo" config is rejected
+# - valid "mac-vrf soo" config is applied to local VNIs
+# - valid "mac-vrf soo" is set for locally originated type-2/3 routes
+# - routes received with SoO are unimported/uninstalled from L2VNI/zebra
+# - routes received with SoO are unimported/uninstalled from L3VNI/RIB
+# - routes received with SoO are still present in global EVPN loc-rib
+#
+
+import os
+import sys
+import json
+from functools import partial
+from time import sleep
+import pytest
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+from lib.common_config import step
+
+pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd]
+
+
+def build_topo(tgen):
+ "Build function"
+
+ # Create routers
+ tgen.add_router("P1")
+ tgen.add_router("PE1")
+ tgen.add_router("PE2")
+ tgen.add_router("host1")
+ tgen.add_router("host2")
+
+ # Host1-PE1
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["host1"])
+ switch.add_link(tgen.gears["PE1"])
+
+ # PE1-P1
+ switch = tgen.add_switch("s2")
+ switch.add_link(tgen.gears["PE1"])
+ switch.add_link(tgen.gears["P1"])
+
+ # P1-PE2
+ switch = tgen.add_switch("s3")
+ switch.add_link(tgen.gears["P1"])
+ switch.add_link(tgen.gears["PE2"])
+
+ # PE2-host2
+ switch = tgen.add_switch("s4")
+ switch.add_link(tgen.gears["PE2"])
+ switch.add_link(tgen.gears["host2"])
+
+
+def setup_module(mod):
+ "Sets up the pytest environment"
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ pe1 = tgen.gears["PE1"]
+ pe2 = tgen.gears["PE2"]
+ p1 = tgen.gears["P1"]
+ host1 = tgen.gears["host1"]
+ host2 = tgen.gears["host2"]
+
+ # Setup PEs with:
+ # - vrf: VRF-A
+ # - l3vni 404: vxlan404 / br404
+ # - l2vni 101: vxlan101 / br101
+
+ ## Setup VRF
+ # pe1
+ pe1.run("ip link add VRF-A type vrf table 4000")
+ pe1.run("ip link set VRF-A up")
+ # pe2
+ pe2.run("ip link add VRF-A type vrf table 4000")
+ pe2.run("ip link set VRF-A up")
+
+ ## Setup L3VNI bridge/vxlan
+ # pe1
+ pe1.run("ip link add name br404 type bridge stp_state 0")
+ pe1.run("ip link set dev br404 addr aa:bb:cc:00:11:ff")
+ pe1.run("ip link set dev br404 master VRF-A addrgenmode none")
+ pe1.run("ip link set dev br404 up")
+ pe1.run(
+ "ip link add vxlan404 type vxlan id 404 dstport 4789 local 10.10.10.10 nolearning"
+ )
+ pe1.run("ip link set dev vxlan404 master br404 addrgenmode none")
+ pe1.run("ip link set dev vxlan404 type bridge_slave neigh_suppress on learning off")
+ pe1.run("ip link set dev vxlan404 up")
+ # pe2
+ pe2.run("ip link add name br404 type bridge stp_state 0")
+ pe2.run("ip link set dev br404 addr aa:bb:cc:00:22:ff")
+ pe2.run("ip link set dev br404 master VRF-A addrgenmode none")
+ pe2.run("ip link set dev br404 up")
+ pe2.run(
+ "ip link add vxlan404 type vxlan id 404 dstport 4789 local 10.30.30.30 nolearning"
+ )
+ pe2.run("ip link set dev vxlan404 master br404 addrgenmode none")
+ pe2.run("ip link set dev vxlan404 type bridge_slave neigh_suppress on learning off")
+ pe2.run("ip link set dev vxlan404 up")
+
+ ## Setup L2VNI bridge/vxlan + L2 PE/CE link
+ # pe1
+ pe1.run("ip link add name br101 type bridge stp_state 0")
+ pe1.run("ip addr add 10.10.1.1/24 dev br101")
+ pe1.run("ip link set dev br101 addr aa:bb:cc:00:11:aa")
+ pe1.run("ip link set dev br101 master VRF-A")
+ pe1.run("ip link set dev br101 up")
+ pe1.run(
+ "ip link add vxlan101 type vxlan id 101 dstport 4789 local 10.10.10.10 nolearning"
+ )
+ pe1.run("ip link set dev vxlan101 master br101")
+ pe1.run("ip link set dev vxlan101 type bridge_slave neigh_suppress on learning off")
+ pe1.run("ip link set dev vxlan101 up")
+ pe1.run("ip link set dev PE1-eth0 master br101")
+ pe1.run("ip link set dev PE1-eth0 up")
+ # pe2
+ pe2.run("ip link add name br101 type bridge stp_state 0")
+ pe2.run("ip addr add 10.10.1.3/24 dev br101")
+ pe2.run("ip link set dev br101 addr aa:bb:cc:00:22:ff")
+ pe2.run("ip link set dev br101 master VRF-A")
+ pe2.run("ip link set dev br101 up")
+ pe2.run(
+ "ip link add vxlan101 type vxlan id 101 dstport 4789 local 10.30.30.30 nolearning"
+ )
+ pe2.run("ip link set dev vxlan101 master br101")
+ pe2.run("ip link set dev vxlan101 type bridge_slave neigh_suppress on learning off")
+ pe2.run("ip link set dev vxlan101 up")
+ pe2.run("ip link set dev PE2-eth1 master br101")
+ pe2.run("ip link set dev PE2-eth1 up")
+
+ ## Enable IPv4 Routing
+ p1.run("sysctl -w net.ipv4.ip_forward=1")
+ pe1.run("sysctl -w net.ipv4.ip_forward=1")
+ pe2.run("sysctl -w net.ipv4.ip_forward=1")
+
+ ## tell hosts to send GARP upon IPv4 addr assignment
+ host1.run("sysctl -w net.ipv4.conf.host1-eth0.arp_announce=1")
+ host2.run("sysctl -w net.ipv4.conf.host2-eth0.arp_announce=1")
+
+ ## Load FRR config on all nodes and start topo
+ router_list = tgen.routers()
+ for rname, router in router_list.items():
+ router.load_config(
+ TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
+ )
+ router.load_config(
+ TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname))
+ )
+ router.load_config(
+ TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
+ )
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ "Teardown the pytest environment"
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def show_vni_json_elide_ifindex(pe, vni, expected):
+ output_json = pe.vtysh_cmd("show evpn vni {} json".format(vni), isjson=True)
+ if "ifindex" in output_json:
+ output_json.pop("ifindex")
+
+ return topotest.json_cmp(output_json, expected)
+
+
+def check_vni_macs_present(tgen, router, vni, maclist):
+ result = router.vtysh_cmd("show evpn mac vni {} json".format(vni), isjson=True)
+ for rname, ifname in maclist:
+ m = tgen.net.macs[(rname, ifname)]
+ if m not in result["macs"]:
+ return "MAC ({}) for interface {} on {} missing on {} from {}".format(
+ m, ifname, rname, router.name, json.dumps(result, indent=4)
+ )
+ return None
+
+
+def test_pe1_converge_evpn():
+ "Wait for protocol convergence"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ pe1 = tgen.gears["PE1"]
+ json_file = "{}/{}/evpn.vni.json".format(CWD, pe1.name)
+ expected = json.loads(open(json_file).read())
+
+ test_func = partial(show_vni_json_elide_ifindex, pe1, 101, expected)
+ _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
+ assertmsg = '"{}" JSON output mismatches'.format(pe1.name)
+
+ # Let's ensure that the hosts have actually tried talking to
+ # each other. Otherwise under certain startup conditions
+ # they may not actually do any l2 arp'ing and as such
+ # the bridges won't know about the hosts on their networks
+ host1 = tgen.gears["host1"]
+ host1.run("ping -c 1 10.10.1.56")
+ host2 = tgen.gears["host2"]
+ host2.run("ping -c 1 10.10.1.55")
+
+ test_func = partial(
+ check_vni_macs_present,
+ tgen,
+ pe1,
+ 101,
+ (("host1", "host1-eth0"), ("host2", "host2-eth0")),
+ )
+
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ if result:
+ logger.warning("%s", result)
+ assert None, '"{}" missing expected MACs'.format(pe1.name)
+
+
+def test_pe2_converge_evpn():
+ "Wait for protocol convergence"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ pe2 = tgen.gears["PE2"]
+ json_file = "{}/{}/evpn.vni.json".format(CWD, pe2.name)
+ expected = json.loads(open(json_file).read())
+
+ test_func = partial(show_vni_json_elide_ifindex, pe2, 101, expected)
+ _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
+ assertmsg = '"{}" JSON output mismatches'.format(pe2.name)
+ assert result is None, assertmsg
+
+ test_func = partial(
+ check_vni_macs_present,
+ tgen,
+ pe2,
+ 101,
+ (("host1", "host1-eth0"), ("host2", "host2-eth0")),
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ if result:
+ logger.warning("%s", result)
+ assert None, '"{}" missing expected MACs'.format(pe2.name)
+
+
+def mac_learn_test(host, local):
+ "check the host MAC gets learned by the VNI"
+
+ host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name))
+ int_lines = host_output.splitlines()
+ for line in int_lines:
+ line_items = line.split(": ")
+ if "HWaddr" in line_items[0]:
+ mac = line_items[1]
+ break
+
+ mac_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac))
+ mac_output_json = json.loads(mac_output)
+ assertmsg = "Local MAC output does not match interface mac {}".format(mac)
+ assert mac_output_json[mac]["type"] == "local", assertmsg
+
+
+def mac_test_local_remote(local, remote):
+ "test MAC transfer between local and remote"
+
+ local_output = local.vtysh_cmd("show evpn mac vni all json")
+ remote_output = remote.vtysh_cmd("show evpn mac vni all json")
+ local_output_vni = local.vtysh_cmd("show evpn vni detail json")
+ local_output_json = json.loads(local_output)
+ remote_output_json = json.loads(remote_output)
+ local_output_vni_json = json.loads(local_output_vni)
+
+ for vni in local_output_json:
+ mac_list = local_output_json[vni]["macs"]
+ for mac in mac_list:
+ if mac_list[mac]["type"] == "local" and mac_list[mac]["intf"] != "br101":
+ assertmsg = "JSON output mismatches local: {} remote: {}".format(
+ local_output_vni_json[0]["vtepIp"],
+ remote_output_json[vni]["macs"][mac]["remoteVtep"],
+ )
+ assert (
+ remote_output_json[vni]["macs"][mac]["remoteVtep"]
+ == local_output_vni_json[0]["vtepIp"]
+ ), assertmsg
+
+
+def test_learning_pe1():
+ "test MAC learning on PE1"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ host1 = tgen.gears["host1"]
+ pe1 = tgen.gears["PE1"]
+ mac_learn_test(host1, pe1)
+
+
+def test_learning_pe2():
+ "test MAC learning on PE2"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ host2 = tgen.gears["host2"]
+ pe2 = tgen.gears["PE2"]
+ mac_learn_test(host2, pe2)
+
+
+def test_local_remote_mac_pe1():
+ "Test MAC transfer PE1 local and PE2 remote"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ pe1 = tgen.gears["PE1"]
+ pe2 = tgen.gears["PE2"]
+ mac_test_local_remote(pe1, pe2)
+
+
+def test_local_remote_mac_pe2():
+ "Test MAC transfer PE2 local and PE1 remote"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ pe1 = tgen.gears["PE1"]
+ pe2 = tgen.gears["PE2"]
+ mac_test_local_remote(pe2, pe1)
+
+
+def ip_learn_test(tgen, host, local, remote, ip_addr):
+ "check the host IP gets learned by the VNI"
+ host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name))
+ int_lines = host_output.splitlines()
+ for line in int_lines:
+ line_items = line.split(": ")
+ if "HWaddr" in line_items[0]:
+ mac = line_items[1]
+ break
+ print(host_output)
+
+ # check we have a local association between the MAC and IP
+ local_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac))
+ print(local_output)
+ local_output_json = json.loads(local_output)
+ mac_type = local_output_json[mac]["type"]
+ assertmsg = "Failed to learn local IP address on host {}".format(host.name)
+ assert local_output_json[mac]["neighbors"] != "none", assertmsg
+ learned_ip = local_output_json[mac]["neighbors"]["active"][0]
+
+ assertmsg = "local learned mac wrong type: {} ".format(mac_type)
+ assert mac_type == "local", assertmsg
+
+ assertmsg = (
+ "learned address mismatch with configured address host: {} learned: {}".format(
+ ip_addr, learned_ip
+ )
+ )
+ assert ip_addr == learned_ip, assertmsg
+
+ # now lets check the remote
+ count = 0
+ converged = False
+ while count < 30:
+ remote_output = remote.vtysh_cmd(
+ "show evpn mac vni 101 mac {} json".format(mac)
+ )
+ print(remote_output)
+ remote_output_json = json.loads(remote_output)
+ type = remote_output_json[mac]["type"]
+ if not remote_output_json[mac]["neighbors"] == "none":
+ # due to a kernel quirk, learned IPs can be inactive
+ if (
+ remote_output_json[mac]["neighbors"]["active"]
+ or remote_output_json[mac]["neighbors"]["inactive"]
+ ):
+ converged = True
+ break
+ count += 1
+ sleep(1)
+
+ print("tries: {}".format(count))
+ assertmsg = "{} remote learned mac no address: {} ".format(host.name, mac)
+ # some debug for this failure
+ if not converged == True:
+ log_output = remote.run("cat zebra.log")
+ print(log_output)
+
+ assert converged == True, assertmsg
+ if remote_output_json[mac]["neighbors"]["active"]:
+ learned_ip = remote_output_json[mac]["neighbors"]["active"][0]
+ else:
+ learned_ip = remote_output_json[mac]["neighbors"]["inactive"][0]
+ assertmsg = "remote learned mac wrong type: {} ".format(type)
+ assert type == "remote", assertmsg
+
+ assertmsg = "remote learned address mismatch with configured address host: {} learned: {}".format(
+ ip_addr, learned_ip
+ )
+ assert ip_addr == learned_ip, assertmsg
+
+
+def test_ip_pe1_learn():
+ "run the IP learn test for PE1"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ host1 = tgen.gears["host1"]
+ pe1 = tgen.gears["PE1"]
+ pe2 = tgen.gears["PE2"]
+ # pe2.vtysh_cmd("debug zebra vxlan")
+ # pe2.vtysh_cmd("debug zebra kernel")
+ # lets populate that arp cache
+ host1.run("ping -c1 10.10.1.1")
+ ip_learn_test(tgen, host1, pe1, pe2, "10.10.1.55")
+ # tgen.mininet_cli()
+
+
+def test_ip_pe2_learn():
+ "run the IP learn test for PE2"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ host2 = tgen.gears["host2"]
+ pe1 = tgen.gears["PE1"]
+ pe2 = tgen.gears["PE2"]
+ # pe1.vtysh_cmd("debug zebra vxlan")
+ # pe1.vtysh_cmd("debug zebra kernel")
+ # lets populate that arp cache
+ host2.run("ping -c1 10.10.1.3")
+ ip_learn_test(tgen, host2, pe2, pe1, "10.10.1.56")
+ # tgen.mininet_cli()
+
+
+def is_installed(json_paths, soo):
+ """
+ check if any path has been selected as best.
+ optionally check for matching SoO on bestpath.
+ """
+ best = False
+ soo_present = False
+ for path in json_paths:
+ path = path[0]
+ # sometimes "bestpath" is a bool, other times it's a dict
+ # either way, the key isn't present when the bool is false...
+ # so we may as well just check for the key's existence
+ best = "bestpath" in path
+ path_keys = path.keys()
+ if best:
+ if soo:
+ soo_present = soo in path["extendedCommunity"]["string"]
+ break
+ return (best and soo_present) if soo else best
+
+
+def change_soo(pe, soo, vni):
+ soo_cmd_str = "mac-vrf soo "
+ if soo:
+ soo_cmd_str += soo
+ else:
+ soo_cmd_str = "no " + soo_cmd_str
+ pe.vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65000
+ address-family l2vpn evpn
+ {}
+ """.format(
+ soo_cmd_str
+ )
+ )
+ bgp_l2vni = get_bgp_l2vni_fields(pe, vni)
+ l2vni_soo = bgp_l2vni[2]
+ return l2vni_soo == soo
+
+
+def get_evpn_rt_json_str(vni, rd, oip=None, mac=None, ip=None):
+ "convert evpn route fields into a route string + global/l2vni cli syntax"
+ # type-3
+ if oip:
+ rt_str = "[3]:[0]:[32]:[{}]".format(oip)
+ global_rt_cmd = "show bgp l2vpn evpn route rd {} type 3 json".format(rd)
+ l2vni_rt_cmd = "show bgp vni {} type 3 vtep {} json".format(vni, oip)
+ # type-2
+ else:
+ rt_str = "[2]:[0]:[48]:[{}]".format(mac)
+ global_rt_cmd = "show bgp l2vpn evpn route rd {} type 2".format(rd)
+ l2vni_rt_cmd = "show bgp vni {} type 2 mac {}".format(vni, mac)
+ if ip:
+ ip_len = 128 if ":" in ip else 32
+ rt_str += ":[{}]:[{}]".format(ip_len, ip)
+ l2vni_rt_cmd = "show bgp vni {} type 2 ip {}".format(vni, ip)
+ global_rt_cmd += " json"
+ l2vni_rt_cmd += " json"
+ return [rt_str, global_rt_cmd, l2vni_rt_cmd]
+
+
+def get_evpn_rt_json(pe, vni, rd, oip=None, mac=None, ip=None):
+ "get json global/l2vni json blobs for the corresponding evpn route"
+ rt = get_evpn_rt_json_str(vni, rd, oip, mac, ip)
+ rt_str = rt.pop(0)
+ global_rt_cmd = rt.pop(0)
+ l2vni_rt_cmd = rt.pop(0)
+ logger.info(
+ "collecting global/l2vni evpn routes for pfx {} on {}".format(rt_str, pe.name)
+ )
+ global_rt_json = pe.vtysh_cmd(global_rt_cmd, isjson=True)
+ logger.info("global evpn route for pfx {} on {}".format(rt_str, pe.name))
+ logger.info(global_rt_json)
+ l2vni_rt_json = pe.vtysh_cmd(l2vni_rt_cmd, isjson=True)
+ logger.info("l2vni evpn route for pfx {} on {}".format(rt_str, pe.name))
+ logger.info(l2vni_rt_json)
+ return [rt_str, global_rt_json, l2vni_rt_json]
+
+
+def get_bgp_l2vni_fields(pe, vni):
+ bgp_vni_output = pe.vtysh_cmd(
+ "show bgp l2vpn evpn vni {} json".format(vni), isjson=True
+ )
+ rd = bgp_vni_output["rd"]
+ oip = bgp_vni_output["originatorIp"]
+ soo = bgp_vni_output["siteOfOrigin"]
+ return [rd, oip, soo]
+
+
+def rt_test(pe, vni, rd, oip, mac, ip, soo):
+ """
+ Check installation status of a given route.
+ @pe = router where bgp routes are collected from
+ @vni = l2vni
+ @rd = rd of the route
+ @oip = originator-ip, set only for type-3 route
+ @mac = nlri mac, set only for type-2
+ @ip = nlri ip, optionally set for type-2
+ @soo = MAC-VRF SoO string, set if SoO needs to be
+ on the rt to be considered installed.
+ """
+ rt = get_evpn_rt_json(pe, vni, rd, oip, mac, ip)
+ rt_str = rt.pop(0)
+ rt_global_json = rt.pop(0)
+ rt_l2vni_json = rt.pop(0)
+
+ if (
+ not rt_global_json
+ or rd not in rt_global_json
+ or rt_str not in rt_global_json[rd]
+ ):
+ global_installed = False
+ else:
+ global_json_paths = rt_global_json[rd][rt_str]["paths"]
+ global_installed = is_installed(global_json_paths, soo)
+ if not rt_l2vni_json:
+ l2vni_installed = False
+ else:
+ if not oip:
+ # json for RT2s in l2vni don't key by route string
+ l2vni_json_paths = rt_l2vni_json["paths"]
+ l2vni_installed = is_installed(l2vni_json_paths, soo)
+ elif rt_str in rt_l2vni_json and "paths" in rt_l2vni_json[rt_str]:
+ l2vni_json_paths = rt_l2vni_json[rt_str]["paths"]
+ l2vni_installed = is_installed(l2vni_json_paths, soo)
+ else:
+ l2vni_installed = False
+ return [global_installed, l2vni_installed]
+
+
+def test_macvrf_soo():
+ "Test MAC-VRF Site-of-Origin on pe1"
+ l2vni = 101
+ l3vni = 404
+ soo = "65000:0"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ host1 = tgen.gears["host1"]
+ host2 = tgen.gears["host2"]
+ pe1 = tgen.gears["PE1"]
+ pe2 = tgen.gears["PE2"]
+
+ # Collect pe2 RD/Originator-IP
+ pe2_bgp_vni = get_bgp_l2vni_fields(pe2, l2vni)
+ pe2_rd = pe2_bgp_vni[0]
+ pe2_oip = pe2_bgp_vni[1]
+ # Collect local addrs
+ h2_mac = host2.run("ip -br link show host2-eth0").split()[2]
+ h2_ip = host2.run("ip -4 -br addr show host2-eth0").split()[2].split("/")[0]
+ pe2_mac = pe2.run("ip -br link show br101").split()[2]
+ pe2_ip = pe2.run("ip -4 -br addr show br101").split()[2].split("/")[0]
+ # Route fields
+ pe2_svi_parms = [l2vni, pe2_rd, None, pe2_mac, pe2_ip]
+ pe2_imet_parms = [l2vni, pe2_rd, pe2_oip, None, None]
+ host2_mac_parms = [l2vni, pe2_rd, None, h2_mac, None]
+ host2_neigh_parms = [l2vni, pe2_rd, None, h2_mac, h2_ip]
+ # Route strings
+ pe2_svi_rt_str, _, _ = get_evpn_rt_json_str(*pe2_svi_parms)
+ pe2_imet_rt_str, _, _ = get_evpn_rt_json_str(*pe2_imet_parms)
+ host2_mac_rt_str, _, _ = get_evpn_rt_json_str(*host2_mac_parms)
+ host2_neigh_rt_str, _, _ = get_evpn_rt_json_str(*host2_neigh_parms)
+
+ ## trigger mac/arp learn
+ host1.run("ping -c1 10.10.1.1")
+ host2.run("ping -c1 10.10.1.3")
+
+ step("Test pe2/host2 routes are installed on pe1 (global/l2vni)")
+
+ # expected state:
+ # - global table: present w/o soo
+ # - l2vni table: present w/o soo
+ assertmsg = "{} missing on {} in {}{} evpn table(s)"
+ global_parms = [pe2.name, "global", ""]
+ l2vni_parms = [pe2.name, "l2vni", l2vni]
+ # pe2's type-2 for l2vni 101 svi mac/ip
+ test_f = partial(rt_test, pe2, *pe2_svi_parms, None)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(pe2_svi_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(pe2_svi_rt_str, *l2vni_parms)
+ # pe2's type-3 for l2vni 101
+ test_f = partial(rt_test, pe2, *pe2_imet_parms, None)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(pe2_imet_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(pe2_imet_rt_str, *l2vni_parms)
+ # mac-only type-2 for host2
+ test_f = partial(rt_test, pe1, *host2_mac_parms, None)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(host2_mac_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(host2_mac_rt_str, *l2vni_parms)
+ # mac+ip type-2 for host2
+ test_f = partial(rt_test, pe1, *host2_neigh_parms, None)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(host2_neigh_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(host2_neigh_rt_str, *l2vni_parms)
+
+ step("Add valid SoO config to pe2")
+ test_f = partial(change_soo, pe2, soo, l2vni)
+ _, res = topotest.run_and_expect(test_f, True, count=10, wait=1)
+ assertmsg = "soo '{}' not properly applied on {}".format(soo, pe2.name)
+ assert res == True, assertmsg
+
+ step("Test valid config applied to L2VNI on pe2")
+ ## expected state:
+ ## - global table: present w/ soo
+ ## - l2vni table: present w/ soo
+ assertmsg = "{} not originated with soo {} by {} in {}{} evpn table(s)"
+ global_parms = [soo, pe2.name, "global", ""]
+ l2vni_parms = [soo, pe2.name, "l2vni", l2vni]
+ # type-2 for l2vni 101 svi mac/ip
+ test_f = partial(rt_test, pe2, *pe2_svi_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(pe2_svi_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(pe2_svi_rt_str, *l2vni_parms)
+ # type-3 for l2vni 101
+ test_f = partial(rt_test, pe2, *pe2_imet_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(pe2_imet_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(pe2_imet_rt_str, *l2vni_parms)
+
+ step("Test invalid SoO config on pe2")
+ test_f = partial(change_soo, pe2, "1:1:1", l2vni)
+ _, res = topotest.run_and_expect(test_f, False, count=10, wait=1)
+ assertmsg = "soo '1:1:1' should not have been allowed on {}".format(pe2.name)
+ assert res == False, assertmsg
+
+ step("Test valid SoO applied to host2 routes (mac-only + mac/ip) on pe2")
+
+ ## expected state:
+ ## - global table: present w/ soo
+ ## - l2vni table: present w/ soo
+ assertmsg = "{} not originated with soo {} by {} in {}{} evpn table(s)"
+ global_parms = [soo, pe1.name, "global", ""]
+ l2vni_parms = [soo, pe1.name, "l2vni", l2vni]
+ # mac-only type-2 for host2
+ test_f = partial(rt_test, pe2, *host2_mac_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(host2_mac_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(host2_mac_rt_str, *l2vni_parms)
+ # mac+ip type-2 for host2
+ test_f = partial(rt_test, pe2, *host2_neigh_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(host2_neigh_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(host2_neigh_rt_str, *l2vni_parms)
+
+ step("Add valid SoO to pe1")
+ test_f = partial(change_soo, pe1, soo, l2vni)
+ _, res = topotest.run_and_expect(test_f, True, count=10, wait=1)
+ assertmsg = "soo '{}' not properly applied on {}".format(soo, pe1.name)
+ assert res == True, assertmsg
+
+ step("Test pe2's routes are filtered from l2vni on pe1.")
+ ## expected state:
+ ## - global table: present w/ soo
+ ## - l2vni table: not present
+ global_assertmsg = "{} with soo {} from {} missing from global evpn table"
+ l2vni_assertmsg = "{} with soo {} from {} not filtered from {}{} evpn table"
+ global_parms = [soo, pe1.name, "global", ""]
+ l2vni_parms = [soo, pe1.name, "l2vni", l2vni]
+ # pe2's svi route
+ test_f = partial(rt_test, pe1, *pe2_svi_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, False], count=30, wait=1)
+ assert res[0] == True, global_assertmsg.format(pe2_svi_rt_str, *global_parms)
+ assert res[1] == False, l2vni_assertmsg.format(pe2_svi_rt_str, *l2vni_parms)
+ # pe2's imet route
+ test_f = partial(rt_test, pe1, *pe2_imet_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, False], count=30, wait=1)
+ assert res[0] == True, global_assertmsg.format(pe2_imet_rt_str, *global_parms)
+ assert res[1] == False, l2vni_assertmsg.format(pe2_imet_rt_str, *l2vni_parms)
+ # mac-only type-2 for host2
+ test_f = partial(rt_test, pe1, *host2_mac_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, False], count=30, wait=1)
+ assert res[0] == True, global_assertmsg.format(host2_mac_rt_str, *global_parms)
+ assert res[1] == False, l2vni_assertmsg.format(host2_mac_rt_str, *l2vni_parms)
+ # mac+ip type-2 for host2
+ test_f = partial(rt_test, pe1, *host2_neigh_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, False], count=30, wait=1)
+ assert res[0] == True, global_assertmsg.format(host2_neigh_rt_str, *global_parms)
+ assert res[1] == False, l2vni_assertmsg.format(host2_neigh_rt_str, *l2vni_parms)
+
+ step("Remove SoO from pe1")
+ test_f = partial(change_soo, pe1, "", l2vni)
+ _, res = topotest.run_and_expect(test_f, True, count=10, wait=1)
+ assertmsg = "soo '{}' not properly removed from {}".format(soo, pe1.name)
+ assert res == True, assertmsg
+
+ step("Test pe2/host2 routes are installed on pe1 (global/l2vni)")
+ ## expected state:
+ ## - global table: present w/ soo
+ ## - l2vni table: present w/ soo
+ assertmsg = "{} with soo {} missing on {} in {}{} evpn table"
+ global_parms = [soo, pe1.name, "global", ""]
+ l2vni_parms = [soo, pe1.name, "l2vni", l2vni]
+ # pe2's type-2 for l2vni 101 svi mac/ip
+ test_f = partial(rt_test, pe1, *pe2_svi_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(pe2_svi_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(pe2_svi_rt_str, *l2vni_parms)
+ # pe2's type-3 for l2vni 101
+ test_f = partial(rt_test, pe1, *pe2_imet_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(pe2_imet_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(pe2_imet_rt_str, *l2vni_parms)
+ # mac-only type-2 for host2
+ test_f = partial(rt_test, pe1, *host2_mac_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(host2_mac_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(host2_mac_rt_str, *l2vni_parms)
+ # mac+ip type-2 for host2
+ test_f = partial(rt_test, pe1, *host2_neigh_parms, soo)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(host2_neigh_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(host2_neigh_rt_str, *l2vni_parms)
+
+ step("Remove SoO from pe2")
+ test_f = partial(change_soo, pe2, "", l2vni)
+ _, res = topotest.run_and_expect(test_f, True, count=10, wait=1)
+ assertmsg = "soo '{}' not properly removed from {}".format(soo, pe2.name)
+ assert res == True, assertmsg
+
+ step("Test pe2's 'self' routes are installed on pe1 (global/l2vni)")
+ ## expected state:
+ ## - global table: present w/o soo
+ ## - l2vni table: present w/o soo
+ assertmsg = "{} missing on {} in {}{} evpn table(s)"
+ global_parms = [pe1.name, "global", ""]
+ l2vni_parms = [pe1.name, "l2vni", l2vni]
+ # pe2's type-2 for l2vni 101 svi mac/ip
+ test_f = partial(rt_test, pe1, *pe2_svi_parms, None)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(pe2_svi_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(pe2_svi_rt_str, *l2vni_parms)
+ # pe2's type-3 for l2vni 101
+ test_f = partial(rt_test, pe1, *pe2_imet_parms, None)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(pe2_imet_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(pe2_imet_rt_str, *l2vni_parms)
+ # mac-only type-2 for host2
+ test_f = partial(rt_test, pe1, *host2_mac_parms, None)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(host2_mac_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(host2_mac_rt_str, *l2vni_parms)
+ # mac+ip type-2 for host2
+ test_f = partial(rt_test, pe1, *host2_neigh_parms, None)
+ _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1)
+ assert res[0] == True, assertmsg.format(host2_neigh_rt_str, *global_parms)
+ assert res[1] == True, assertmsg.format(host2_neigh_rt_str, *l2vni_parms)
+
+ # tgen.mininet_cli()
+
+
+def test_memory_leak():
+ "Run the memory leak test and report results."
+ tgen = get_topogen()
+ if not tgen.is_memleak_enabled():
+ pytest.skip("Memory leak test/report is disabled")
+
+ tgen.report_memory_leaks()
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))