From 00a7710c25aae45874622d69f5cea6ea6dff0a47 Mon Sep 17 00:00:00 2001 From: Anuradha Karuppiah Date: Wed, 5 Aug 2020 07:13:55 -0700 Subject: [PATCH] zebra: support for lacp bypass with EVPN MH Feature overview: ================= A 802.3ad bond can be setup to allow lacp-bypass. This is done to enable servers to pxe boot without a LACP license i.e. allows the bond to go oper up (with a single link) without LACP converging. If an ES-bond is oper-up in an "LACP-bypass" state MH treats it as a non-ES bond. This involves the following special handling - 1. If the bond is in a bypass-state the associated ES is placed in a bypass state. 2. If an ES is in a bypass state - a. DF election is disabled (i.e. assumed DF) b. SPH filter is not installed. 3. MACs learnt via the host bond are advertised with a zero ESI. When the ES moves out of "bypass" the MACs are moved from a zero-ESI to the correct non-zero id. This is treated as a local station move. Implementation: =============== When (a) an ES is detached from a hostbond or (b) an ES-bond goes into LACP bypass zebra deletes all the local macs (with that ES as destination) in the kernel and its local db. BGP re-sends any imported MAC-IP routes that may exist with this ES destination as remote routes i.e. zebra can end up programming a MAC that was perviously local as remote pointing to a VTEP-ECMP group. When an ES is attached to a hostbond or an ES-bond goes LACP-up (out of bypss) zebra again deletes all the local macs in the kernel and its local db. At this point BGP resends any imported MAC-IP routes that may exist with this ES destination as sync routes i.e. zebra can end up programming a MAC that was perviously remote as local pointing to an access port. Signed-off-by: Anuradha Karuppiah --- zebra/if_netlink.c | 28 ++++- zebra/interface.c | 3 + zebra/interface.h | 11 +- zebra/zebra_dplane.c | 18 +++ zebra/zebra_dplane.h | 5 + zebra/zebra_evpn_mac.c | 44 +++++--- zebra/zebra_evpn_mac.h | 5 +- zebra/zebra_evpn_mh.c | 236 ++++++++++++++++++++++++++++++++------- zebra/zebra_evpn_mh.h | 6 + zebra/zebra_evpn_neigh.c | 6 +- zebra/zebra_l2.c | 64 ++++++++++- zebra/zebra_l2.h | 2 +- zebra/zebra_vxlan.c | 16 +-- 13 files changed, 368 insertions(+), 76 deletions(-) diff --git a/zebra/if_netlink.c b/zebra/if_netlink.c index 3828f8800f..61a1807fc9 100644 --- a/zebra/if_netlink.c +++ b/zebra/if_netlink.c @@ -719,6 +719,20 @@ static void netlink_proc_dplane_if_protodown(struct zebra_if *zif, } } +static uint8_t netlink_parse_lacp_bypass(struct rtattr **linkinfo) +{ + uint8_t bypass = 0; + struct rtattr *mbrinfo[IFLA_BOND_SLAVE_MAX + 1]; + + parse_rtattr_nested(mbrinfo, IFLA_BOND_SLAVE_MAX, + linkinfo[IFLA_INFO_SLAVE_DATA]); + if (mbrinfo[IFLA_BOND_SLAVE_AD_RX_BYPASS]) + bypass = *(uint8_t *)RTA_DATA( + mbrinfo[IFLA_BOND_SLAVE_AD_RX_BYPASS]); + + return bypass; +} + /* * Called from interface_lookup_netlink(). This function is only used * during bootstrap. @@ -743,6 +757,7 @@ static int netlink_interface(struct nlmsghdr *h, ns_id_t ns_id, int startup) ifindex_t bond_ifindex = IFINDEX_INTERNAL; struct zebra_if *zif; ns_id_t link_nsid = ns_id; + uint8_t bypass = 0; zns = zebra_ns_lookup(ns_id); ifi = NLMSG_DATA(h); @@ -816,6 +831,7 @@ static int netlink_interface(struct nlmsghdr *h, ns_id_t ns_id, int startup) } else if (slave_kind && (strcmp(slave_kind, "bond") == 0)) { zif_slave_type = ZEBRA_IF_SLAVE_BOND; bond_ifindex = *(ifindex_t *)RTA_DATA(tb[IFLA_MASTER]); + bypass = netlink_parse_lacp_bypass(linkinfo); } else zif_slave_type = ZEBRA_IF_SLAVE_OTHER; } @@ -882,7 +898,7 @@ static int netlink_interface(struct nlmsghdr *h, ns_id_t ns_id, int startup) if (IS_ZEBRA_IF_BRIDGE_SLAVE(ifp)) zebra_l2if_update_bridge_slave(ifp, bridge_ifindex, ns_id); else if (IS_ZEBRA_IF_BOND_SLAVE(ifp)) - zebra_l2if_update_bond_slave(ifp, bond_ifindex); + zebra_l2if_update_bond_slave(ifp, bond_ifindex, !!bypass); if (tb[IFLA_PROTO_DOWN]) { uint8_t protodown; @@ -1322,6 +1338,7 @@ int netlink_link_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) struct zebra_if *zif; ns_id_t link_nsid = ns_id; ifindex_t master_infindex = IFINDEX_INTERNAL; + uint8_t bypass = 0; zns = zebra_ns_lookup(ns_id); ifi = NLMSG_DATA(h); @@ -1423,6 +1440,7 @@ int netlink_link_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) zif_slave_type = ZEBRA_IF_SLAVE_BOND; master_infindex = bond_ifindex = *(ifindex_t *)RTA_DATA(tb[IFLA_MASTER]); + bypass = netlink_parse_lacp_bypass(linkinfo); } else zif_slave_type = ZEBRA_IF_SLAVE_OTHER; } @@ -1484,7 +1502,8 @@ int netlink_link_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) bridge_ifindex, ns_id); else if (IS_ZEBRA_IF_BOND_SLAVE(ifp)) - zebra_l2if_update_bond_slave(ifp, bond_ifindex); + zebra_l2if_update_bond_slave(ifp, bond_ifindex, + !!bypass); if (tb[IFLA_PROTO_DOWN]) { uint8_t protodown; @@ -1597,7 +1616,8 @@ int netlink_link_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) bridge_ifindex, ns_id); else if (IS_ZEBRA_IF_BOND_SLAVE(ifp) || was_bond_slave) - zebra_l2if_update_bond_slave(ifp, bond_ifindex); + zebra_l2if_update_bond_slave(ifp, bond_ifindex, + !!bypass); if (tb[IFLA_PROTO_DOWN]) { uint8_t protodown; @@ -1633,6 +1653,8 @@ int netlink_link_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) if (IS_ZEBRA_IF_BOND(ifp)) zebra_l2if_update_bond(ifp, false); + if (IS_ZEBRA_IF_BOND_SLAVE(ifp)) + zebra_l2if_update_bond_slave(ifp, bond_ifindex, false); /* Special handling for bridge or VxLAN interfaces. */ if (IS_ZEBRA_IF_BRIDGE(ifp)) zebra_l2_bridge_del(ifp); diff --git a/zebra/interface.c b/zebra/interface.c index f74030e4d8..aeaf794fa2 100644 --- a/zebra/interface.c +++ b/zebra/interface.c @@ -1604,6 +1604,9 @@ static void if_dump_vty(struct vty *vty, struct interface *ifp) } } + if (zebra_if->flags & ZIF_FLAG_LACP_BYPASS) + vty_out(vty, " LACP bypass: on\n"); + zebra_evpn_if_es_print(vty, zebra_if); vty_out(vty, " protodown: %s %s\n", (zebra_if->flags & ZIF_FLAG_PROTODOWN) ? "on" : "off", diff --git a/zebra/interface.h b/zebra/interface.h index 8dcb477f10..cfdaa5d24c 100644 --- a/zebra/interface.h +++ b/zebra/interface.h @@ -286,6 +286,9 @@ struct zebra_es_if_info { esi_t esi; uint16_t df_pref; + uint8_t flags; +#define ZIF_CFG_ES_FLAG_BYPASS (1 << 0) + struct zebra_evpn_es *es; /* local ES */ }; @@ -297,7 +300,13 @@ enum zebra_if_flags { ZIF_FLAG_EVPN_MH_UPLINK_OPER_UP = (1 << 1), /* Dataplane protodown-on */ - ZIF_FLAG_PROTODOWN = (1 << 2) + ZIF_FLAG_PROTODOWN = (1 << 2), + + /* LACP bypass state is set by the dataplane on a bond member + * and inherited by the bond (if one or more bond members are in + * a bypass state the bond is placed in a bypass state) + */ + ZIF_FLAG_LACP_BYPASS = (1 << 3) }; /* `zebra' daemon local interface structure. */ diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c index db2b9e002e..b9b6b4af71 100644 --- a/zebra/zebra_dplane.c +++ b/zebra/zebra_dplane.c @@ -3250,6 +3250,24 @@ enum zebra_dplane_result dplane_local_mac_add(const struct interface *ifp, return result; } +/* + * Enqueue local mac del + */ +enum zebra_dplane_result +dplane_local_mac_del(const struct interface *ifp, + const struct interface *bridge_ifp, vlanid_t vid, + const struct ethaddr *mac) +{ + enum zebra_dplane_result result; + struct in_addr vtep_ip; + + vtep_ip.s_addr = 0; + + /* Use common helper api */ + result = mac_update_common(DPLANE_OP_MAC_DELETE, ifp, bridge_ifp, vid, + mac, vtep_ip, false, 0, 0); + return result; +} /* * Public api to init an empty context - either newly-allocated or * reset/cleared - for a MAC update. diff --git a/zebra/zebra_dplane.h b/zebra/zebra_dplane.h index 595d3fe562..f3ab1058a5 100644 --- a/zebra/zebra_dplane.h +++ b/zebra/zebra_dplane.h @@ -582,6 +582,11 @@ enum zebra_dplane_result dplane_local_mac_add(const struct interface *ifp, uint32_t set_static, uint32_t set_inactive); +enum zebra_dplane_result +dplane_local_mac_del(const struct interface *ifp, + const struct interface *bridge_ifp, vlanid_t vid, + const struct ethaddr *mac); + enum zebra_dplane_result dplane_rem_mac_del(const struct interface *ifp, const struct interface *bridge_ifp, vlanid_t vid, diff --git a/zebra/zebra_evpn_mac.c b/zebra/zebra_evpn_mac.c index 90227aa597..b2ac99ab20 100644 --- a/zebra/zebra_evpn_mac.c +++ b/zebra/zebra_evpn_mac.c @@ -753,14 +753,10 @@ static char *zebra_evpn_print_mac_flags(zebra_mac_t *mac, char *flags_buf, size_t flags_buf_sz) { snprintf(flags_buf, flags_buf_sz, "%s%s%s%s", - mac->sync_neigh_cnt ? - "N" : "", - (mac->flags & ZEBRA_MAC_ES_PEER_ACTIVE) ? - "P" : "", - (mac->flags & ZEBRA_MAC_ES_PEER_PROXY) ? - "X" : "", - (mac->flags & ZEBRA_MAC_LOCAL_INACTIVE) ? - "I" : ""); + mac->sync_neigh_cnt ? "N" : "", + (mac->flags & ZEBRA_MAC_ES_PEER_ACTIVE) ? "P" : "", + (mac->flags & ZEBRA_MAC_ES_PEER_PROXY) ? "X" : "", + (mac->flags & ZEBRA_MAC_LOCAL_INACTIVE) ? "I" : ""); return flags_buf; } @@ -1796,6 +1792,7 @@ static bool zebra_evpn_local_mac_update_fwd_info(zebra_mac_t *mac, bool es_change; ns_id_t local_ns_id = NS_DEFAULT; struct zebra_vrf *zvrf; + struct zebra_evpn_es *es; zvrf = zebra_vrf_lookup_by_id(ifp->vrf_id); if (zvrf && zvrf->zns) @@ -1803,7 +1800,10 @@ static bool zebra_evpn_local_mac_update_fwd_info(zebra_mac_t *mac, memset(&mac->fwd_info, 0, sizeof(mac->fwd_info)); - es_change = zebra_evpn_es_mac_ref_entry(mac, zif->es_info.es); + es = zif->es_info.es; + if (es && (es->flags & ZEBRA_EVPNES_BYPASS)) + es = NULL; + es_change = zebra_evpn_es_mac_ref_entry(mac, es); if (!mac->es) { /* if es is set fwd_info is not-relevant/taped-out */ @@ -2064,9 +2064,8 @@ int zebra_evpn_add_update_local_mac(struct zebra_vrf *zvrf, zebra_evpn_t *zevpn, struct interface *ifp, struct ethaddr *macaddr, vlanid_t vid, bool sticky, bool local_inactive, - bool dp_static) + bool dp_static, zebra_mac_t *mac) { - zebra_mac_t *mac; char buf[ETHER_ADDR_STRLEN]; bool mac_sticky = false; bool inform_client = false; @@ -2083,7 +2082,8 @@ int zebra_evpn_add_update_local_mac(struct zebra_vrf *zvrf, zebra_evpn_t *zevpn, assert(ifp); /* Check if we need to create or update or it is a NO-OP. */ - mac = zebra_evpn_mac_lookup(zevpn, macaddr); + if (!mac) + mac = zebra_evpn_mac_lookup(zevpn, macaddr); if (!mac) { if (IS_ZEBRA_DEBUG_VXLAN || IS_ZEBRA_DEBUG_EVPN_MH_MAC) zlog_debug( @@ -2134,6 +2134,8 @@ int zebra_evpn_add_update_local_mac(struct zebra_vrf *zvrf, zebra_evpn_t *zevpn, old_static = zebra_evpn_mac_is_static(mac); if (CHECK_FLAG(mac->flags, ZEBRA_MAC_STICKY)) mac_sticky = true; + es_change = zebra_evpn_local_mac_update_fwd_info( + mac, ifp, vid); /* * Update any changes and if changes are relevant to @@ -2142,7 +2144,7 @@ int zebra_evpn_add_update_local_mac(struct zebra_vrf *zvrf, zebra_evpn_t *zevpn, if (mac_sticky == sticky && old_ifp == ifp && old_vid == vid && old_local_inactive == local_inactive - && dp_static == old_static) { + && dp_static == old_static && !es_change) { if (IS_ZEBRA_DEBUG_VXLAN) zlog_debug( " Add/Update %sMAC %s intf %s(%u) VID %u -> VNI %u%s, " @@ -2166,15 +2168,17 @@ int zebra_evpn_add_update_local_mac(struct zebra_vrf *zvrf, zebra_evpn_t *zevpn, inform_client = true; } - es_change = zebra_evpn_local_mac_update_fwd_info( - mac, ifp, vid); /* If an es_change is detected we need to advertise * the route with a sequence that is one * greater. This is need to indicate a mac-move * to the ES peers */ if (es_change) { - mac->loc_seq = mac->loc_seq + 1; + /* update the sequence number only if the entry + * is locally active + */ + if (!local_inactive) + mac->loc_seq = mac->loc_seq + 1; /* force drop the peer/sync info as it is * simply no longer relevant */ @@ -2308,7 +2312,8 @@ int zebra_evpn_add_update_local_mac(struct zebra_vrf *zvrf, zebra_evpn_t *zevpn, return 0; } -int zebra_evpn_del_local_mac(zebra_evpn_t *zevpn, zebra_mac_t *mac) +int zebra_evpn_del_local_mac(zebra_evpn_t *zevpn, zebra_mac_t *mac, + bool clear_static) { char buf[ETHER_ADDR_STRLEN]; bool old_bgp_ready; @@ -2321,7 +2326,7 @@ int zebra_evpn_del_local_mac(zebra_evpn_t *zevpn, zebra_mac_t *mac) listcount(mac->neigh_list)); old_bgp_ready = zebra_evpn_mac_is_ready_for_bgp(mac->flags); - if (zebra_evpn_mac_is_static(mac)) { + if (!clear_static && zebra_evpn_mac_is_static(mac)) { /* this is a synced entry and can only be removed when the * es-peers stop advertising it. */ @@ -2356,6 +2361,9 @@ int zebra_evpn_del_local_mac(zebra_evpn_t *zevpn, zebra_mac_t *mac) return 0; } + /* flush the peer info */ + zebra_evpn_mac_clear_sync_info(mac); + /* Update all the neigh entries associated with this mac */ zebra_evpn_process_neigh_on_local_mac_del(zevpn, mac); diff --git a/zebra/zebra_evpn_mac.h b/zebra/zebra_evpn_mac.h index c021765843..b8159914a2 100644 --- a/zebra/zebra_evpn_mac.h +++ b/zebra/zebra_evpn_mac.h @@ -260,8 +260,9 @@ int zebra_evpn_add_update_local_mac(struct zebra_vrf *zvrf, zebra_evpn_t *zevpn, struct interface *ifp, struct ethaddr *macaddr, vlanid_t vid, bool sticky, bool local_inactive, - bool dp_static); -int zebra_evpn_del_local_mac(zebra_evpn_t *zevpn, zebra_mac_t *mac); + bool dp_static, zebra_mac_t *mac); +int zebra_evpn_del_local_mac(zebra_evpn_t *zevpn, zebra_mac_t *mac, + bool clear_static); int zebra_evpn_mac_gw_macip_add(struct interface *ifp, zebra_evpn_t *zevpn, struct ipaddr *ip, zebra_mac_t **macp, struct ethaddr *macaddr, vlanid_t vlan_id, diff --git a/zebra/zebra_evpn_mh.c b/zebra/zebra_evpn_mh.c index 0bb1ebb564..249a2f0aad 100644 --- a/zebra/zebra_evpn_mh.c +++ b/zebra/zebra_evpn_mh.c @@ -1548,20 +1548,30 @@ static bool zebra_evpn_es_br_port_dplane_update(struct zebra_evpn_es *es, return false; if (IS_ZEBRA_DEBUG_EVPN_MH_ES) - zlog_debug("es %s br-port dplane update by %s", es->esi_str, caller); + zlog_debug("es %s br-port dplane update by %s", es->esi_str, + caller); backup_nhg_id = (es->flags & ZEBRA_EVPNES_NHG_ACTIVE) ? es->nhg_id : 0; memset(&sph_filters, 0, sizeof(sph_filters)); - if (listcount(es->es_vtep_list) > ES_VTEP_MAX_CNT) { - zlog_warn("es %s vtep count %d exceeds filter cnt %d", - es->esi_str, listcount(es->es_vtep_list), - ES_VTEP_MAX_CNT); + if (es->flags & ZEBRA_EVPNES_BYPASS) { + if (IS_ZEBRA_DEBUG_EVPN_MH_ES) + zlog_debug( + "es %s SPH filter disabled as it is in bypass", + es->esi_str); } else { - for (ALL_LIST_ELEMENTS_RO(es->es_vtep_list, node, es_vtep)) { - if (es_vtep->flags & ZEBRA_EVPNES_VTEP_DEL_IN_PROG) - continue; - sph_filters[sph_filter_cnt] = es_vtep->vtep_ip; - ++sph_filter_cnt; + if (listcount(es->es_vtep_list) > ES_VTEP_MAX_CNT) { + zlog_warn("es %s vtep count %d exceeds filter cnt %d", + es->esi_str, listcount(es->es_vtep_list), + ES_VTEP_MAX_CNT); + } else { + for (ALL_LIST_ELEMENTS_RO(es->es_vtep_list, node, + es_vtep)) { + if (es_vtep->flags + & ZEBRA_EVPNES_VTEP_DEL_IN_PROG) + continue; + sph_filters[sph_filter_cnt] = es_vtep->vtep_ip; + ++sph_filter_cnt; + } } } @@ -1609,6 +1619,7 @@ static bool zebra_evpn_es_run_df_election(struct zebra_evpn_es *es, * is no need to setup the BUM block filter */ if (!(es->flags & ZEBRA_EVPNES_LOCAL) + || (es->flags & ZEBRA_EVPNES_BYPASS) || !zmh_info->es_originator_ip.s_addr) return zebra_evpn_es_df_change(es, new_non_df, caller, "not-ready"); @@ -1834,6 +1845,7 @@ static int zebra_evpn_es_send_add_to_client(struct zebra_evpn_es *es) struct zserv *client; struct stream *s; uint8_t oper_up; + bool bypass; client = zserv_find_client(ZEBRA_ROUTE_BGP, 0); /* BGP may not be running. */ @@ -1848,15 +1860,18 @@ static int zebra_evpn_es_send_add_to_client(struct zebra_evpn_es *es) oper_up = !!(es->flags & ZEBRA_EVPNES_OPER_UP); stream_putc(s, oper_up); stream_putw(s, es->df_pref); + bypass = !!(es->flags & ZEBRA_EVPNES_BYPASS); + stream_putc(s, bypass); /* Write packet size. */ stream_putw_at(s, 0, stream_get_endp(s)); if (IS_ZEBRA_DEBUG_EVPN_MH_ES) - zlog_debug("send add local es %s %pI4 active %u df_pref %u to %s", - es->esi_str, &zmh_info->es_originator_ip, - oper_up, es->df_pref, - zebra_route_string(client->proto)); + zlog_debug( + "send add local es %s %pI4 active %u df_pref %u%s to %s", + es->esi_str, &zmh_info->es_originator_ip, oper_up, + es->df_pref, bypass ? " bypass" : "", + zebra_route_string(client->proto)); client->local_es_add_cnt++; return zserv_send_message(client, s); @@ -1980,18 +1995,50 @@ static void zebra_evpn_es_setup_evis(struct zebra_evpn_es *es) } } -static void zebra_evpn_es_local_mac_update(struct zebra_evpn_es *es, - bool force_clear_static) +static void zebra_evpn_flush_local_mac(zebra_mac_t *mac, struct interface *ifp) +{ + struct zebra_if *zif; + struct interface *br_ifp; + vlanid_t vid; + + zif = ifp->info; + br_ifp = zif->brslave_info.br_if; + if (!br_ifp) + return; + + if (mac->zevpn->vxlan_if) { + zif = mac->zevpn->vxlan_if->info; + vid = zif->l2info.vxl.access_vlan; + } else { + vid = 0; + } + + /* delete the local mac from the dataplane */ + dplane_local_mac_del(ifp, br_ifp, vid, &mac->macaddr); + /* delete the local mac in zebra */ + zebra_evpn_del_local_mac(mac->zevpn, mac, true); +} + +static void zebra_evpn_es_flush_local_macs(struct zebra_evpn_es *es, + struct interface *ifp, bool add) { zebra_mac_t *mac; struct listnode *node; + struct listnode *nnode; - for (ALL_LIST_ELEMENTS_RO(es->mac_list, node, mac)) { - if (CHECK_FLAG(mac->flags, ZEBRA_MAC_ES_PEER_ACTIVE)) { - zebra_evpn_sync_mac_dp_install( - mac, false /* set_inactive */, - force_clear_static, __func__); - } + for (ALL_LIST_ELEMENTS(es->mac_list, node, nnode, mac)) { + if (!CHECK_FLAG(mac->flags, ZEBRA_MAC_LOCAL)) + continue; + + /* If ES is being attached/detached from the access port we + * need to clear local activity and peer activity and start + * over */ + if (IS_ZEBRA_DEBUG_EVPN_MH_MAC) + zlog_debug("VNI %u mac %pEA update; local ES %s %s", + mac->zevpn->vni, + &mac->macaddr, + es->esi_str, add ? "add" : "del"); + zebra_evpn_flush_local_mac(mac, ifp); } } @@ -2134,6 +2181,10 @@ static void zebra_evpn_es_local_info_set(struct zebra_evpn_es *es, if (zif->brslave_info.bridge_ifindex != IFINDEX_INTERNAL) es->flags |= ZEBRA_EVPNES_BR_PORT; + /* inherit the bypass flag from the interface */ + if (zif->flags & ZIF_FLAG_LACP_BYPASS) + es->flags |= ZEBRA_EVPNES_BYPASS; + /* setup base-vni if one doesn't already exist; the ES will get sent * to BGP as a part of that process */ @@ -2165,11 +2216,9 @@ static void zebra_evpn_es_local_info_set(struct zebra_evpn_es *es, */ zebra_evpn_es_setup_evis(es); /* if there any local macs referring to the ES as dest we - * need to set the static reference on them if the MAC is - * synced from an ES peer + * need to clear the contents and start over */ - zebra_evpn_es_local_mac_update(es, - false /* force_clear_static */); + zebra_evpn_es_flush_local_macs(es, zif->ifp, true); /* inherit EVPN protodown flags on the access port */ zebra_evpn_mh_update_protodown_es(es, true /*resync_dplane*/); @@ -2184,33 +2233,34 @@ static void zebra_evpn_es_local_info_clear(struct zebra_evpn_es **esp) if (!(es->flags & ZEBRA_EVPNES_LOCAL)) return; + zif = es->zif; + + /* if there any local macs referring to the ES as dest we + * need to clear the contents and start over + */ + zebra_evpn_es_flush_local_macs(es, zif->ifp, false); + es->flags &= ~(ZEBRA_EVPNES_LOCAL | ZEBRA_EVPNES_READY_FOR_BGP); THREAD_OFF(es->df_delay_timer); - /* remove the DF filter */ - dplane_updated = zebra_evpn_es_run_df_election(es, __func__); - /* clear EVPN protodown flags on the access port */ zebra_evpn_mh_clear_protodown_es(es); - /* if there any local macs referring to the ES as dest we - * need to clear the static reference on them - */ - zebra_evpn_es_local_mac_update(es, - true /* force_clear_static */); + /* remove the DF filter */ + dplane_updated = zebra_evpn_es_run_df_election(es, __func__); /* flush the BUM filters and backup NHG */ if (!dplane_updated) zebra_evpn_es_br_port_dplane_clear(es); /* clear the es from the parent interface */ - zif = es->zif; zif->es_info.es = NULL; es->zif = NULL; /* clear all local flags associated with the ES */ - es->flags &= ~(ZEBRA_EVPNES_OPER_UP | ZEBRA_EVPNES_BR_PORT); + es->flags &= ~(ZEBRA_EVPNES_OPER_UP | ZEBRA_EVPNES_BR_PORT + | ZEBRA_EVPNES_BYPASS); /* remove from the ES list */ list_delete_node(zmh_info->local_es_list, &es->local_es_listnode); @@ -2623,6 +2673,82 @@ static void zebra_evpn_es_df_pref_update(struct zebra_if *zif, uint16_t df_pref) zebra_evpn_es_send_add_to_client(es); } +/* If bypass mode on an es changed we set all local macs to + * inactive and drop the sync info + */ +static void zebra_evpn_es_bypass_update_macs(struct zebra_evpn_es *es, + struct interface *ifp, bool bypass) +{ + zebra_mac_t *mac; + struct listnode *node; + struct listnode *nnode; + + /* Flush all MACs linked to the ES */ + for (ALL_LIST_ELEMENTS(es->mac_list, node, nnode, mac)) { + if (!CHECK_FLAG(mac->flags, ZEBRA_MAC_LOCAL)) + continue; + + if (IS_ZEBRA_DEBUG_EVPN_MH_MAC) + zlog_debug("VNI %u mac %pEA %s update es %s", + mac->zevpn->vni, + &mac->macaddr, + bypass ? "bypass" : "non-bypass", + es->esi_str); + zebra_evpn_flush_local_mac(mac, ifp); + } +} + +void zebra_evpn_es_bypass_update(struct zebra_evpn_es *es, + struct interface *ifp, bool bypass) +{ + bool old_bypass; + bool dplane_updated; + + old_bypass = !!(es->flags & ZEBRA_EVPNES_BYPASS); + if (old_bypass == bypass) + return; + + if (bypass) + es->flags |= ZEBRA_EVPNES_BYPASS; + else + es->flags &= ~ZEBRA_EVPNES_BYPASS; + + if (IS_ZEBRA_DEBUG_EVPN_MH_ES) + zlog_debug("bond %s es %s lacp bypass changed to %s", ifp->name, + es->esi_str, bypass ? "on" : "off"); + + /* send bypass update to BGP */ + if (es->flags & ZEBRA_EVPNES_READY_FOR_BGP) + zebra_evpn_es_send_add_to_client(es); + + zebra_evpn_es_bypass_update_macs(es, ifp, bypass); + + /* re-run DF election */ + dplane_updated = zebra_evpn_es_run_df_election(es, __func__); + + /* disable SPH filter */ + if (!dplane_updated && (es->flags & ZEBRA_EVPNES_LOCAL) + && (listcount(es->es_vtep_list) > ES_VTEP_MAX_CNT)) + zebra_evpn_es_br_port_dplane_update(es, __func__); +} + +static void zebra_evpn_es_bypass_cfg_update(struct zebra_if *zif, bool bypass) +{ + bool old_bypass = !!(zif->es_info.flags & ZIF_CFG_ES_FLAG_BYPASS); + + if (old_bypass == bypass) + return; + + if (bypass) + zif->es_info.flags |= ZIF_CFG_ES_FLAG_BYPASS; + else + zif->es_info.flags &= ~ZIF_CFG_ES_FLAG_BYPASS; + + + if (zif->es_info.es) + zebra_evpn_es_bypass_update(zif->es_info.es, zif->ifp, bypass); +} + /* Only certain types of access ports can be setup as an Ethernet Segment */ bool zebra_evpn_is_if_es_capable(struct zebra_if *zif) @@ -2818,7 +2944,7 @@ static void zebra_evpn_es_json_vtep_fill(struct zebra_evpn_es *es, static void zebra_evpn_es_show_entry(struct vty *vty, struct zebra_evpn_es *es, json_object *json_array) { - char type_str[4]; + char type_str[5]; char vtep_str[ES_VTEP_LIST_STR_SZ]; if (json_array) { @@ -2839,6 +2965,8 @@ static void zebra_evpn_es_show_entry(struct vty *vty, struct zebra_evpn_es *es, json_array_string_add(json_flags, "remote"); if (es->flags & ZEBRA_EVPNES_NON_DF) json_array_string_add(json_flags, "nonDF"); + if (es->flags & ZEBRA_EVPNES_BYPASS) + json_array_string_add(json_flags, "bypass"); json_object_object_add(json, "flags", json_flags); } @@ -2860,6 +2988,8 @@ static void zebra_evpn_es_show_entry(struct vty *vty, struct zebra_evpn_es *es, strlcat(type_str, "R", sizeof(type_str)); if (es->flags & ZEBRA_EVPNES_NON_DF) strlcat(type_str, "N", sizeof(type_str)); + if (es->flags & ZEBRA_EVPNES_BYPASS) + strlcat(type_str, "B", sizeof(type_str)); zebra_evpn_es_vtep_str(vtep_str, es, sizeof(vtep_str)); @@ -2897,6 +3027,8 @@ static void zebra_evpn_es_show_entry_detail(struct vty *vty, json_array_string_add(json_flags, "remote"); if (es->flags & ZEBRA_EVPNES_NON_DF) json_array_string_add(json_flags, "nonDF"); + if (es->flags & ZEBRA_EVPNES_BYPASS) + json_array_string_add(json_flags, "bypass"); if (es->flags & ZEBRA_EVPNES_READY_FOR_BGP) json_array_string_add(json_flags, "readyForBgp"); @@ -2952,6 +3084,8 @@ static void zebra_evpn_es_show_entry_detail(struct vty *vty, vty_out(vty, " Ready for BGP: %s\n", (es->flags & ZEBRA_EVPNES_READY_FOR_BGP) ? "yes" : "no"); + if (es->flags & ZEBRA_EVPNES_BYPASS) + vty_out(vty, " LACP bypass: on\n"); vty_out(vty, " VNI Count: %d\n", listcount(es->es_evi_list)); vty_out(vty, " MAC Count: %d\n", listcount(es->mac_list)); if (es->flags & ZEBRA_EVPNES_LOCAL) @@ -2991,7 +3125,7 @@ void zebra_evpn_es_show(struct vty *vty, bool uj) if (uj) { json_array = json_object_new_array(); } else { - vty_out(vty, "Type: L local, R remote, N non-DF\n"); + vty_out(vty, "Type: B bypass, L local, R remote, N non-DF\n"); vty_out(vty, "%-30s %-4s %-21s %s\n", "ESI", "Type", "ES-IF", "VTEPs"); } @@ -3097,12 +3231,35 @@ int zebra_evpn_mh_if_write(struct vty *vty, struct interface *ifp) #ifndef VTYSH_EXTRACT_PL #include "zebra/zebra_evpn_mh_clippy.c" #endif +/* CLI for setting an ES in bypass mode */ +DEFPY_HIDDEN(zebra_evpn_es_bypass, zebra_evpn_es_bypass_cmd, + "[no] evpn mh bypass", + NO_STR "EVPN\n" EVPN_MH_VTY_STR "set bypass mode\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif; + + zif = ifp->info; + + if (no) { + zebra_evpn_es_bypass_cfg_update(zif, false); + } else { + if (!zebra_evpn_is_if_es_capable(zif)) { + vty_out(vty, + "%%DF bypass cannot be associated with this interface type\n"); + return CMD_WARNING; + } + zebra_evpn_es_bypass_cfg_update(zif, true); + } + return CMD_SUCCESS; +} + /* CLI for configuring DF preference part for an ES */ DEFPY(zebra_evpn_es_pref, zebra_evpn_es_pref_cmd, "[no$no] evpn mh es-df-pref [(1-65535)$df_pref]", NO_STR "EVPN\n" EVPN_MH_VTY_STR "preference value used for DF election\n" - "ID\n") + "pref\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif; @@ -3767,6 +3924,7 @@ void zebra_evpn_interface_init(void) install_element(INTERFACE_NODE, &zebra_evpn_es_id_cmd); install_element(INTERFACE_NODE, &zebra_evpn_es_sys_mac_cmd); install_element(INTERFACE_NODE, &zebra_evpn_es_pref_cmd); + install_element(INTERFACE_NODE, &zebra_evpn_es_bypass_cmd); install_element(INTERFACE_NODE, &zebra_evpn_mh_uplink_cmd); } diff --git a/zebra/zebra_evpn_mh.h b/zebra/zebra_evpn_mh.h index 94de84ff14..2361a70bff 100644 --- a/zebra/zebra_evpn_mh.h +++ b/zebra/zebra_evpn_mh.h @@ -60,6 +60,10 @@ struct zebra_evpn_es { * filter, SPH filter and backup NHG for fast-failover */ #define ZEBRA_EVPNES_BR_PORT (1 << 6) +/* ES is in bypass mode i.e. must not be advertised. ES-bypass is set + * when the associated host bond goes into LACP bypass + */ +#define ZEBRA_EVPNES_BYPASS (1 << 7) /* memory used for adding the es to zmh_info->es_rb_tree */ RB_ENTRY(zebra_evpn_es) rb_node; @@ -376,5 +380,7 @@ extern void zebra_evpn_l2_nh_show(struct vty *vty, bool uj); extern void zebra_evpn_acc_bd_svi_set(struct zebra_if *vlan_zif, struct zebra_if *br_zif, bool is_up); extern void zebra_evpn_acc_bd_svi_mac_add(struct interface *vlan_if); +extern void zebra_evpn_es_bypass_update(struct zebra_evpn_es *es, + struct interface *ifp, bool bypass); #endif /* _ZEBRA_EVPN_MH_H */ diff --git a/zebra/zebra_evpn_neigh.c b/zebra/zebra_evpn_neigh.c index 834ad5381d..ace5ec8a1b 100644 --- a/zebra/zebra_evpn_neigh.c +++ b/zebra/zebra_evpn_neigh.c @@ -327,9 +327,9 @@ int zebra_evpn_neigh_send_add_to_client(vni_t vni, struct ipaddr *ip, if (CHECK_FLAG(neigh_flags, ZEBRA_NEIGH_SVI_IP)) SET_FLAG(flags, ZEBRA_MACIP_TYPE_SVI_IP); - return zebra_evpn_macip_send_msg_to_client( - vni, macaddr, ip, flags, seq, ZEBRA_NEIGH_ACTIVE, - zmac ? zmac->es : NULL, ZEBRA_MACIP_ADD); + return zebra_evpn_macip_send_msg_to_client(vni, macaddr, ip, flags, seq, + ZEBRA_NEIGH_ACTIVE, zmac->es, + ZEBRA_MACIP_ADD); } /* diff --git a/zebra/zebra_l2.c b/zebra/zebra_l2.c index c1ad91c8ca..3583c5fbf4 100644 --- a/zebra/zebra_l2.c +++ b/zebra/zebra_l2.c @@ -110,6 +110,44 @@ void zebra_l2_unmap_slave_from_bridge(struct zebra_l2info_brslave *br_slave) br_slave->br_if = NULL; } +/* If any of the bond members are in bypass state the bond is placed + * in bypass state + */ +static void zebra_l2_bond_lacp_bypass_eval(struct zebra_if *bond_zif) +{ + struct listnode *node; + struct zebra_if *bond_mbr; + bool old_bypass = !!(bond_zif->flags & ZIF_FLAG_LACP_BYPASS); + bool new_bypass = false; + + if (bond_zif->bond_info.mbr_zifs) { + for (ALL_LIST_ELEMENTS_RO(bond_zif->bond_info.mbr_zifs, node, + bond_mbr)) { + if (bond_mbr->flags & ZIF_FLAG_LACP_BYPASS) { + new_bypass = true; + break; + } + } + } + + if (old_bypass == new_bypass) + return; + + if (IS_ZEBRA_DEBUG_EVPN_MH_ES || IS_ZEBRA_DEBUG_EVENT) + zlog_debug("bond %s lacp bypass changed to %s", + bond_zif->ifp->name, new_bypass ? "on" : "off"); + + if (new_bypass) + bond_zif->flags |= ZIF_FLAG_LACP_BYPASS; + else + bond_zif->flags &= ~ZIF_FLAG_LACP_BYPASS; + + if (bond_zif->es_info.es) + zebra_evpn_es_bypass_update(bond_zif->es_info.es, bond_zif->ifp, + new_bypass); +} + +/* Returns true if member was newly linked to bond */ void zebra_l2_map_slave_to_bond(struct zebra_if *zif, vrf_id_t vrf_id) { struct interface *bond_if; @@ -138,6 +176,7 @@ void zebra_l2_map_slave_to_bond(struct zebra_if *zif, vrf_id_t vrf_id) if (zebra_evpn_is_es_bond(bond_if)) zebra_evpn_mh_update_protodown_bond_mbr( zif, false /*clear*/, __func__); + zebra_l2_bond_lacp_bypass_eval(bond_zif); } } else { if (IS_ZEBRA_DEBUG_EVPN_MH_ES || IS_ZEBRA_DEBUG_EVENT) @@ -170,6 +209,7 @@ void zebra_l2_unmap_slave_from_bond(struct zebra_if *zif) __func__); listnode_delete(bond_zif->bond_info.mbr_zifs, zif); bond_slave->bond_if = NULL; + zebra_l2_bond_lacp_bypass_eval(bond_zif); } void zebra_l2if_update_bond(struct interface *ifp, bool add) @@ -378,14 +418,36 @@ void zebra_l2if_update_bridge_slave(struct interface *ifp, } } -void zebra_l2if_update_bond_slave(struct interface *ifp, ifindex_t bond_ifindex) +void zebra_l2if_update_bond_slave(struct interface *ifp, ifindex_t bond_ifindex, + bool new_bypass) { struct zebra_if *zif; ifindex_t old_bond_ifindex; + bool old_bypass; + struct zebra_l2info_bondslave *bond_mbr; zif = ifp->info; assert(zif); + old_bypass = !!(zif->flags & ZIF_FLAG_LACP_BYPASS); + if (old_bypass != new_bypass) { + if (IS_ZEBRA_DEBUG_EVPN_MH_ES || IS_ZEBRA_DEBUG_EVENT) + zlog_debug("bond-mbr %s lacp bypass changed to %s", + zif->ifp->name, new_bypass ? "on" : "off"); + + if (new_bypass) + zif->flags |= ZIF_FLAG_LACP_BYPASS; + else + zif->flags &= ~ZIF_FLAG_LACP_BYPASS; + + bond_mbr = &zif->bondslave_info; + if (bond_mbr->bond_if) { + struct zebra_if *bond_zif = bond_mbr->bond_if->info; + + zebra_l2_bond_lacp_bypass_eval(bond_zif); + } + } + old_bond_ifindex = zif->bondslave_info.bond_ifindex; if (old_bond_ifindex == bond_ifindex) return; diff --git a/zebra/zebra_l2.h b/zebra/zebra_l2.h index 4b84eb071e..1834430287 100644 --- a/zebra/zebra_l2.h +++ b/zebra/zebra_l2.h @@ -107,7 +107,7 @@ extern void zebra_l2if_update_bridge_slave(struct interface *ifp, ns_id_t ns_id); extern void zebra_l2if_update_bond_slave(struct interface *ifp, - ifindex_t bond_ifindex); + ifindex_t bond_ifindex, bool bypass); extern void zebra_vlan_bitmap_compute(struct interface *ifp, uint32_t vid_start, uint16_t vid_end); extern void zebra_vlan_mbr_re_eval(struct interface *ifp, diff --git a/zebra/zebra_vxlan.c b/zebra/zebra_vxlan.c index a4365e551f..0be1fc2c1d 100644 --- a/zebra/zebra_vxlan.c +++ b/zebra/zebra_vxlan.c @@ -2852,11 +2852,10 @@ void zebra_vxlan_print_macs_vni(struct vty *vty, struct zebra_vrf *zvrf, vty_out(vty, "Number of MACs (local and remote) known for this VNI: %u\n", num_macs); - vty_out(vty, - "Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy\n"); - vty_out(vty, "%-17s %-6s %-5s %-30s %-5s %s\n", "MAC", - "Type", "Flags", "Intf/Remote ES/VTEP", - "VLAN", "Seq #'s"); + vty_out(vty, + "Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy\n"); + vty_out(vty, "%-17s %-6s %-5s %-30s %-5s %s\n", "MAC", "Type", + "Flags", "Intf/Remote ES/VTEP", "VLAN", "Seq #'s"); } else json_object_int_add(json, "numMacs", num_macs); @@ -4123,7 +4122,8 @@ int zebra_vxlan_dp_network_mac_del(struct interface *ifp, if (IS_ZEBRA_DEBUG_VXLAN || IS_ZEBRA_DEBUG_EVPN_MH_MAC) zlog_debug("dpDel local-nw-MAC %pEA VNI %u", macaddr, vni); - return zebra_evpn_del_local_mac(zevpn, mac); + + zebra_evpn_del_local_mac(zevpn, mac, false); } return 0; @@ -4160,7 +4160,7 @@ int zebra_vxlan_local_mac_del(struct interface *ifp, struct interface *br_if, if (!CHECK_FLAG(mac->flags, ZEBRA_MAC_LOCAL)) return 0; - return zebra_evpn_del_local_mac(zevpn, mac); + return zebra_evpn_del_local_mac(zevpn, mac, false); } /* @@ -4209,7 +4209,7 @@ int zebra_vxlan_local_mac_add_update(struct interface *ifp, return zebra_evpn_add_update_local_mac(zvrf, zevpn, ifp, macaddr, vid, sticky, local_inactive, - dp_static); + dp_static, NULL); } /* -- 2.39.5