summaryrefslogtreecommitdiffstats
path: root/zebra/zebra_l2.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
commite2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch)
treef0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /zebra/zebra_l2.c
parentInitial commit. (diff)
downloadfrr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz
frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'zebra/zebra_l2.c')
-rw-r--r--zebra/zebra_l2.c567
1 files changed, 567 insertions, 0 deletions
diff --git a/zebra/zebra_l2.c b/zebra/zebra_l2.c
new file mode 100644
index 0000000..4f7a1cd
--- /dev/null
+++ b/zebra/zebra_l2.c
@@ -0,0 +1,567 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Zebra Layer-2 interface handling code
+ * Copyright (C) 2016, 2017 Cumulus Networks, Inc.
+ */
+
+#include <zebra.h>
+
+#include "if.h"
+#include "prefix.h"
+#include "table.h"
+#include "memory.h"
+#include "log.h"
+#include "linklist.h"
+#include "stream.h"
+#include "hash.h"
+#include "jhash.h"
+
+#include "zebra/rib.h"
+#include "zebra/rt.h"
+#include "zebra/zebra_ns.h"
+#include "zebra/zserv.h"
+#include "zebra/debug.h"
+#include "zebra/interface.h"
+#include "zebra/zebra_vrf.h"
+#include "zebra/rt_netlink.h"
+#include "zebra/interface.h"
+#include "zebra/zebra_l2.h"
+#include "zebra/zebra_l2_bridge_if.h"
+#include "zebra/zebra_vxlan.h"
+#include "zebra/zebra_vxlan_if.h"
+#include "zebra/zebra_evpn_mh.h"
+
+/* definitions */
+
+/* static function declarations */
+
+/* Private functions */
+static void map_slaves_to_bridge(struct interface *br_if, int link,
+ bool update_slave, uint8_t chgflags)
+{
+ struct vrf *vrf;
+ struct interface *ifp;
+ struct zebra_vrf *zvrf;
+ struct zebra_ns *zns;
+
+ zvrf = br_if->vrf->info;
+ assert(zvrf);
+ zns = zvrf->zns;
+ assert(zns);
+ RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ struct zebra_if *zif;
+ struct zebra_l2info_brslave *br_slave;
+
+ if (ifp->ifindex == IFINDEX_INTERNAL || !ifp->info)
+ continue;
+ if (!IS_ZEBRA_IF_BRIDGE_SLAVE(ifp))
+ continue;
+
+ /* NOTE: This assumes 'zebra_l2info_brslave' is the
+ * first field
+ * for any L2 interface.
+ */
+ zif = (struct zebra_if *)ifp->info;
+ br_slave = &zif->brslave_info;
+
+ if (link) {
+ if (br_slave->bridge_ifindex == br_if->ifindex
+ && br_slave->ns_id == zns->ns_id) {
+ br_slave->br_if = br_if;
+ if (update_slave) {
+ zebra_l2if_update_bridge_slave(
+ ifp,
+ br_slave->bridge_ifindex,
+ br_slave->ns_id,
+ chgflags);
+ }
+ }
+ } else {
+ if (br_slave->br_if == br_if)
+ br_slave->br_if = NULL;
+ }
+ }
+ }
+}
+
+/* Public functions */
+void zebra_l2_map_slave_to_bridge(struct zebra_l2info_brslave *br_slave,
+ struct zebra_ns *zns)
+{
+ struct interface *br_if;
+
+ /* TODO: Handle change of master */
+ assert(zns);
+ br_if = if_lookup_by_index_per_ns(zebra_ns_lookup(zns->ns_id),
+ br_slave->bridge_ifindex);
+ if (br_if)
+ br_slave->br_if = br_if;
+}
+
+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;
+ struct zebra_if *bond_zif;
+ struct zebra_l2info_bondslave *bond_slave = &zif->bondslave_info;
+
+ bond_if = if_lookup_by_index(bond_slave->bond_ifindex, vrf_id);
+ if (bond_if == bond_slave->bond_if)
+ return;
+
+ /* unlink the slave from the old master */
+ zebra_l2_unmap_slave_from_bond(zif);
+
+ /* If the bond is present and ready link the bond-member
+ * to it
+ */
+ if (bond_if && (bond_zif = bond_if->info)) {
+ if (bond_zif->bond_info.mbr_zifs) {
+ if (IS_ZEBRA_DEBUG_EVPN_MH_ES || IS_ZEBRA_DEBUG_EVENT)
+ zlog_debug("bond mbr %s linked to %s",
+ zif->ifp->name, bond_if->name);
+ bond_slave->bond_if = bond_if;
+ /* link the slave to the new bond master */
+ listnode_add(bond_zif->bond_info.mbr_zifs, zif);
+ /* inherit protodown flags from the es-bond */
+ 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)
+ zlog_debug("bond mbr %s link to bond skipped",
+ zif->ifp->name);
+ }
+}
+
+void zebra_l2_unmap_slave_from_bond(struct zebra_if *zif)
+{
+ struct zebra_l2info_bondslave *bond_slave = &zif->bondslave_info;
+ struct zebra_if *bond_zif;
+
+ if (!bond_slave->bond_if) {
+ if (IS_ZEBRA_DEBUG_EVPN_MH_ES || IS_ZEBRA_DEBUG_EVENT)
+ zlog_debug("bond mbr %s unlink from bond skipped",
+ zif->ifp->name);
+ return;
+ }
+
+ if (IS_ZEBRA_DEBUG_EVPN_MH_ES || IS_ZEBRA_DEBUG_EVENT)
+ zlog_debug("bond mbr %s un-linked from %s", zif->ifp->name,
+ bond_slave->bond_if->name);
+
+ /* unlink the slave from the bond master */
+ bond_zif = bond_slave->bond_if->info;
+ /* clear protodown flags */
+ if (zebra_evpn_is_es_bond(bond_zif->ifp))
+ zebra_evpn_mh_update_protodown_bond_mbr(zif, true /*clear*/,
+ __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)
+{
+ struct zebra_if *zif;
+ struct zebra_l2info_bond *bond;
+
+ zif = ifp->info;
+ assert(zif);
+ bond = &zif->bond_info;
+
+ if (add) {
+ if (!bond->mbr_zifs) {
+ if (IS_ZEBRA_DEBUG_EVPN_MH_ES || IS_ZEBRA_DEBUG_EVENT)
+ zlog_debug("bond %s mbr list create",
+ ifp->name);
+ bond->mbr_zifs = list_new();
+ }
+ } else {
+ struct listnode *node;
+ struct listnode *nnode;
+ struct zebra_if *bond_mbr;
+
+ if (!bond->mbr_zifs)
+ return;
+
+ if (IS_ZEBRA_DEBUG_EVPN_MH_ES || IS_ZEBRA_DEBUG_EVENT)
+ zlog_debug("bond %s mbr list delete", ifp->name);
+ for (ALL_LIST_ELEMENTS(bond->mbr_zifs, node, nnode, bond_mbr))
+ zebra_l2_unmap_slave_from_bond(bond_mbr);
+
+ list_delete(&bond->mbr_zifs);
+ }
+}
+
+/*
+ * Handle Bridge interface add or update. Update relevant info,
+ * map slaves (if any) to the bridge.
+ */
+void zebra_l2_bridge_add_update(struct interface *ifp,
+ const struct zebra_l2info_bridge *bridge_info)
+{
+ struct zebra_if *zif;
+ struct zebra_l2_bridge_if *br;
+
+ zif = ifp->info;
+ assert(zif);
+
+ br = BRIDGE_FROM_ZEBRA_IF(zif);
+ br->vlan_aware = bridge_info->bridge.vlan_aware;
+ zebra_l2_bridge_if_add(ifp);
+
+ /* Link all slaves to this bridge */
+ map_slaves_to_bridge(ifp, 1, false, ZEBRA_BRIDGE_NO_ACTION);
+}
+
+/*
+ * Handle Bridge interface delete.
+ */
+void zebra_l2_bridge_del(struct interface *ifp)
+{
+ zebra_l2_bridge_if_del(ifp);
+
+ /* Unlink all slaves to this bridge */
+ map_slaves_to_bridge(ifp, 0, false, ZEBRA_BRIDGE_NO_ACTION);
+}
+
+void zebra_l2if_update_bridge(struct interface *ifp, uint8_t chgflags)
+{
+ if (!chgflags)
+ return;
+ map_slaves_to_bridge(ifp, 1, true, chgflags);
+}
+
+/*
+ * Update L2 info for a VLAN interface. Only relevant parameter is the
+ * VLAN Id and this cannot change.
+ */
+void zebra_l2_vlanif_update(struct interface *ifp,
+ const struct zebra_l2info_vlan *vlan_info)
+{
+ struct zebra_if *zif;
+
+ zif = ifp->info;
+ assert(zif);
+
+ /* Copy over the L2 information. */
+ memcpy(&zif->l2info.vl, vlan_info, sizeof(*vlan_info));
+}
+
+/*
+ * Update L2 info for a GRE interface. This is called upon interface
+ * addition as well as update. Upon add/update, need to inform
+ * clients about GRE information.
+ */
+void zebra_l2_greif_add_update(struct interface *ifp,
+ const struct zebra_l2info_gre *gre_info, int add)
+{
+ struct zebra_if *zif;
+ struct in_addr old_vtep_ip;
+
+ zif = ifp->info;
+ assert(zif);
+
+ if (add) {
+ memcpy(&zif->l2info.gre, gre_info, sizeof(*gre_info));
+ return;
+ }
+
+ old_vtep_ip = zif->l2info.gre.vtep_ip;
+ if (IPV4_ADDR_SAME(&old_vtep_ip, &gre_info->vtep_ip))
+ return;
+
+ zif->l2info.gre.vtep_ip = gre_info->vtep_ip;
+}
+
+/*
+ * Update L2 info for a VxLAN interface. This is called upon interface
+ * addition as well as update. Upon add, need to invoke the VNI create
+ * function. Upon update, the params of interest are the local tunnel
+ * IP and VLAN mapping, but the latter is handled separately.
+ */
+void zebra_l2_vxlanif_add_update(struct interface *ifp,
+ const struct zebra_l2info_vxlan *vxlan_info,
+ int add)
+{
+ struct zebra_if *zif;
+ uint16_t chgflags = 0;
+ struct zebra_vxlan_if_update_ctx ctx;
+
+ zif = ifp->info;
+ assert(zif);
+
+ if (add) {
+ memcpy(&zif->l2info.vxl, vxlan_info, sizeof(*vxlan_info));
+ zebra_vxlan_if_add(ifp);
+ return;
+ }
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.old_vtep_ip = zif->l2info.vxl.vtep_ip;
+
+ if (!IPV4_ADDR_SAME(&ctx.old_vtep_ip, &vxlan_info->vtep_ip)) {
+ chgflags |= ZEBRA_VXLIF_LOCAL_IP_CHANGE;
+ zif->l2info.vxl.vtep_ip = vxlan_info->vtep_ip;
+ }
+
+ if (IS_ZEBRA_VXLAN_IF_VNI(zif)) {
+ ctx.old_vni = vxlan_info->vni_info.vni;
+ if (!IPV4_ADDR_SAME(&zif->l2info.vxl.vni_info.vni.mcast_grp,
+ &vxlan_info->vni_info.vni.mcast_grp)) {
+ chgflags |= ZEBRA_VXLIF_MCAST_GRP_CHANGE;
+ zif->l2info.vxl.vni_info.vni.mcast_grp =
+ vxlan_info->vni_info.vni.mcast_grp;
+ }
+ }
+
+ if (chgflags) {
+ ctx.chgflags = chgflags;
+ zebra_vxlan_if_update(ifp, &ctx);
+ }
+}
+
+/*
+ * Handle change to VLAN to VNI mapping.
+ */
+void zebra_l2_vxlanif_update_access_vlan(struct interface *ifp,
+ vlanid_t access_vlan)
+{
+ struct zebra_if *zif;
+ vlanid_t old_access_vlan;
+ struct zebra_vxlan_vni *vni;
+ struct zebra_vxlan_if_update_ctx ctx;
+
+
+ zif = ifp->info;
+ assert(zif);
+
+ /* This would be called only in non svd case */
+ if (!IS_ZEBRA_VXLAN_IF_VNI(zif))
+ return;
+
+ old_access_vlan = zif->l2info.vxl.vni_info.vni.access_vlan;
+ ;
+ if (old_access_vlan == access_vlan)
+ return;
+
+ memset(&ctx, 0, sizeof(ctx));
+ vni = zebra_vxlan_if_vni_find(zif, 0);
+ ctx.old_vni = *vni;
+ ctx.chgflags = ZEBRA_VXLIF_VLAN_CHANGE;
+ vni->access_vlan = access_vlan;
+
+ zebra_evpn_vl_vxl_deref(old_access_vlan, vni->vni, zif);
+ zebra_evpn_vl_vxl_ref(access_vlan, vni->vni, zif);
+ zebra_vxlan_if_update(ifp, &ctx);
+}
+
+/*
+ * Handle VxLAN interface delete.
+ */
+void zebra_l2_vxlanif_del(struct interface *ifp)
+{
+ struct zebra_if *zif;
+
+ zif = ifp->info;
+ assert(zif);
+
+ zebra_vxlan_if_del(ifp);
+}
+
+/*
+ * Map or unmap interface from bridge.
+ * NOTE: It is currently assumped that an interface has to be unmapped
+ * from a bridge before it can be mapped to another bridge.
+ */
+void zebra_l2if_update_bridge_slave(struct interface *ifp,
+ ifindex_t bridge_ifindex, ns_id_t ns_id,
+ uint8_t chgflags)
+{
+ struct zebra_if *zif;
+ ifindex_t old_bridge_ifindex;
+ ns_id_t old_ns_id;
+ struct zebra_vrf *zvrf;
+ struct zebra_vxlan_if_update_ctx ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+
+ zif = ifp->info;
+ assert(zif);
+
+ zvrf = ifp->vrf->info;
+ if (!zvrf)
+ return;
+
+ if (zif->zif_type == ZEBRA_IF_VXLAN
+ && chgflags != ZEBRA_BRIDGE_NO_ACTION) {
+ if (chgflags & ZEBRA_BRIDGE_MASTER_MAC_CHANGE) {
+ ctx.chgflags = ZEBRA_VXLIF_MASTER_MAC_CHANGE;
+ zebra_vxlan_if_update(ifp, &ctx);
+ }
+ if (chgflags & ZEBRA_BRIDGE_MASTER_UP) {
+ ctx.chgflags = ZEBRA_VXLIF_MASTER_CHANGE;
+ zebra_vxlan_if_update(ifp, &ctx);
+ }
+ }
+ old_bridge_ifindex = zif->brslave_info.bridge_ifindex;
+ old_ns_id = zif->brslave_info.ns_id;
+ if (old_bridge_ifindex == bridge_ifindex &&
+ old_ns_id == zif->brslave_info.ns_id)
+ return;
+
+ ctx.chgflags = ZEBRA_VXLIF_MASTER_CHANGE;
+
+
+ zif->brslave_info.ns_id = ns_id;
+ zif->brslave_info.bridge_ifindex = bridge_ifindex;
+ /* Set up or remove link with master */
+ if (bridge_ifindex != IFINDEX_INTERNAL) {
+ zebra_l2_map_slave_to_bridge(&zif->brslave_info, zvrf->zns);
+ /* In the case of VxLAN, invoke the handler for EVPN. */
+ if (zif->zif_type == ZEBRA_IF_VXLAN)
+ zebra_vxlan_if_update(ifp, &ctx);
+ if (zif->es_info.es)
+ zebra_evpn_es_local_br_port_update(zif);
+ } else if (old_bridge_ifindex != IFINDEX_INTERNAL) {
+ /*
+ * In the case of VxLAN, invoke the handler for EVPN.
+ * Note that this should be done *prior*
+ * to unmapping the interface from the bridge.
+ */
+ if (zif->zif_type == ZEBRA_IF_VXLAN)
+ zebra_vxlan_if_update(ifp, &ctx);
+ if (zif->es_info.es)
+ zebra_evpn_es_local_br_port_update(zif);
+ zebra_l2_unmap_slave_from_bridge(&zif->brslave_info);
+ }
+}
+
+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;
+
+ zif->bondslave_info.bond_ifindex = bond_ifindex;
+
+ /* Set up or remove link with master */
+ if (bond_ifindex != IFINDEX_INTERNAL)
+ zebra_l2_map_slave_to_bond(zif, ifp->vrf->vrf_id);
+ else if (old_bond_ifindex != IFINDEX_INTERNAL)
+ zebra_l2_unmap_slave_from_bond(zif);
+}
+
+void zebra_vlan_bitmap_compute(struct interface *ifp,
+ uint32_t vid_start, uint16_t vid_end)
+{
+ uint32_t vid;
+ struct zebra_if *zif;
+
+ zif = (struct zebra_if *)ifp->info;
+ assert(zif);
+
+ for (vid = vid_start; vid <= vid_end; ++vid)
+ bf_set_bit(zif->vlan_bitmap, vid);
+}
+
+void zebra_vlan_mbr_re_eval(struct interface *ifp, bitfield_t old_vlan_bitmap)
+{
+ uint32_t vid;
+ struct zebra_if *zif;
+
+ zif = (struct zebra_if *)ifp->info;
+ assert(zif);
+
+ if (!bf_cmp(zif->vlan_bitmap, old_vlan_bitmap))
+ /* no change */
+ return;
+
+ bf_for_each_set_bit(zif->vlan_bitmap, vid, IF_VLAN_BITMAP_MAX) {
+ /* if not already set create new reference */
+ if (!bf_test_index(old_vlan_bitmap, vid))
+ zebra_evpn_vl_mbr_ref(vid, zif);
+
+ /* also clear from the old vlan bitmap */
+ bf_release_index(old_vlan_bitmap, vid);
+ }
+
+ /* any bits remaining in the old vlan bitmap are stale references */
+ bf_for_each_set_bit(old_vlan_bitmap, vid, IF_VLAN_BITMAP_MAX) {
+ zebra_evpn_vl_mbr_deref(vid, zif);
+ }
+}