The following is an issue identified by AI when I asked it to do an adversarial code review of #795. This was pre-existing in the sync/pthread impl, which is why it was not addressed as part of the async swap-out.
Title: BFD: prevent stale RIB writes from superseded sessions
Problem
BFD session teardown does not wait for all RIB work associated with the
session to finish.
Session::drop() aborts the session's async tasks, but RibTask performs
RDB updates through tokio::task::spawn_blocking(). Once a blocking task
has started, aborting the async task waiting for it does not cancel the
blocking operation.
The shutdown handle returned when removing a peer waits only for the UDP
listener. It does not wait for the session's RIB task or an in-flight
blocking RDB update.
Failure scenario
- Session A begins a RIB update for peer P.
- A is removed.
- The async RIB task is aborted, but its blocking update remains live.
- Session B is created for the same peer.
- B publishes its current state to the RIB.
- A's stale update completes afterward and becomes the final RIB state.
For example, session B may be Down while a stale shutdown = false write
from A leaves routes through the peer enabled. Because B already believes
it synchronized shutdown = true, no further write is guaranteed until
another state transition occurs.
The inverse is also possible: a stale shutdown = true write can suppress
routes belonging to a new Up session.
Impact
The forwarding state can permanently disagree with the currently visible
BFD session until another BFD transition happens.
Proposed direction
Give peer removal an awaitable, session-level shutdown operation that:
- stops and awaits the driver and egress tasks;
- closes the state channel;
- allows any current blocking RIB operation to complete;
- awaits the RIB task;
- awaits the listener task.
The same peer must not be re-added until shutdown of the prior generation
has completed. Alternatively, serialize RIB updates through a
generation-aware per-peer worker that rejects writes from superseded
sessions.
Testing
Add a fake RIB sink with barriers that can:
- block an old session's write;
- remove and recreate the session;
- allow the new session's write to complete;
- release the old write;
- verify that the old generation cannot overwrite the new state.
The following is an issue identified by AI when I asked it to do an adversarial code review of #795. This was pre-existing in the sync/pthread impl, which is why it was not addressed as part of the async swap-out.
Title: BFD: prevent stale RIB writes from superseded sessions
Problem
BFD session teardown does not wait for all RIB work associated with the
session to finish.
Session::drop()aborts the session's async tasks, butRibTaskperformsRDB updates through
tokio::task::spawn_blocking(). Once a blocking taskhas started, aborting the async task waiting for it does not cancel the
blocking operation.
The shutdown handle returned when removing a peer waits only for the UDP
listener. It does not wait for the session's RIB task or an in-flight
blocking RDB update.
Failure scenario
For example, session B may be Down while a stale
shutdown = falsewritefrom A leaves routes through the peer enabled. Because B already believes
it synchronized
shutdown = true, no further write is guaranteed untilanother state transition occurs.
The inverse is also possible: a stale
shutdown = truewrite can suppressroutes belonging to a new Up session.
Impact
The forwarding state can permanently disagree with the currently visible
BFD session until another BFD transition happens.
Proposed direction
Give peer removal an awaitable, session-level shutdown operation that:
The same peer must not be re-added until shutdown of the prior generation
has completed. Alternatively, serialize RIB updates through a
generation-aware per-peer worker that rejects writes from superseded
sessions.
Testing
Add a fake RIB sink with barriers that can: