mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-05-19 15:16:00 +00:00
Merge pull request #12971 from taspelund/trey/mac_vrf_soo_upstream
bgpd: Add MAC-VRF Site-of-Origin support
This commit is contained in:
commit
56a10caa03
@ -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);
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
500
bgpd/bgp_evpn.c
500
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 (IPV4_ADDR_SAME(&vpn->originator_ip, &originator_ip))
|
||||
if (bgp_vrf) /* L3VNI */
|
||||
old_vtep_ip = bgp_vrf->originator_ip;
|
||||
else /* L2VNI */
|
||||
old_vtep_ip = vpn->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 (!is_route_matching_for_vni(bgp, vpn, pi))
|
||||
continue;
|
||||
|
||||
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);
|
||||
if (install) {
|
||||
if (bgp_evpn_route_matches_macvrf_soo(
|
||||
pi, evp))
|
||||
continue;
|
||||
|
||||
bgp_dest_unlock_node(rd_dest);
|
||||
bgp_dest_unlock_node(dest);
|
||||
return ret;
|
||||
}
|
||||
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,105 @@ 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.
|
||||
*/
|
||||
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)) {
|
||||
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;
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
/* 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);
|
||||
@ -6085,18 +6317,33 @@ int bgp_filter_evpn_routes_upon_martian_nh_change(struct bgp *bgp)
|
||||
|
||||
for (pi = bgp_dest_get_bgp_path_info(dest); pi;
|
||||
pi = pi->next) {
|
||||
bool affected = false;
|
||||
const struct prefix *p;
|
||||
|
||||
/* 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 (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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
17
bgpd/bgpd.c
17
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");
|
||||
}
|
||||
|
20
bgpd/bgpd.h
20
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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -0,0 +1 @@
|
||||
!
|
@ -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
|
||||
!
|
@ -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
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
@ -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
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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
|
||||
!
|
@ -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
|
||||
!
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
@ -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
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
!
|
@ -0,0 +1,6 @@
|
||||
!
|
||||
interface lo
|
||||
ip address 10.30.30.30/32
|
||||
interface PE2-eth0
|
||||
ip address 10.20.2.3/24
|
||||
!
|
@ -0,0 +1 @@
|
||||
!
|
@ -0,0 +1 @@
|
||||
!
|
@ -0,0 +1,3 @@
|
||||
!
|
||||
int host1-eth0
|
||||
ip address 10.10.1.55/24
|
@ -0,0 +1 @@
|
||||
!
|
@ -0,0 +1 @@
|
||||
!
|
@ -0,0 +1,3 @@
|
||||
!
|
||||
interface host2-eth0
|
||||
ip address 10.10.1.56/24
|
@ -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))
|
Loading…
Reference in New Issue
Block a user