Merge pull request #12971 from taspelund/trey/mac_vrf_soo_upstream

bgpd: Add MAC-VRF Site-of-Origin support
This commit is contained in:
Russ White 2023-06-20 09:08:28 -04:00 committed by GitHub
commit 56a10caa03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1782 additions and 120 deletions

View File

@ -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);
}

View File

@ -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 */

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
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, vpn, &p);
delete_evpn_route(bgp_evpn, vpn, &p);
/* Update the tunnel IP and re-advertise all routes for this VNI. */
vpn->originator_ip = originator_ip;
} else
bgp_vrf->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,11 +3797,17 @@ 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
if (!is_route_matching_for_vni(bgp, vpn, pi))
continue;
if (install) {
if (bgp_evpn_route_matches_macvrf_soo(
pi, evp))
continue;
ret = install_evpn_route_entry(bgp, vpn,
evp, pi);
} else
ret = uninstall_evpn_route_entry(
bgp, vpn, evp, pi);
@ -3740,7 +3830,6 @@ static int install_uninstall_routes_for_vni(struct bgp *bgp,
}
}
}
}
return 0;
}
@ -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,
/* 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

View File

@ -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);

View File

@ -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);

View File

@ -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");
if (bgp_vrf && bgp_vrf->evpn_info) {
json_object_string_add(json, "advertisePip",
bgp_vrf->evpn_info->advertise_pip ?
"Enabled" : "Disabled");
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)));
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");
if (bgp_vrf && bgp_vrf->evpn_info) {
vty_out(vty, " Advertise-pip: %s\n",
bgp_vrf->evpn_info->advertise_pip ? "Yes" : "No");
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)));
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,11 +1105,18 @@ 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) {
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,12 +1126,19 @@ 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,6 +1263,13 @@ 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) {
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,12 +1280,19 @@ 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,
enum bgp_show_type type, void *output_arg,
@ -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);

View File

@ -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_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,6 +332,7 @@ void bgp_mac_add_mac_entry(struct interface *ifp)
listnode_add(bsm->ifp_list, ifname);
}
if (mac_added)
bgp_mac_rescan_all_evpn_tables(&bsm->macaddr);
}

View File

@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -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");
}

View File

@ -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;

View File

@ -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

View File

@ -0,0 +1 @@
!

View File

@ -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
!

View File

@ -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

View File

@ -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"
]
}

View File

@ -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

View File

@ -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"
}
]
}

View File

@ -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
!

View File

@ -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
!

View File

@ -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"
]
}

View File

@ -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

View File

@ -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"
}
]
}

View File

@ -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
!

View File

@ -0,0 +1,6 @@
!
interface lo
ip address 10.30.30.30/32
interface PE2-eth0
ip address 10.20.2.3/24
!

View File

@ -0,0 +1 @@
!

View File

@ -0,0 +1,3 @@
!
int host1-eth0
ip address 10.10.1.55/24

View File

@ -0,0 +1 @@
!

View File

@ -0,0 +1,3 @@
!
interface host2-eth0
ip address 10.10.1.56/24

View File

@ -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))