diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index ec9f12d61a..221605d985 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c @@ -5189,3 +5189,18 @@ enum bgp_attr_parse_ret bgp_attr_ignore(struct peer *peer, uint8_t type) return withdraw ? BGP_ATTR_PARSE_WITHDRAW : BGP_ATTR_PARSE_PROCEED; } + +bool route_matches_soo(struct bgp_path_info *pi, struct ecommunity *soo) +{ + struct attr *attr = pi->attr; + struct ecommunity *ecom; + + if (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) + return false; + + ecom = attr->ecommunity; + if (!ecom || !ecom->size) + return false; + + return soo_in_ecom(ecom, soo); +} diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h index 1c7e88a4f9..6cd34d301c 100644 --- a/bgpd/bgp_attr.h +++ b/bgpd/bgp_attr.h @@ -637,4 +637,6 @@ bgp_attr_set_vnc_subtlvs(struct attr *attr, #endif } +extern bool route_matches_soo(struct bgp_path_info *pi, struct ecommunity *soo); + #endif /* _QUAGGA_BGP_ATTR_H */ diff --git a/bgpd/bgp_ecommunity.c b/bgpd/bgp_ecommunity.c index a555930137..29b2250747 100644 --- a/bgpd/bgp_ecommunity.c +++ b/bgpd/bgp_ecommunity.c @@ -1765,3 +1765,18 @@ struct ecommunity *ecommunity_replace_linkbw(as_t as, struct ecommunity *ecom, return new; } + +bool soo_in_ecom(struct ecommunity *ecom, struct ecommunity *soo) +{ + if (ecom && soo) { + if ((ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_AS, + ECOMMUNITY_SITE_ORIGIN) || + ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_AS4, + ECOMMUNITY_SITE_ORIGIN) || + ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_IP, + ECOMMUNITY_SITE_ORIGIN)) && + ecommunity_include(ecom, soo)) + return true; + } + return false; +} diff --git a/bgpd/bgp_ecommunity.h b/bgpd/bgp_ecommunity.h index 94a178bbb6..d62dc2e84c 100644 --- a/bgpd/bgp_ecommunity.h +++ b/bgpd/bgp_ecommunity.h @@ -360,6 +360,8 @@ extern struct ecommunity *ecommunity_replace_linkbw(as_t as, uint64_t cum_bw, bool disable_ieee_floating); +extern bool soo_in_ecom(struct ecommunity *ecom, struct ecommunity *soo); + static inline void ecommunity_strip_rts(struct ecommunity *ecom) { uint8_t subtype = ECOMMUNITY_ROUTE_TARGET; diff --git a/bgpd/bgp_evpn.c b/bgpd/bgp_evpn.c index fc8889c175..ef1817b479 100644 --- a/bgpd/bgp_evpn.c +++ b/bgpd/bgp_evpn.c @@ -41,6 +41,7 @@ #include "bgpd/bgp_nht.h" #include "bgpd/bgp_trace.h" #include "bgpd/bgp_mpath.h" +#include "bgpd/bgp_packet.h" /* * Definitions and external declarations. @@ -48,6 +49,7 @@ DEFINE_QOBJ_TYPE(bgpevpn); DEFINE_QOBJ_TYPE(bgp_evpn_es); +DEFINE_MTYPE_STATIC(BGPD, BGP_EVPN_INFO, "BGP EVPN instance information"); DEFINE_MTYPE_STATIC(BGPD, VRF_ROUTE_TARGET, "L3 Route Target"); /* @@ -1050,7 +1052,8 @@ static void build_evpn_type5_route_extcomm(struct bgp *bgp_vrf, * type-2 routes. */ static void build_evpn_route_extcomm(struct bgpevpn *vpn, struct attr *attr, - int add_l3_ecomm) + int add_l3_ecomm, + struct ecommunity *macvrf_soo) { struct ecommunity ecom_encap; struct ecommunity ecom_sticky; @@ -1147,6 +1150,11 @@ static void build_evpn_route_extcomm(struct bgpevpn *vpn, struct attr *attr, attr, ecommunity_merge(bgp_attr_get_ecommunity(attr), &ecom_na)); } + + /* Add MAC-VRF SoO, if configured */ + if (macvrf_soo) + bgp_attr_set_ecommunity( + attr, ecommunity_merge(attr->ecommunity, macvrf_soo)); } /* @@ -2068,6 +2076,7 @@ static int update_evpn_route(struct bgp *bgp, struct bgpevpn *vpn, int route_change; bool old_is_sync = false; bool mac_only = false; + struct ecommunity *macvrf_soo = NULL; memset(&attr, 0, sizeof(attr)); @@ -2125,8 +2134,11 @@ static int update_evpn_route(struct bgp *bgp, struct bgpevpn *vpn, add_l3_ecomm = bgp_evpn_route_add_l3_ecomm_ok( vpn, p, (attr.es_flags & ATTR_ES_IS_LOCAL) ? &attr.esi : NULL); + if (bgp->evpn_info) + macvrf_soo = bgp->evpn_info->soo; + /* Set up extended community. */ - build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm); + build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm, macvrf_soo); /* First, create (or fetch) route node within the VNI. * NOTE: There is no RD here. @@ -2333,6 +2345,7 @@ void bgp_evpn_update_type2_route_entry(struct bgp *bgp, struct bgpevpn *vpn, struct prefix_evpn evp; int route_change; bool old_is_sync = false; + struct ecommunity *macvrf_soo = NULL; if (CHECK_FLAG(local_pi->flags, BGP_PATH_REMOVED)) return; @@ -2380,8 +2393,11 @@ void bgp_evpn_update_type2_route_entry(struct bgp *bgp, struct bgpevpn *vpn, vpn, &evp, (attr.es_flags & ATTR_ES_IS_LOCAL) ? &attr.esi : NULL); + if (bgp->evpn_info) + macvrf_soo = bgp->evpn_info->soo; + /* Set up extended community. */ - build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm); + build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm, macvrf_soo); seq = mac_mobility_seqnum(local_pi->attr); if (bgp_debug_zebra(NULL)) { @@ -2673,6 +2689,21 @@ int update_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn) return 0; } +/* Update Type-2/3 Routes for L2VNI. + * Called by hash_iterate() + */ +static void update_routes_for_vni_hash(struct hash_bucket *bucket, + struct bgp *bgp) +{ + struct bgpevpn *vpn; + + if (!bucket) + return; + + vpn = (struct bgpevpn *)bucket->data; + update_routes_for_vni(bgp, vpn); +} + /* * Delete (and withdraw) local routes for specified VNI from the global * table and per-VNI table. After this, remove all other routes from @@ -2720,43 +2751,60 @@ static int bgp_evpn_mcast_grp_change(struct bgp *bgp, struct bgpevpn *vpn, } /* - * There is a tunnel endpoint IP address change for this VNI, delete - * prior type-3 route (if needed) and update. + * If there is a tunnel endpoint IP address (VTEP-IP) change for this VNI. + - Deletes tip_hash entry for old VTEP-IP + - Adds tip_hash entry/refcount for new VTEP-IP + - Deletes prior type-3 route for L2VNI (if needed) + - Updates originator_ip * Note: Route re-advertisement happens elsewhere after other processing * other changes. */ -static void handle_tunnel_ip_change(struct bgp *bgp, struct bgpevpn *vpn, +static void handle_tunnel_ip_change(struct bgp *bgp_vrf, struct bgp *bgp_evpn, + struct bgpevpn *vpn, struct in_addr originator_ip) { struct prefix_evpn p; + struct in_addr old_vtep_ip; - if (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 diff --git a/bgpd/bgp_evpn.h b/bgpd/bgp_evpn.h index a034bfbd7e..076248c9f7 100644 --- a/bgpd/bgp_evpn.h +++ b/bgpd/bgp_evpn.h @@ -157,7 +157,16 @@ extern int bgp_evpn_import_route(struct bgp *bgp, afi_t afi, safi_t safi, extern int bgp_evpn_unimport_route(struct bgp *bgp, afi_t afi, safi_t safi, const struct prefix *p, struct bgp_path_info *ri); -extern int bgp_filter_evpn_routes_upon_martian_nh_change(struct bgp *bgp); +extern void +bgp_reimport_evpn_routes_upon_macvrf_soo_change(struct bgp *bgp, + struct ecommunity *old_soo, + struct ecommunity *new_soo); +extern void bgp_reimport_evpn_routes_upon_martian_change( + struct bgp *bgp, enum bgp_martian_type martian_type, void *old_martian, + void *new_martian); +extern void +bgp_filter_evpn_routes_upon_martian_change(struct bgp *bgp, + enum bgp_martian_type martian_type); extern int bgp_evpn_local_macip_del(struct bgp *bgp, vni_t vni, struct ethaddr *mac, struct ipaddr *ip, int state); diff --git a/bgpd/bgp_evpn_private.h b/bgpd/bgp_evpn_private.h index fd8d2c118f..8cee048b69 100644 --- a/bgpd/bgp_evpn_private.h +++ b/bgpd/bgp_evpn_private.h @@ -162,6 +162,13 @@ struct bgp_evpn_info { /* EVPN enable - advertise svi macip routes */ int advertise_svi_macip; + /* MAC-VRF Site-of-Origin + * - added to all routes exported from L2VNI + * - Type-2/3 routes with matching SoO not imported into L2VNI + * - Type-2/5 routes with matching SoO not imported into L3VNI + */ + struct ecommunity *soo; + /* PIP feature knob */ bool advertise_pip; /* PIP IP (sys ip) */ @@ -680,6 +687,8 @@ extern void bgp_evpn_handle_autort_change(struct bgp *bgp); extern void bgp_evpn_handle_vrf_rd_change(struct bgp *bgp_vrf, int withdraw); extern void bgp_evpn_handle_rd_change(struct bgp *bgp, struct bgpevpn *vpn, int withdraw); +void bgp_evpn_handle_global_macvrf_soo_change(struct bgp *bgp, + struct ecommunity *new_soo); extern int bgp_evpn_install_routes(struct bgp *bgp, struct bgpevpn *vpn); extern int bgp_evpn_uninstall_routes(struct bgp *bgp, struct bgpevpn *vpn); extern void bgp_evpn_map_vrf_to_its_rts(struct bgp *bgp_vrf); diff --git a/bgpd/bgp_evpn_vty.c b/bgpd/bgp_evpn_vty.c index 66079cad22..3a5047f152 100644 --- a/bgpd/bgp_evpn_vty.c +++ b/bgpd/bgp_evpn_vty.c @@ -362,10 +362,11 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf, char *ecom_str; struct listnode *node, *nnode; struct vrf_route_target *l3rt; + struct bgp *bgp_evpn = NULL; json_object *json_import_rtl = NULL; json_object *json_export_rtl = NULL; - char buf2[ETHER_ADDR_STRLEN]; + bgp_evpn = bgp_get_evpn(); json_import_rtl = json_export_rtl = 0; if (json) { @@ -379,19 +380,26 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf, &bgp_vrf->vrf_prd); json_object_string_addf(json, "originatorIp", "%pI4", &bgp_vrf->originator_ip); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + json_object_string_add(json, "siteOfOrigin", ecom_str); + ecommunity_strfree(&ecom_str); + } json_object_string_add(json, "advertiseGatewayMacip", "n/a"); json_object_string_add(json, "advertiseSviMacIp", "n/a"); - json_object_string_add(json, "advertisePip", - bgp_vrf->evpn_info->advertise_pip ? - "Enabled" : "Disabled"); - json_object_string_addf(json, "sysIP", "%pI4", - &bgp_vrf->evpn_info->pip_ip); - json_object_string_add(json, "sysMac", - prefix_mac2str(&bgp_vrf->evpn_info->pip_rmac, - buf2, sizeof(buf2))); - json_object_string_add(json, "rmac", - prefix_mac2str(&bgp_vrf->rmac, - buf2, sizeof(buf2))); + if (bgp_vrf && bgp_vrf->evpn_info) { + json_object_string_add(json, "advertisePip", + bgp_vrf->evpn_info->advertise_pip + ? "Enabled" + : "Disabled"); + json_object_string_addf(json, "sysIP", "%pI4", + &bgp_vrf->evpn_info->pip_ip); + json_object_string_addf(json, "sysMac", "%pEA", + &bgp_vrf->evpn_info->pip_rmac); + } + json_object_string_addf(json, "rmac", "%pEA", &bgp_vrf->rmac); } else { vty_out(vty, "VNI: %d", bgp_vrf->l3vni); vty_out(vty, " (known to the kernel)"); @@ -406,18 +414,26 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf, vty_out(vty, "\n"); vty_out(vty, " Originator IP: %pI4\n", &bgp_vrf->originator_ip); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " MAC-VRF Site-of-Origin: %s\n", + ecom_str); + ecommunity_strfree(&ecom_str); + } vty_out(vty, " Advertise-gw-macip : %s\n", "n/a"); vty_out(vty, " Advertise-svi-macip : %s\n", "n/a"); - vty_out(vty, " Advertise-pip: %s\n", - bgp_vrf->evpn_info->advertise_pip ? "Yes" : "No"); - vty_out(vty, " System-IP: %pI4\n", - &bgp_vrf->evpn_info->pip_ip); - vty_out(vty, " System-MAC: %s\n", - prefix_mac2str(&bgp_vrf->evpn_info->pip_rmac, - buf2, sizeof(buf2))); - vty_out(vty, " Router-MAC: %s\n", - prefix_mac2str(&bgp_vrf->rmac, - buf2, sizeof(buf2))); + if (bgp_vrf && bgp_vrf->evpn_info) { + vty_out(vty, " Advertise-pip: %s\n", + bgp_vrf->evpn_info->advertise_pip ? "Yes" + : "No"); + vty_out(vty, " System-IP: %pI4\n", + &bgp_vrf->evpn_info->pip_ip); + vty_out(vty, " System-MAC: %pEA\n", + &bgp_vrf->evpn_info->pip_rmac); + } + vty_out(vty, " Router-MAC: %pEA\n", &bgp_vrf->rmac); } if (!json) @@ -433,7 +449,7 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf, else vty_out(vty, " %s\n", ecom_str); - XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + ecommunity_strfree(&ecom_str); } if (json) @@ -451,7 +467,7 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf, else vty_out(vty, " %s\n", ecom_str); - XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + ecommunity_strfree(&ecom_str); } if (json) @@ -484,6 +500,13 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json) &vpn->originator_ip); json_object_string_addf(json, "mcastGroup", "%pI4", &vpn->mcast_grp); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + json_object_string_add(json, "siteOfOrigin", ecom_str); + ecommunity_strfree(&ecom_str); + } /* per vni knob is enabled -- Enabled * Global knob is enabled -- Active * default -- Disabled @@ -499,6 +522,7 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json) json_object_string_add(json, "advertiseGatewayMacip", "Disabled"); if (!vpn->advertise_svi_macip && bgp_evpn && + bgp_evpn->evpn_info && bgp_evpn->evpn_info->advertise_svi_macip) json_object_string_add(json, "advertiseSviMacIp", "Active"); @@ -525,6 +549,14 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json) vty_out(vty, "\n"); vty_out(vty, " Originator IP: %pI4\n", &vpn->originator_ip); vty_out(vty, " Mcast group: %pI4\n", &vpn->mcast_grp); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " MAC-VRF Site-of-Origin: %s\n", + ecom_str); + ecommunity_strfree(&ecom_str); + } if (!vpn->advertise_gw_macip && bgp_evpn && bgp_evpn->advertise_gw_macip) vty_out(vty, " Advertise-gw-macip : %s\n", @@ -536,6 +568,7 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json) vty_out(vty, " Advertise-gw-macip : %s\n", "Disabled"); if (!vpn->advertise_svi_macip && bgp_evpn && + bgp_evpn->evpn_info && bgp_evpn->evpn_info->advertise_svi_macip) vty_out(vty, " Advertise-svi-macip : %s\n", "Active"); @@ -562,7 +595,7 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json) else vty_out(vty, " %s\n", ecom_str); - XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + ecommunity_strfree(&ecom_str); } if (json) @@ -580,7 +613,7 @@ static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json) else vty_out(vty, " %s\n", ecom_str); - XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + ecommunity_strfree(&ecom_str); } if (json) @@ -981,10 +1014,13 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp, char *ecom_str; struct listnode *node, *nnode; struct vrf_route_target *l3rt; + struct bgp *bgp_evpn; if (!bgp->l3vni) return; + bgp_evpn = bgp_get_evpn(); + if (json) { json_vni = json_object_new_object(); json_import_rtl = json_object_new_array(); @@ -1041,7 +1077,7 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp, vty_out(vty, " %-25s", rt_buf); } - XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + ecommunity_strfree(&ecom_str); /* If there are multiple import RTs we break here and show only * one */ @@ -1069,12 +1105,19 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp, vty_out(vty, " %-25s", rt_buf); } - XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + ecommunity_strfree(&ecom_str); /* If there are multiple export RTs we break here and show only * one */ if (!json) { - vty_out(vty, "%-37s", vrf_id_to_name(bgp->vrf_id)); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " %-25s", ecom_str); + ecommunity_strfree(&ecom_str); + } + vty_out(vty, " %-37s", vrf_id_to_name(bgp->vrf_id)); break; } } @@ -1083,11 +1126,18 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp, char vni_str[VNI_STR_LEN]; json_object_object_add(json_vni, "exportRTs", json_export_rtl); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + json_object_string_add(json_vni, "siteOfOrigin", + ecom_str); + ecommunity_strfree(&ecom_str); + } snprintf(vni_str, sizeof(vni_str), "%u", bgp->l3vni); json_object_object_add(json, vni_str, json_vni); - } else { + } else vty_out(vty, "\n"); - } } static void show_vni_entry(struct hash_bucket *bucket, void *args[]) @@ -1213,7 +1263,14 @@ static void show_vni_entry(struct hash_bucket *bucket, void *args[]) /* If there are multiple export RTs we break here and show only * one */ if (!json) { - vty_out(vty, "%-37s", + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " %-25s", ecom_str); + ecommunity_strfree(&ecom_str); + } + vty_out(vty, " %-37s", vrf_id_to_name(vpn->tenant_vrf_id)); break; } @@ -1223,11 +1280,18 @@ static void show_vni_entry(struct hash_bucket *bucket, void *args[]) char vni_str[VNI_STR_LEN]; json_object_object_add(json_vni, "exportRTs", json_export_rtl); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + json_object_string_add(json_vni, "siteOfOrigin", + ecom_str); + ecommunity_strfree(&ecom_str); + } snprintf(vni_str, sizeof(vni_str), "%u", vpn->vni); json_object_object_add(json, vni_str, json_vni); - } else { + } else vty_out(vty, "\n"); - } } static int bgp_show_ethernet_vpn(struct vty *vty, struct prefix_rd *prd, @@ -3276,8 +3340,9 @@ static void evpn_show_all_vnis(struct vty *vty, struct bgp *bgp, if (!json) { vty_out(vty, "Flags: * - Kernel\n"); - vty_out(vty, " %-10s %-4s %-21s %-25s %-25s %-37s\n", "VNI", - "Type", "RD", "Import RT", "Export RT", "Tenant VRF"); + vty_out(vty, " %-10s %-4s %-21s %-25s %-25s %-25s %-37s\n", + "VNI", "Type", "RD", "Import RT", "Export RT", + "MAC-VRF Site-of-Origin", "Tenant VRF"); } /* print all L2 VNIS */ @@ -3923,6 +3988,58 @@ DEFPY(bgp_evpn_advertise_svi_ip_vni, return CMD_SUCCESS; } +DEFPY(macvrf_soo_global, macvrf_soo_global_cmd, + "mac-vrf soo ASN:NN_OR_IP-ADDRESS:NN$soo", + "EVPN MAC-VRF\n" + "Site-of-Origin extended community\n" + "VPN extended community\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + struct bgp *bgp_evpn = bgp_get_evpn(); + struct ecommunity *ecomm_soo; + + if (!bgp || !bgp_evpn || !bgp_evpn->evpn_info) + return CMD_WARNING; + + if (bgp != bgp_evpn) { + vty_out(vty, + "%% Please configure MAC-VRF SoO in the EVPN underlay: %s\n", + bgp_evpn->name_pretty); + return CMD_WARNING_CONFIG_FAILED; + } + + ecomm_soo = ecommunity_str2com(soo, ECOMMUNITY_SITE_ORIGIN, 0); + if (!ecomm_soo) { + vty_out(vty, "%% Malformed SoO extended community\n"); + return CMD_WARNING_CONFIG_FAILED; + } + ecommunity_str(ecomm_soo); + + bgp_evpn_handle_global_macvrf_soo_change(bgp_evpn, ecomm_soo); + + return CMD_SUCCESS; +} + +DEFPY(no_macvrf_soo_global, no_macvrf_soo_global_cmd, + "no mac-vrf soo [ASN:NN_OR_IP-ADDRESS:NN$soo]", + NO_STR + "EVPN MAC-VRF\n" + "Site-of-Origin extended community\n" + "VPN extended community\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + struct bgp *bgp_evpn = bgp_get_evpn(); + + if (!bgp || !bgp_evpn || !bgp_evpn->evpn_info) + return CMD_WARNING; + + if (bgp_evpn) + bgp_evpn_handle_global_macvrf_soo_change(bgp_evpn, + NULL /* new_soo */); + + return CMD_SUCCESS; +} + DEFUN_HIDDEN (bgp_evpn_advertise_vni_subnet, bgp_evpn_advertise_vni_subnet_cmd, "advertise-subnet", @@ -7158,6 +7275,15 @@ void bgp_config_write_evpn_info(struct vty *vty, struct bgp *bgp, afi_t afi, if (bgp->evpn_info->advertise_svi_macip) vty_out(vty, " advertise-svi-ip\n"); + if (bgp->evpn_info->soo) { + char *ecom_str; + + ecom_str = ecommunity_ecom2str(bgp->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " mac-vrf soo %s\n", ecom_str); + ecommunity_strfree(&ecom_str); + } + if (bgp->resolve_overlay_index) vty_out(vty, " enable-resolve-overlay-index\n"); @@ -7390,6 +7516,8 @@ void bgp_ethernetvpn_init(void) install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_default_gw_cmd); install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_default_gw_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_svi_ip_cmd); + install_element(BGP_EVPN_NODE, &macvrf_soo_global_cmd); + install_element(BGP_EVPN_NODE, &no_macvrf_soo_global_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_type5_cmd); install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_type5_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_default_originate_cmd); diff --git a/bgpd/bgp_mac.c b/bgpd/bgp_mac.c index 6272bdb884..0398e4e8c1 100644 --- a/bgpd/bgp_mac.c +++ b/bgpd/bgp_mac.c @@ -279,15 +279,29 @@ static void bgp_mac_remove_ifp_internal(struct bgp_self_mac *bsm, char *ifname, } } +/* Add/Update entry of the 'bgp mac hash' table. + * A rescan of the EVPN tables is only needed if + * a new hash bucket is allocated. + * Learning an existing mac on a new interface (or + * having an existing mac move from one interface to + * another) does not result in changes to self mac + * state, so we shouldn't trigger a rescan. + */ void bgp_mac_add_mac_entry(struct interface *ifp) { struct bgp_self_mac lookup; struct bgp_self_mac *bsm; struct bgp_self_mac *old_bsm; char *ifname; + bool mac_added = false; memcpy(&lookup.macaddr, &ifp->hw_addr, ETH_ALEN); - bsm = hash_get(bm->self_mac_hash, &lookup, bgp_mac_hash_alloc); + bsm = hash_lookup(bm->self_mac_hash, &lookup); + if (!bsm) { + bsm = hash_get(bm->self_mac_hash, &lookup, bgp_mac_hash_alloc); + /* mac is new, rescan needs to be triggered */ + mac_added = true; + } /* * Does this happen to be a move @@ -318,7 +332,8 @@ void bgp_mac_add_mac_entry(struct interface *ifp) listnode_add(bsm->ifp_list, ifname); } - bgp_mac_rescan_all_evpn_tables(&bsm->macaddr); + if (mac_added) + bgp_mac_rescan_all_evpn_tables(&bsm->macaddr); } void bgp_mac_del_mac_entry(struct interface *ifp) diff --git a/bgpd/bgp_nexthop.h b/bgpd/bgp_nexthop.h index 95e2f9165b..47b6464085 100644 --- a/bgpd/bgp_nexthop.h +++ b/bgpd/bgp_nexthop.h @@ -104,11 +104,6 @@ struct tip_addr { int refcnt; }; -struct bgp_addrv6 { - struct in6_addr addrv6; - struct list *ifp_name_list; -}; - /* Forward declaration(s). */ struct peer; struct update_subgroup; diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index 9469a0778f..0cac58ade1 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -1080,6 +1080,7 @@ void bgp_notify_io_invalid(struct peer *peer, uint8_t code, uint8_t sub_code, * @param orf_type Outbound Route Filtering type * @param when_to_refresh Whether to refresh immediately or defer * @param remove Whether to remove ORF for specified AFI/SAFI + * @param subtype BGP enhanced route refresh optional subtypes */ void bgp_route_refresh_send(struct peer *peer, afi_t afi, safi_t safi, uint8_t orf_type, uint8_t when_to_refresh, diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c index 1965cd2704..d1a68f3bcd 100644 --- a/bgpd/bgp_zebra.c +++ b/bgpd/bgp_zebra.c @@ -2984,9 +2984,9 @@ static int bgp_zebra_process_local_l3vni(ZAPI_CALLBACK_ARGS) if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug( - "Rx L3-VNI ADD VRF %s VNI %u RMAC svi-mac %pEA vrr-mac %pEA filter %s svi-if %u", - vrf_id_to_name(vrf_id), l3vni, &svi_rmac, - &vrr_rmac, + "Rx L3-VNI ADD VRF %s VNI %u Originator-IP %pI4 RMAC svi-mac %pEA vrr-mac %pEA filter %s svi-if %u", + vrf_id_to_name(vrf_id), l3vni, &originator_ip, + &svi_rmac, &vrr_rmac, filter ? "prefix-routes-only" : "none", svi_ifindex); diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index ba2985d304..c710501f05 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -80,7 +80,6 @@ #include "bgp_trace.h" DEFINE_MTYPE_STATIC(BGPD, PEER_TX_SHUTDOWN_MSG, "Peer shutdown message (TX)"); -DEFINE_MTYPE_STATIC(BGPD, BGP_EVPN_INFO, "BGP EVPN instance information"); DEFINE_QOBJ_TYPE(bgp_master); DEFINE_QOBJ_TYPE(bgp); DEFINE_QOBJ_TYPE(peer); @@ -3403,8 +3402,6 @@ static struct bgp *bgp_create(as_t *as, const char *name, /* assign a unique rd id for auto derivation of vrf's RD */ bf_assign_index(bm->rd_idspace, bgp->vrf_rd_id); - bgp->evpn_info = XCALLOC(MTYPE_BGP_EVPN_INFO, - sizeof(struct bgp_evpn_info)); bgp_evpn_init(bgp); bgp_evpn_vrf_es_init(bgp); bgp_pbr_init(bgp); @@ -3971,7 +3968,6 @@ void bgp_free(struct bgp *bgp) bgp_evpn_cleanup(bgp); bgp_pbr_cleanup(bgp); bgp_srv6_cleanup(bgp); - XFREE(MTYPE_BGP_EVPN_INFO, bgp->evpn_info); for (afi = AFI_IP; afi < AFI_MAX; afi++) { enum vpn_policy_direction dir; @@ -8390,3 +8386,16 @@ static ssize_t printfrr_bp(struct fbuf *buf, struct printfrr_eargs *ea, return bprintfrr(buf, "%s(%s)", peer->host, peer->hostname ? peer->hostname : "Unknown"); } + +const struct message bgp_martian_type_str[] = { + {BGP_MARTIAN_IF_IP, "Self Interface IP"}, + {BGP_MARTIAN_TUN_IP, "Self Tunnel IP"}, + {BGP_MARTIAN_IF_MAC, "Self Interface MAC"}, + {BGP_MARTIAN_RMAC, "Self RMAC"}, + {BGP_MARTIAN_SOO, "Self Site-of-Origin"}, + {0}}; + +const char *bgp_martian_type2str(enum bgp_martian_type mt) +{ + return lookup_msg(bgp_martian_type_str, mt, "Unknown Martian Type"); +} diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index ecd122fee2..6deb7d466c 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -2105,6 +2105,26 @@ enum peer_change_type { peer_change_reset_out, }; +/* Enumeration of martian ("self") entry types. + * Routes carrying fields that match a self entry are considered martians + * and should be handled accordingly, i.e. dropped or import-filtered. + * Note: + * These "martians" are separate from routes optionally allowed via + * 'bgp allow-martian-nexthop'. The optionally allowed martians are + * simply prefixes caught by ipv4_martian(), i.e. routes outside + * the non-reserved IPv4 Unicast address space. + */ +enum bgp_martian_type { + BGP_MARTIAN_IF_IP, /* bgp->address_hash */ + BGP_MARTIAN_TUN_IP, /* bgp->tip_hash */ + BGP_MARTIAN_IF_MAC, /* bgp->self_mac_hash */ + BGP_MARTIAN_RMAC, /* bgp->rmac */ + BGP_MARTIAN_SOO, /* bgp->evpn_info->macvrf_soo */ +}; + +extern const struct message bgp_martian_type_str[]; +extern const char *bgp_martian_type2str(enum bgp_martian_type mt); + extern struct bgp_master *bm; extern unsigned int multipath_num; diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index a2585f3a57..b3252dffc0 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -3229,6 +3229,77 @@ Example configuration: exit-address-family ! +.. _bgp-evpn-mac-vrf-site-of-origin: + +EVPN MAC-VRF Site-of-Origin +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In some EVPN deployments it is useful to associate a logical VTEP's Layer 2 +domain (MAC-VRF) with a Site-of-Origin "site" identifier. This provides a +BGP topology-independent means of marking and import-filtering EVPN routes +originated from a particular L2 domain. One situation where this is valuable +is when deploying EVPN using anycast VTEPs, i.e. Active/Active MLAG, as it +can be used to avoid ownership conflicts between the two control planes +(EVPN vs MLAG). + +Example Use Case (MLAG Anycast VTEPs): + +During normal operation, an MLAG VTEP will advertise EVPN routes for attached +hosts using a shared anycast IP as the BGP next-hop. It is expected for its +MLAG peer to drop routes originated by the MLAG Peer since they have a Martian +(self) next-hop. However, prior to the anycast IP being assigned to the local +system, the anycast BGP next-hop will not be considered a Martian (self) IP. +This results in a timing window where hosts that are locally attached to the +MLAG pair's L2 domain can be learned both as "local" (via MLAG) or "remote" +(via an EVPN route with a non-local next-hop). This can trigger erroneous MAC +Mobility events, as the host "moves" between one MLAG Peer's Unique VTEP-IP +and the shared anycast VTEP-IP, which causes unnecessary control plane and +data plane events to propagate throughout the EVPN domain. +By associating the MAC-VRF of both MLAG VTEPs with the same site identifier, +EVPN routes originated by one MLAG VTEP will ignored by its MLAG peer, ensuring +that only the MLAG control plane attempts to take ownership of local hosts. + +The EVPN MAC-VRF Site-of-Origin feature works by influencing two behaviors: + +1. All EVPN routes originating from the local MAC-VRF will have a + Site-of-Origin extended community added to the route, matching the + configured value. +2. EVPN routes will be subjected to a "self SoO" check during MAC-VRF + or IP-VRF import processing. If the EVPN route is found to carry a + Site-of-Origin extended community whose value matches the locally + configured MAC-VRF Site-of-Origin, the route will be maintained in + the global EVPN RIB ("show bgp l2vpn evpn route") but will not be + imported into the corresponding MAC-VRF ("show bgp vni") or IP-VRF + ("show bgp [vrf ] [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 + +Example configuration: + +.. code-block:: frr + + router bgp 100 + neighbor 192.168.0.1 remote-as 101 + ! + address-family ipv4 l2vpn evpn + neighbor 192.168.0.1 activate + advertise-all-vni + mac-vrf soo 100.64.0.0:777 + exit-address-family + +This configuration ensures: + +1. EVPN routes originated from a local L2VNI will have a Site-of-Origin + extended community with the value ``100.64.0.0:777`` +2. Received EVPN routes carrying a Site-of-Origin extended community with the + value ``100.64.0.0:777`` will not be imported into a local MAC-VRF (L2VNI) + or IP-VRF (L3VNI). + .. _bgp-evpn-mh: EVPN Multihoming diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/bgpd.conf new file mode 100644 index 0000000000..cdf4cb4feb --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/ospfd.conf new file mode 100644 index 0000000000..2db7edb806 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/ospfd.conf @@ -0,0 +1,13 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.20.20.20/32 area 0 +! +int P1-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int P1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/zebra.conf new file mode 100644 index 0000000000..95b5da8402 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/zebra.conf @@ -0,0 +1,7 @@ +! +interface lo + ip address 10.20.20.20/32 +interface P1-eth0 + ip address 10.20.1.2/24 +interface P1-eth1 + ip address 10.20.2.2/24 diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgp.l2vpn.evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgp.l2vpn.evpn.vni.json new file mode 100644 index 0000000000..9f93635c21 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgp.l2vpn.evpn.vni.json @@ -0,0 +1,19 @@ +{ + "vni":101, + "type":"L2", + "inKernel":"True", + "rd":"10.10.10.10:101", + "originatorIp":"10.10.10.10", + "mcastGroup":"0.0.0.0", + "siteOfOrigin":"65000:0", + "advertiseGatewayMacip":"Disabled", + "advertiseSviMacIp":"Active", + "sviInterface":"br101", + "importRts":[ + "65000:101" + ], + "exportRts":[ + "65000:101" + ] +} + diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgpd.conf new file mode 100644 index 0000000000..f839443025 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65000 + timers 3 9 + bgp router-id 10.10.10.10 + no bgp default ipv4-unicast + neighbor 10.30.30.30 remote-as 65000 + neighbor 10.30.30.30 update-source lo + neighbor 10.30.30.30 timers 3 10 + ! + address-family l2vpn evpn + neighbor 10.30.30.30 activate + advertise-all-vni + advertise-svi-ip + vni 101 + rd 10.10.10.10:101 + route-target import 65000:101 + route-target export 65000:101 + exit-vni + advertise-svi-ip diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/evpn.vni.json new file mode 100644 index 0000000000..4bea8b384f --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/evpn.vni.json @@ -0,0 +1,17 @@ +{ + "vni":101, + "type":"L2", + "tenantVrf":"VRF-A", + "vxlanInterface":"vxlan101", + "vtepIp":"10.10.10.10", + "mcastGroup":"0.0.0.0", + "advertiseGatewayMacip":"No", + "numRemoteVteps":1, + "remoteVteps":[ + { + "ip":"10.30.30.30", + "flood":"HER" + } + ] +} + diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/ospfd.conf new file mode 100644 index 0000000000..f1c2b42dc1 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/ospfd.conf @@ -0,0 +1,9 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.10.10.10/32 area 0 +! +int PE1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/zebra.conf new file mode 100644 index 0000000000..e2699475c9 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/zebra.conf @@ -0,0 +1,8 @@ +! +log file zebra.log +! +interface lo + ip address 10.10.10.10/32 +interface PE1-eth1 + ip address 10.20.1.1/24 +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgp.l2vpn.evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgp.l2vpn.evpn.vni.json new file mode 100644 index 0000000000..63ac730144 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgp.l2vpn.evpn.vni.json @@ -0,0 +1,19 @@ +{ + "vni":101, + "type":"L2", + "inKernel":"True", + "rd":"10.30.30.30:101", + "originatorIp":"10.30.30.30", + "mcastGroup":"0.0.0.0", + "siteOfOrigin":"65000:0", + "advertiseGatewayMacip":"Disabled", + "advertiseSviMacIp":"Active", + "sviInterface":"br101", + "importRts":[ + "65000:101" + ], + "exportRts":[ + "65000:101" + ] +} + diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgpd.conf new file mode 100644 index 0000000000..9a0830d8a3 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65000 + timers bgp 3 9 + bgp router-id 10.30.30.30 + no bgp default ipv4-unicast + neighbor 10.10.10.10 remote-as 65000 + neighbor 10.10.10.10 update-source lo + neighbor 10.10.10.10 timers 3 10 + ! + address-family l2vpn evpn + neighbor 10.10.10.10 activate + advertise-all-vni + advertise-svi-ip + vni 101 + rd 10.30.30.30:101 + route-target import 65000:101 + route-target export 65000:101 + exit-vni + advertise-svi-ip diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/evpn.vni.json new file mode 100644 index 0000000000..5566fff954 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/evpn.vni.json @@ -0,0 +1,16 @@ +{ + "vni":101, + "type":"L2", + "tenantVrf":"VRF-A", + "vxlanInterface":"vxlan101", + "vtepIp":"10.30.30.30", + "mcastGroup":"0.0.0.0", + "advertiseGatewayMacip":"No", + "numRemoteVteps":1, + "remoteVteps":[ + { + "ip":"10.10.10.10", + "flood":"HER" + } + ] +} diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/ospfd.conf new file mode 100644 index 0000000000..065c993303 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/ospfd.conf @@ -0,0 +1,9 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.30.30.30/32 area 0 +! +int PE2-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/zebra.conf new file mode 100644 index 0000000000..9738916ab0 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/zebra.conf @@ -0,0 +1,6 @@ +! +interface lo + ip address 10.30.30.30/32 +interface PE2-eth0 + ip address 10.20.2.3/24 +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/bgpd.conf new file mode 100644 index 0000000000..cdf4cb4feb --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/ospfd.conf new file mode 100644 index 0000000000..cdf4cb4feb --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/ospfd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/zebra.conf new file mode 100644 index 0000000000..91fae9eeba --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/zebra.conf @@ -0,0 +1,3 @@ +! +int host1-eth0 + ip address 10.10.1.55/24 diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/bgpd.conf new file mode 100644 index 0000000000..cdf4cb4feb --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/ospfd.conf new file mode 100644 index 0000000000..cdf4cb4feb --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/ospfd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/zebra.conf new file mode 100644 index 0000000000..df9adeb3b5 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/zebra.conf @@ -0,0 +1,3 @@ +! +interface host2-eth0 + ip address 10.10.1.56/24 diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/test_bgp_evpn_vxlan_macvrf_soo.py b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/test_bgp_evpn_vxlan_macvrf_soo.py new file mode 100755 index 0000000000..558f7379e9 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/test_bgp_evpn_vxlan_macvrf_soo.py @@ -0,0 +1,839 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: GPL-2.0-or-later +# +# test_bgp_evpn_vxlan_macvrf_soo.py +# +# May 10 2023, Trey Aspelund +# +# 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))