Commit 080f85ae37 for frr

commit 080f85ae37c830c08534001c56d8f7d51e459040
Author: Maxime Gouin <maxime.gouin@6wind.com>
Date:   Fri Oct 16 10:48:36 2026 +0200

    zebra: free L2-VNI of deleted vxlan interface when EVPN is disabled

    When EVPN is disabled, zebra_vxlan_if_del_vni() returns early, so the
    L2-VNI entry (struct zebra_evpn) bound to a deleted VxLAN interface is
    not freed. Such entries can exist while EVPN is disabled: they are kept
    when bgpd disconnects (zebra_evpn_bgp_cfg_clean_up()) and they are
    created when a vrf-vni mapping is removed
    (zebra_vxlan_handle_vni_transition()).

    Since commit 988738a787 ("zebra: Don't leave stale interface pointers
    in the VNI databases"), the reference to the deleted VxLAN interface
    (zevpn->vxlan_if) is cleared, which avoids a use-after-free when EVPN
    is enabled again. However, the L2-VNI entry itself is leaked: it stays
    in the EVPN table without a VxLAN interface, and is still shown by
    "show evpn vni" once EVPN is enabled again.

    Delete the L2-VNI entry bound to the deleted VxLAN interface even if
    EVPN is disabled, and only if it is actually bound to this interface.
    Only the L3-VNI processing still requires EVPN to be enabled.

    Signed-off-by: Maxime Gouin <maxime.gouin@6wind.com>

diff --git a/zebra/zebra_vxlan_if.c b/zebra/zebra_vxlan_if.c
index 188aa0284c..8f388434ef 100644
--- a/zebra/zebra_vxlan_if.c
+++ b/zebra/zebra_vxlan_if.c
@@ -145,15 +145,12 @@ static int zebra_vxlan_if_del_vni(struct interface *ifp,
 	struct zebra_l3vni *zl3vni;
 	struct interface *br_if;

-	/* Check if EVPN is enabled. */
-	if (!is_evpn_enabled())
-		return 0;
-
 	zif = ifp->info;
 	assert(zif);
 	vni = vnip->vni;

-	zl3vni = zl3vni_lookup(vni);
+	/* L3-VNIs are only processed while EVPN is enabled. */
+	zl3vni = is_evpn_enabled() ? zl3vni_lookup(vni) : NULL;
 	if (zl3vni) {

 		if (IS_ZEBRA_DEBUG_VXLAN)
@@ -176,12 +173,17 @@ static int zebra_vxlan_if_del_vni(struct interface *ifp,
 			zlog_debug("Del L2-VNI %u intf %s(%u)", vni, ifp->name,
 				   ifp->ifindex);

-		/* Locate hash entry; it is expected to exist. */
+		/*
+		 * L2-VNI hash entries survive a BGP disconnect (see
+		 * zebra_evpn_bgp_cfg_clean_up()) and are created by vrf-vni
+		 * removal, so they must be freed here even when EVPN is
+		 * disabled, or they leak.
+		 */
 		zevpn = zebra_evpn_lookup(vni);
-		if (!zevpn) {
-			zlog_debug(
-				"Failed to locate VNI hash at del, IF %s(%u) VNI %u",
-				ifp->name, ifp->ifindex, vni);
+		if (!zevpn || zevpn->vxlan_if != ifp) {
+			if (IS_ZEBRA_DEBUG_VXLAN)
+				zlog_debug("No L2-VNI %u bound to intf %s(%u) at del", vni,
+					   ifp->name, ifp->ifindex);
 			return 0;
 		}