Commit 0a60bdc233 for frr
commit 0a60bdc2331b3fc9f892909b1a568066fa1953a8
Author: Nageswara Soma <nsoma@cisco.com>
Date: Tue Oct 6 16:23:59 2026 -0700
bgpd: clear local EVPN ES state on instance delete
Deleting the EVPN instance with "no router bgp"
withdraws the local ES routes and frees the VNIs,
but each ES stays marked local, keeps the ESR
references taken by the peers' Type-4 routes, and
its route table still points at the freed bgp
instance. A recreated instance then inherits this
stale ES state, and the leftover VTEP entries leak
once the ES is freed.
After the VNIs are released, purge each ES table
owned by the deleted instance, drop the stale ESR
VTEP references and clear the local ES state.
Re-bind the ES route table to the new instance
when the ES is made local again. Daemon shutdown
keeps using bgp_evpn_mh_finish().
Signed-off-by: Nageswara Soma <nsoma@cisco.com>
diff --git a/bgpd/bgp_evpn_mh.c b/bgpd/bgp_evpn_mh.c
index 7d6a236846..4d89a88a4e 100644
--- a/bgpd/bgp_evpn_mh.c
+++ b/bgpd/bgp_evpn_mh.c
@@ -2132,6 +2132,12 @@ static void bgp_evpn_es_local_info_set(struct bgp *bgp, struct bgp_evpn_es *es)
if (CHECK_FLAG(es->flags, BGP_EVPNES_LOCAL))
return;
+ /* The ES may have outlived the instance that owned its route table */
+ if (es->route_table->bgp != bgp) {
+ assert(!bgp_table_count(es->route_table));
+ es->route_table->bgp = bgp;
+ }
+
old_is_local = bgp_evpn_is_es_local_and_non_bypass(es);
SET_FLAG(es->flags, BGP_EVPNES_LOCAL);
@@ -5222,6 +5228,38 @@ void bgp_evpn_es_cleanup_routes(struct bgp *bgp)
bgp_evpn_es_route_del_all(bgp, es);
}
+/* Release the local ES state owned by a BGP instance that is being deleted.
+ * Must run after the instance's VNIs, and with them the local ES-EVIs,
+ * have been freed. An ES kept alive by remote references has its route
+ * table re-bound when it is made local again under a new instance.
+ */
+void bgp_evpn_es_instance_del_cleanup(struct bgp *bgp)
+{
+ struct bgp_evpn_es *es;
+ struct bgp_evpn_es *es_next;
+ struct bgp_evpn_es_vtep *es_vtep;
+ struct listnode *node, *next;
+
+ if (!bgp_mh_info)
+ return;
+
+ RB_FOREACH_SAFE (es, bgp_es_rb_head, &bgp_mh_info->es_rb_tree, es_next) {
+ if (!es->route_table || es->route_table->bgp != bgp)
+ continue;
+
+ bgp_evpn_es_route_table_purge(es);
+
+ /* The ES table was reaped without route selection, so the
+ * ESR references taken by the imported Type-4 routes remain.
+ */
+ for (ALL_LIST_ELEMENTS(es->es_vtep_list, node, next, es_vtep))
+ if (CHECK_FLAG(es_vtep->flags, BGP_EVPNES_VTEP_ESR))
+ bgp_evpn_es_vtep_do_del(bgp, es_vtep, true);
+
+ bgp_evpn_es_local_info_clear(es, false);
+ }
+}
+
void bgp_evpn_mh_finish(void)
{
struct bgp_evpn_es *es;
diff --git a/bgpd/bgp_evpn_mh.h b/bgpd/bgp_evpn_mh.h
index 5268422535..8da9d584b5 100644
--- a/bgpd/bgp_evpn_mh.h
+++ b/bgpd/bgp_evpn_mh.h
@@ -430,6 +430,7 @@ bgp_evpn_remote_es_evi_del(struct bgp *bgp, struct bgpevpn *vpn,
extern void bgp_evpn_mh_init(void);
extern void bgp_evpn_mh_finish(void);
extern void bgp_evpn_es_cleanup_routes(struct bgp *bgp);
+extern void bgp_evpn_es_instance_del_cleanup(struct bgp *bgp);
void bgp_evpn_vni_es_init(struct bgpevpn *vpn);
void bgp_evpn_vni_es_cleanup(struct bgpevpn *vpn);
void bgp_evpn_es_show_esi(struct vty *vty, esi_t *esi, bool uj);
diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
index 04e168adad..b30483ca83 100644
--- a/bgpd/bgpd.c
+++ b/bgpd/bgpd.c
@@ -4928,6 +4928,10 @@ int bgp_delete(struct bgp *bgp)
* tables are empty) and then frees the bgpevpn structs.
*/
bgp_evpn_cleanup(bgp);
+
+ /* Shutdown releases ESs from bgp_evpn_mh_finish() instead */
+ if (!bm->terminating)
+ bgp_evpn_es_instance_del_cleanup(bgp);
}
for (afi = 0; afi < AFI_MAX; ++afi) {