Commit 57a7f3eb48 for frr
commit 57a7f3eb48837359d6e3ee5ebd4acea8f49320e5
Author: Srinivasan Koona Lokabiraman <srinivasan@nexthop.ai>
Date: Fri Oct 23 08:33:44 2026 +0000
bgpd: recover from SendHoldTimer expiry instead of wedging the peer
When the SendHoldTimer (RFC 9687) expires,
bgp_event_stop_with_notify() called bgp_stop() inline. bgp_stop()
runs event_cancel_event_ready(), which cancels the BGP_Stop event
that bgp_write_notify() had just queued to advance the FSM, and it
does not itself change state. The peer is therefore left stuck in
Established with a closed fd (fd == -1) and no ConnectRetry timer --
it never resets or reconnects.
Send the notification and let the FSM dispatch the queued BGP_Stop
(or TCP_fatal_error) so the peer tears down and reconnects like
every other stop.
Signed-off-by: Srinivasan Koona Lokabiraman <srinivasan@nexthop.ai>
diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c
index 73dc7979a2..be7bb654a8 100644
--- a/bgpd/bgp_fsm.c
+++ b/bgpd/bgp_fsm.c
@@ -3932,5 +3932,15 @@ void bgp_event_stop_with_notify(struct event *event)
{
struct peer_connection *connection = EVENT_ARG(event);
- bgp_stop_with_notify(connection, BGP_NOTIFY_SEND_HOLD_ERR, 0);
+ /*
+ * Drive the teardown through the FSM rather than stopping the peer
+ * inline. bgp_notify_send() -> bgp_write_notify() queues a BGP_Stop
+ * (or TCP_fatal_error, if the stuck send queue rejects the write); the
+ * FSM then advances the state and re-arms the ConnectRetry timer like
+ * every other stop. Calling bgp_stop() directly here (as
+ * bgp_stop_with_notify() does) runs event_cancel_event_ready(), which
+ * cancels that queued event, so the state is never advanced and the peer
+ * is left stuck in Established with fd == -1 -- a zombie session.
+ */
+ bgp_notify_send(connection, BGP_NOTIFY_SEND_HOLD_ERR, 0);
}