Skip to content

bgpd: don't advertise LLGR stale routes to non-LLGR peers#22297

Open
inder-nexthop wants to merge 1 commit into
FRRouting:masterfrom
inder-nexthop:bgpd-dont-advertise-llgr-stale-routes-to-non-llgr-peers
Open

bgpd: don't advertise LLGR stale routes to non-LLGR peers#22297
inder-nexthop wants to merge 1 commit into
FRRouting:masterfrom
inder-nexthop:bgpd-dont-advertise-llgr-stale-routes-to-non-llgr-peers

Conversation

@inder-nexthop

Copy link
Copy Markdown

When a BGP peer goes down, an LLGR helper keeps the routes it learned from that peer, tags them with the LLGR_STALE community, and holds them as a last resort. RFC 9494 (4.3) says a stale route like that shouldn't be advertised to any neighbor that hasn't advertised the LLGR capability to us - a speaker that doesn't implement LLGR has no notion of staleness and would treat the route as a normal, live path.

The advertise check only withheld a stale route when LLGR had been negotiated in neither direction (!RCV && !ADV), but since we advertise the LLGR capability to every peer, ADV is effectively always set and the route went out anyway. The capability that matters here is the neighbor's, so key the decision off whether they advertised it to us (RCV) - which is what the comment sitting right above that check already says we do.

That alone isn't enough: the advertise decision is made once per update-subgroup, and an LLGR peer and a non-LLGR peer with the same outbound policy land in the same subgroup, so there's no way to send the route to one but not the other. Add PEER_CAP_LLGR_RCV to PEER_UPDGRP_CAP_FLAGS so the received capability feeds both the update-group hash key and the cmp function - keying off the same axis the advertise check uses - so a non-LLGR peer gets its own update-group and is never merged back in on a hash collision.

The new bgp_llgr_no_capability topotest covers it: kill the peer that advertised the route, then confirm r2 keeps feeding the stale route to its LLGR-capable peer r3 while withdrawing it from the non-LLGR peer r4. Worth noting in the config - FRR advertises LLGR whenever it advertises graceful restart, and the default helper mode counts too, so r4 has to set "bgp graceful-restart-disable" to actually be a non-LLGR peer.

@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a long-standing RFC 9494 compliance bug where FRR's LLGR helper was advertising LLGR_STALE-tagged routes to peers that never announced the LLGR capability — breaking the RFC requirement that such routes SHOULD NOT be forwarded to non-LLGR neighbors.

  • bgp_route.c: The advertise guard drops the !PEER_CAP_LLGR_ADV half of the && condition, fixing the bug where FRR's own capability advertisement (always set) effectively disabled the suppression.
  • bgp_updgrp.h: PEER_CAP_LLGR_RCV is added to PEER_UPDGRP_CAP_FLAGS so peers that differ in LLGR capability land in separate update groups, ensuring the per-subgroup advertise decision is never applied across an LLGR/non-LLGR boundary.
  • test_bgp_llgr_no_capability.py: A new topotest exercises exactly the fixed scenario: after killing r1, r2 must keep advertising the stale route to r3 (LLGR-capable) while withdrawing it from r4 (non-LLGR via bgp graceful-restart-disable).

Confidence Score: 5/5

The change is safe to merge: both hunks are surgical, well-motivated by RFC 9494, and covered by a new topotest that exercises the exact before/after behavior.

The bgp_route.c fix is a single-clause removal with the correct logic clearly documented in the comment directly above it. The bgp_updgrp.h addition ensures update-group separation on the same axis the advertise check uses, closing the subgroup-merging escape hatch. The topotest covers both the positive case (r3 still receives the stale route) and the negative case (r4 gets a withdrawal), and the r4 config correctly disables GR to guarantee LLGR is not negotiated. No correctness concerns were found.

No files require special attention.

Important Files Changed

Filename Overview
bgpd/bgp_route.c One-line fix: removes the !PEER_CAP_LLGR_ADV clause so the LLGR_STALE suppression is keyed solely on whether the neighbor sent us the capability, matching the RFC comment directly above the check.
bgpd/bgp_updgrp.h Adds PEER_CAP_LLGR_RCV to PEER_UPDGRP_CAP_FLAGS, feeding the received-capability bit into both the update-group hash key and the cmp function so LLGR-capable and non-LLGR peers are never merged into the same subgroup.
tests/topotests/bgp_llgr_no_capability/test_bgp_llgr_no_capability.py New topotest covering the fixed behavior end-to-end: initial convergence, daemon kill, stale-route marking on r2, advertisement to LLGR-capable r3, and withdrawal from non-LLGR r4.
tests/topotests/bgp_llgr_no_capability/r1/bgpd.conf r1 config: originates 172.16.1.1/32 via connected redistribution, sets restart-time 0 so GR stale timer expires immediately when bgpd is killed, letting r2 enter LLGR mode quickly.
tests/topotests/bgp_llgr_no_capability/r4/bgpd.conf r4 config: explicitly disables graceful restart so FRR neither sends GR nor LLGR capabilities, making r4 a genuine non-LLGR peer for the test.

Sequence Diagram

sequenceDiagram
    participant r1 as r1 (route originator)
    participant r2 as r2 (LLGR helper)
    participant r3 as r3 (LLGR-capable peer)
    participant r4 as r4 (non-LLGR peer)

    Note over r1,r4: Initial convergence
    r1->>r2: BGP OPEN (GR+LLGR capability)
    r2->>r3: BGP OPEN (GR+LLGR capability)
    r2->>r4: BGP OPEN (no GR/LLGR from r4)
    r1->>r2: UPDATE 172.16.1.1/32
    r2->>r3: UPDATE 172.16.1.1/32
    r2->>r4: UPDATE 172.16.1.1/32

    Note over r1,r4: r1 bgpd killed - r2 enters LLGR helper mode
    r1--xr2: session down
    Note over r2: GR restart-time=0 expires immediately
    Note over r2: Tags 172.16.1.1/32 with LLGR_STALE community
    Note over r2: PEER_CAP_LLGR_RCV set for r3, unset for r4
    Note over r2: r3 gets own update-group (LLGR_RCV=1)
    Note over r2: r4 gets own update-group (LLGR_RCV=0)
    r2->>r3: UPDATE 172.16.1.1/32 [LLGR_STALE]
    r2->>r4: WITHDRAW 172.16.1.1/32
Loading

Reviews (1): Last reviewed commit: "bgpd: don't advertise LLGR stale routes ..." | Re-trigger Greptile

Comment thread tests/topotests/bgp_llgr_no_capability/test_bgp_llgr_no_capability.py Outdated
When a BGP peer goes down, an LLGR helper keeps the routes it learned from
that peer, tags them with the LLGR_STALE community, and holds them as a
last resort. RFC 9494 (4.3) says a stale route like that shouldn't be
advertised to any neighbor that hasn't advertised the LLGR capability to
us - a speaker that doesn't implement LLGR has no notion of staleness and
would treat the route as a normal, live path.

The advertise check only withheld a stale route when LLGR had been
negotiated in neither direction (!RCV && !ADV), but since we advertise the
LLGR capability to every peer, ADV is effectively always set and the route
went out anyway. The capability that matters here is the neighbor's, so key
the decision off whether they advertised it to us (RCV) - which is what the
comment sitting right above that check already says we do.

That alone isn't enough: the advertise decision is made once per
update-subgroup, and an LLGR peer and a non-LLGR peer with the same outbound
policy land in the same subgroup, so there's no way to send the route to one
but not the other. Add PEER_CAP_LLGR_RCV to PEER_UPDGRP_CAP_FLAGS so the
received capability feeds both the update-group hash key and the cmp
function - keying off the same axis the advertise check uses - so a non-LLGR
peer gets its own update-group and is never merged back in on a hash
collision.

The new bgp_llgr_no_capability topotest covers it: kill the peer that
advertised the route, then confirm r2 keeps feeding the stale route to its
LLGR-capable peer r3 while withdrawing it from the non-LLGR peer r4. Worth
noting in the config - FRR advertises LLGR whenever it advertises graceful
restart, and the default helper mode counts too, so r4 has to set
"bgp graceful-restart-disable" to actually be a non-LLGR peer.

Signed-off-by: Inder Pooni <inder@nexthop.ai>
@inder-nexthop inder-nexthop force-pushed the bgpd-dont-advertise-llgr-stale-routes-to-non-llgr-peers branch from 9cd7d59 to 804e57f Compare June 11, 2026 19:54
@github-actions github-actions Bot added the rebase PR needs rebase label Jun 11, 2026
@inder-nexthop inder-nexthop requested a review from ton31337 June 11, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants