Commit 197c580ba4 for frr

commit 197c580ba4b4163a6a77e43081e269e7e688c26d
Author: Philippe Guibert <philippe.guibert@6wind.com>
Date:   Thu Oct 22 15:48:10 2026 +0200

    bgpd: fix VPN SIDs after a zebra restart

    When zebra restarts while bgpd keeps running, zebra loses the SIDs
    allocated for the VRFs ('sid vpn export' per address-family or per VRF),
    and their local SIDs. BGP keeps exporting the VPN paths with these SIDs,
    while zebra is stopped and after it is started again, although these
    SIDs are no longer allocated in zebra, and may be given to another
    client. Before zebra is stopped on r1:

    > r1# show segment-routing srv6 sid
    >  SID               Behavior    Context        Daemon/Instance    Locator    AllocationType
    >  ----------------------------  -------------  -----------------  ---------  ----------------
    >  2002:db8:1:1:1::  End.DT4     VRF 'vrf10'    bgp(0)             loc2       dynamic
    >  2002:db8:1:1:2::  End.DT6     VRF 'vrf10'    bgp(0)             loc2       dynamic
    >  2002:db8:1:1:3::  End.DT6     VRF 'vrf30'    bgp(0)             loc2       dynamic

    One minute after zebra is stopped on r1, its peer r2 still uses the SID
    2002:db8:1:1:1:: of r1 for the VPN path 192.168.1.0/24 of RD 11:10.
    After a restart of zebra:

    > r1# show segment-routing srv6 sid
    >  SID  Behavior    Context    Daemon/Instance    Locator    AllocationType

    Nothing withdraws the VPN paths when the connection to zebra is lost.
    And when the locator is received again, bgp does not request a SID, due
    to ensure_vrf_tovpn_sid_per_af() and ensure_vrf_tovpn_sid_per_vrf(): the
    last state of the SID is not reset.

    Fix this by withdrawing the VPN paths exported with a SID, and by
    forgetting the SIDs of the VRFs without releasing them, when the
    connection to zebra is lost. A VPN path without its SID is not exported
    again: vpn_leak_to_vpn_active() refuses the export until the SID is
    allocated. The SIDs are requested again when the locator is received,
    the VPN paths are exported again once the SIDs are allocated, and their
    local SIDs are installed.

    With this fix, two seconds after zebra is stopped on r1, r2 no longer
    has the VPN paths of r1 (RD 1:10 and 1:30):

    > r2# show bgp ipv6 vpn
    > [..]
    >      Network          Next Hop            Metric LocPrf Weight Path
    > Route Distinguisher: 2:10
    >      2001:2::/64      ::@6<                    0         32768 ?
    > Route Distinguisher: 2:20
    >  *>  2001:4::/64      ::@7<                    0         32768 ?
    >                       ::@7<                    0         32768 ?
    >  *>  2001:6::/64      ::@7<                    0         32768 ?
    >                       ::@7<                    0         32768 ?
    >
    > Displayed 3 routes and 5 total paths

    Once zebra is started again on r1, the SIDs are allocated again, and r2
    gets the VPN paths of r1 again:

    > r1# show segment-routing srv6 sid
    >  SID               Behavior    Context        Daemon/Instance    Locator    AllocationType
    >  ----------------------------  -------------  -----------------  ---------  ----------------
    >  2002:db8:1:1:1::  End.DT4     VRF 'vrf10'    bgp(0)             loc2       dynamic
    >  2002:db8:1:1:2::  End.DT6     VRF 'vrf10'    bgp(0)             loc2       dynamic
    >  2002:db8:1:1:3::  End.DT6     VRF 'vrf30'    bgp(0)             loc2       dynamic
    > r2# show bgp ipv6 vpn
    > [..]
    >      Network          Next Hop            Metric LocPrf Weight Path
    > Route Distinguisher: 1:10
    >  *>  2001:1::/64      2001::1                  0             0 1 ?
    >  *>  2001:3::/64      2001::1                  0             0 1 ?
    > Route Distinguisher: 1:30
    >  *>  2001:8::/64      2001::1                  0             0 1 ?
    >  *=                   2001::1                  0             0 1 ?
    > Route Distinguisher: 2:10
    >      2001:2::/64      ::@6<                    0         32768 ?
    > Route Distinguisher: 2:20
    >  *>  2001:4::/64      ::@7<                    0         32768 ?
    >                       ::@7<                    0         32768 ?
    >  *>  2001:6::/64      ::@7<                    0         32768 ?
    >                       ::@7<                    0         32768 ?
    >
    > Displayed 6 routes and 9 total paths

    Signed-off-by: Philippe Guibert <philippe.guibert@6wind.com>

diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c
index 9d2e8caa87..5c3f8eacea 100644
--- a/bgpd/bgp_mplsvpn.c
+++ b/bgpd/bgp_mplsvpn.c
@@ -1101,6 +1101,46 @@ void delete_vrf_tovpn_sid_per_vrf(struct bgp *bgp_vpn, struct bgp *bgp_vrf)
 	bgp_vrf->tovpn_sid_locator = NULL;
 }

+/*
+ * A stopped zebra has lost the SIDs of the VRF and their local SIDs: withdraw
+ * the VPN paths exported with them, and forget them without releasing them,
+ * so that they are requested again once the locator is received.
+ */
+void vpn_leak_zebra_vrf_sids_forget(struct bgp *bgp_vrf)
+{
+	struct bgp *bgp_vpn = bgp_get_default();
+	afi_t afi;
+
+	for (afi = AFI_IP; afi <= AFI_IP6; afi++)
+		if (bgp_vrf->vpn_policy[afi].tovpn_sid || bgp_vrf->tovpn_sid)
+			vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_vpn, bgp_vrf);
+
+	for (afi = AFI_IP; afi <= AFI_IP6; afi++) {
+		if (bgp_vrf->vpn_policy[afi].tovpn_sid) {
+			UNSET_FLAG(bgp_vrf->vpn_policy[afi].flags,
+				   BGP_VPN_POLICY_TOVPN_SID_FUNC_WIDE);
+			if (bgp_vpn)
+				sid_unregister(bgp_vpn, bgp_vrf->vpn_policy[afi].tovpn_sid);
+			XFREE(MTYPE_BGP_SRV6_SID, bgp_vrf->vpn_policy[afi].tovpn_sid);
+		}
+		XFREE(MTYPE_BGP_SRV6_SID, bgp_vrf->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent);
+		bgp_vrf->vpn_policy[afi].tovpn_sid_transpose_label = 0;
+		srv6_locator_free(bgp_vrf->vpn_policy[afi].tovpn_sid_locator);
+		bgp_vrf->vpn_policy[afi].tovpn_sid_locator = NULL;
+	}
+
+	if (bgp_vrf->tovpn_sid) {
+		UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_TOVPN_SID_FUNC_WIDE);
+		if (bgp_vpn)
+			sid_unregister(bgp_vpn, bgp_vrf->tovpn_sid);
+		XFREE(MTYPE_BGP_SRV6_SID, bgp_vrf->tovpn_sid);
+	}
+	XFREE(MTYPE_BGP_SRV6_SID, bgp_vrf->tovpn_zebra_vrf_sid_last_sent);
+	bgp_vrf->tovpn_sid_transpose_label = 0;
+	srv6_locator_free(bgp_vrf->tovpn_sid_locator);
+	bgp_vrf->tovpn_sid_locator = NULL;
+}
+
 void delete_vrf_tovpn_sid(struct bgp *bgp_vpn, struct bgp *bgp_vrf, afi_t afi)
 {
 	delete_vrf_tovpn_sid_per_af(bgp_vpn, bgp_vrf, afi);
diff --git a/bgpd/bgp_mplsvpn.h b/bgpd/bgp_mplsvpn.h
index 755a74b067..e08e581081 100644
--- a/bgpd/bgp_mplsvpn.h
+++ b/bgpd/bgp_mplsvpn.h
@@ -85,6 +85,7 @@ extern void delete_vrf_tovpn_sid(struct bgp *vpn, struct bgp *vrf, afi_t afi);
 extern void delete_vrf_tovpn_sid_per_af(struct bgp *vpn, struct bgp *vrf,
 					afi_t afi);
 extern void delete_vrf_tovpn_sid_per_vrf(struct bgp *vpn, struct bgp *vrf);
+extern void vpn_leak_zebra_vrf_sids_forget(struct bgp *bgp_vrf);
 extern void ensure_vrf_tovpn_sid_per_af(struct bgp *vpn, struct bgp *vrf,
 					afi_t afi);
 extern void ensure_vrf_tovpn_sid_per_vrf(struct bgp *vpn, struct bgp *vrf);
diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c
index 00c3e98cba..cc3a27c21b 100644
--- a/bgpd/bgp_zebra.c
+++ b/bgpd/bgp_zebra.c
@@ -3400,6 +3400,9 @@ static void bgp_zebra_disconnected(struct zclient *zclient)
 	struct listnode *node;

 	for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) {
+		/* zebra has lost the SIDs of BGP */
+		vpn_leak_zebra_vrf_sids_forget(bgp);
+
 		/* the locators may have changed when zebra is started again */
 		srv6_locator_free(bgp->srv6_locator);
 		bgp->srv6_locator = NULL;