Skip to content

bgpd: add advertisement-delay to hold route advertisements after startup - #21430

Merged
ton31337 merged 3 commits into
FRRouting:masterfrom
karthikeyav:kmuppalla/upstream-advertisement-delay
Apr 21, 2026
Merged

bgpd: add advertisement-delay to hold route advertisements after startup#21430
ton31337 merged 3 commits into
FRRouting:masterfrom
karthikeyav:kmuppalla/upstream-advertisement-delay

Conversation

@karthikeyav

Copy link
Copy Markdown

Summary

Add a new advertisement-delay CLI that holds route advertisements to peers
for a configured number of seconds after the first peer reaches Established
state. Unlike update-delay, which defers best-path and FIB programming,
advertisement-delay allows best-path selection and FIB installation to
proceed normally -- only peer advertisements are held.
Available as both a global command (bgp advertisement-delay <1-3600>) and
a per-instance command (advertisement-delay <1-3600> under router bgp).

Behavior

  • Timer starts when the first peer reaches Established (same trigger as
    update-delay).
  • During the delay: routes are received, best-path runs, FIB is programmed.
    Only advertisements to peers are held (pfxSnt remains 0).
  • When the timer expires: advertisements are released to all Established peers.
  • With both update-delay and advertisement-delay configured: advertisements
    are released at max(update-delay result, T0 + advertisement-delay).
  • Re-triggers on clear ip bgp *, following the same pattern as update-delay.
  • Not started on the GR restarter path (warm reboot) -- only update-delay/
    advertisement-delay apply to cold start and clear events.
  • show bgp summary json shows advertisementDelay, advertisementDelayInProgress,
    advertisementDelayRemainingSeconds, and advertisementDelayResumeTime.
  • show bgp router json shows bgpAdvertisementDelayTime.

@frrbot frrbot Bot added bgp documentation tests Topotests, make check, etc labels Mar 31, 2026
@enkechen-panw

Copy link
Copy Markdown
Contributor

What's the motivation for this knob? If the local FIB is already updated, delaying the route advertisement would mean stales routes on the receiver, right?

@greptile-apps

greptile-apps Bot commented Mar 31, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces advertisement-delay, a new BGP knob that holds route advertisements to peers for a configured number of seconds after the first peer reaches Established state. Unlike update-delay, best-path selection and FIB programming proceed normally during the hold β€” only peer advertisements are deferred. The feature is available both globally (bgp advertisement-delay) and per-BGP-instance/VRF (advertisement-delay), re-triggers on clear bgp *, and integrates with update-delay so that advertisements are released at max(update-delay, advertisement-delay).

The core state-machine logic, the dual-timer coordination in bgp_route.c, the clear bgp * re-trigger path, and the JSON/text show output are all implemented correctly. However, one P1 defect was found:

  • no advertisement-delay leaves peers permanently unable to receive advertisements β€” when the timer is running and the config is removed, event_cancel stops the timer but bgp->main_peers_update_hold is never reset to 0. Because the only two code paths that clear that flag are the timer callback (cancelled) and the update-delay EOIU path (not triggered if update-delay isn't configured), peers stop receiving route advertisements until a clear bgp * is issued. Both no_bgp_advertisement_delay and no_bgp_global_advertisement_delay are affected. The topotest doesn't catch this because test_bgp_no_advertisement_delay waits for the delay to fully complete before issuing the no command.

  • updateDelayPeerUpdateResume appears in show bgp summary json for advertisement-delay-only instances β€” bgp_start_routeadv() unconditionally stamps bgp->update_delay_peers_resume_time, so the suppression condition added in the display code is never actually effective after the first advertisement-delay cycle fires.

Confidence Score: 4/5

Not safe to merge as-is: removing advertisement-delay while the timer is active permanently blocks peer route advertisements until a hard clear

One P1 defect: the no advertisement-delay / no bgp advertisement-delay handlers cancel the timer but do not reset main_peers_update_hold, leaving peer advertisements permanently blocked. All other paths (timer expiry, clear-bgp re-trigger, dual-timer ordering) are correct. A targeted fix to the two deconfiguration handlers resolves the issue.

bgpd/bgp_vty.c β€” both no_bgp_advertisement_delay and no_bgp_global_advertisement_delay need to release main_peers_update_hold when the timer is still in-flight

Important Files Changed

Filename Overview
bgpd/bgp_fsm.c Adds advertisement-delay timer/begin/applicable/active/configured helpers; timer callback doesn't handle mid-run config removal (hold stuck P1)
bgpd/bgp_vty.c Adds global and per-instance advertisement-delay CLI; no/deconfiguration path cancels timer but omits release of main_peers_update_hold (P1 bug)
bgpd/bgp_route.c End-of-initial-update path correctly defers hold release when advertisement-delay timer is still active; both timers handled correctly
bgpd/bgpd.h Adds advertisement-delay fields to bgp_master and bgp structs; straightforward additions
tests/topotests/bgp_advertisement_delay/test_bgp_advertisement_delay.py 8 test cases covering core scenarios; test_bgp_no_advertisement_delay waits for delay to finish before removing config, missing coverage of mid-run deconfiguration

Sequence Diagram

sequenceDiagram
    participant Peer as First Peer
    participant FSM as bgp_fsm.c
    participant Timer as advertisement-delay timer
    participant Route as bgp_route.c
    participant Adv as Route Advertisements

    Note over FSM: startup / clear ip bgp *

    Peer->>FSM: reaches Established
    FSM->>FSM: bgp_advertisement_delay_begin()<br/>main_peers_update_hold = 1
    FSM->>Timer: start timer (v_advertisement_delay seconds)

    Note over Route: (update-delay also running? update-delay end fires first)
    Route->>Route: bgp_process_main_one() EOIU path<br/>advertisement-delay still active?<br/>YES β†’ keep holding

    Timer->>FSM: bgp_advertisement_delay_timer() fires
    alt update-delay still active or main_zebra_update_hold
        FSM-->>Route: set advertisement_delay_over=1, return<br/>(release deferred to EOIU path)
        Route->>Route: EOIU: advertisement_delay_active=false<br/>β†’ main_peers_update_hold=0
        Route->>Adv: bgp_start_routeadv()
    else advertisement-delay is the last hold
        FSM->>FSM: main_peers_update_hold = 0
        FSM->>Adv: bgp_start_routeadv()
    end

    Note over FSM,Adv: BUG: no advertisement-delay cancels timer<br/>but leaves main_peers_update_hold=1 β†’ Adv permanently blocked
Loading

Comments Outside Diff (2)

  1. bgpd/bgp_vty.c, line 237-252 (link)

    P1 main_peers_update_hold not released on no advertisement-delay

    When advertisement-delay is actively running (bgp_advertisement_delay_begin has already set bgp->main_peers_update_hold = 1) and the user issues no advertisement-delay, the timer is cancelled and the flag fields are cleared β€” but bgp->main_peers_update_hold is never reset to 0. Since the only code paths that clear that flag are:

    1. bgp_advertisement_delay_timer (callback – but the timer was just cancelled), and
    2. the end-of-initial-update path in bgp_process_main_one (only reachable when update-delay is also configured),

    peers will permanently stop receiving route advertisements until a clear bgp * is issued to restart the cycle. The same problem exists in no_bgp_global_advertisement_delay which iterates all BGP instances.

    The fix is to release the hold when the timer is still in-flight at deconfiguration time, provided update-delay post-processing is not also holding things:

    /* Per-instance no handler */
    bgp->v_advertisement_delay = 0;
    if (bgp->advertisement_delay_started && !bgp->advertisement_delay_over) {
        event_cancel(&bgp->t_advertisement_delay);
        bgp->advertisement_delay_started = 0;
        bgp->advertisement_delay_over = 0;
        if (!bgp_update_delay_active(bgp) && !bgp->main_zebra_update_hold) {
            bgp->main_peers_update_hold = 0;
            bgp_start_routeadv(bgp);
        }
    } else {
        event_cancel(&bgp->t_advertisement_delay);
        bgp->advertisement_delay_started = 0;
        bgp->advertisement_delay_over = 0;
    }

    The same pattern should be applied to each instance inside no_bgp_global_advertisement_delay.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: bgpd/bgp_vty.c
    Line: 237-252
    
    Comment:
    **`main_peers_update_hold` not released on `no advertisement-delay`**
    
    When advertisement-delay is actively running (`bgp_advertisement_delay_begin` has already set `bgp->main_peers_update_hold = 1`) and the user issues `no advertisement-delay`, the timer is cancelled and the flag fields are cleared β€” but `bgp->main_peers_update_hold` is never reset to 0. Since the only code paths that clear that flag are:
    
    1. `bgp_advertisement_delay_timer` (callback – but the timer was just cancelled), and
    2. the end-of-initial-update path in `bgp_process_main_one` (only reachable when `update-delay` is also configured),
    
    peers will permanently stop receiving route advertisements until a `clear bgp *` is issued to restart the cycle. The same problem exists in `no_bgp_global_advertisement_delay` which iterates all BGP instances.
    
    The fix is to release the hold when the timer is still in-flight at deconfiguration time, provided `update-delay` post-processing is not also holding things:
    
    ```c
    /* Per-instance no handler */
    bgp->v_advertisement_delay = 0;
    if (bgp->advertisement_delay_started && !bgp->advertisement_delay_over) {
        event_cancel(&bgp->t_advertisement_delay);
        bgp->advertisement_delay_started = 0;
        bgp->advertisement_delay_over = 0;
        if (!bgp_update_delay_active(bgp) && !bgp->main_zebra_update_hold) {
            bgp->main_peers_update_hold = 0;
            bgp_start_routeadv(bgp);
        }
    } else {
        event_cancel(&bgp->t_advertisement_delay);
        bgp->advertisement_delay_started = 0;
        bgp->advertisement_delay_over = 0;
    }
    ```
    
    The same pattern should be applied to each instance inside `no_bgp_global_advertisement_delay`.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. bgpd/bgp_vty.c, line 283-289 (link)

    P2 updateDelayPeerUpdateResume shown for advertisement-delay-only case

    bgp_start_routeadv() unconditionally stamps bgp->update_delay_peers_resume_time (see bgp_fsm.c). When advertisement-delay fires without any update-delay configured, bgp_start_routeadv is still called and populates that field. The guard added here:

    if (!bgp_advertisement_delay_configured(bgp) ||
        bgp->update_delay_peers_resume_time[0] != '\0')

    …evaluates to true (field is non-empty after the delay fires), so updateDelayPeerUpdateResume is emitted in show bgp summary json even when no update-delay was ever configured. This can be confusing to operators parsing the JSON output.

    Consider clearing update_delay_peers_resume_time alongside the other resume fields when only advertisement-delay is in use, or stamp a dedicated field in bgp_start_routeadv when called from the advertisement-delay path.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: bgpd/bgp_vty.c
    Line: 283-289
    
    Comment:
    **`updateDelayPeerUpdateResume` shown for advertisement-delay-only case**
    
    `bgp_start_routeadv()` unconditionally stamps `bgp->update_delay_peers_resume_time` (see `bgp_fsm.c`). When advertisement-delay fires without any `update-delay` configured, `bgp_start_routeadv` is still called and populates that field. The guard added here:
    
    ```c
    if (!bgp_advertisement_delay_configured(bgp) ||
        bgp->update_delay_peers_resume_time[0] != '\0')
    ```
    
    …evaluates to `true` (field is non-empty after the delay fires), so `updateDelayPeerUpdateResume` is emitted in `show bgp summary json` even when no `update-delay` was ever configured. This can be confusing to operators parsing the JSON output.
    
    Consider clearing `update_delay_peers_resume_time` alongside the other resume fields when only advertisement-delay is in use, or stamp a dedicated field in `bgp_start_routeadv` when called from the advertisement-delay path.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: bgpd/bgp_vty.c
Line: 237-252

Comment:
**`main_peers_update_hold` not released on `no advertisement-delay`**

When advertisement-delay is actively running (`bgp_advertisement_delay_begin` has already set `bgp->main_peers_update_hold = 1`) and the user issues `no advertisement-delay`, the timer is cancelled and the flag fields are cleared β€” but `bgp->main_peers_update_hold` is never reset to 0. Since the only code paths that clear that flag are:

1. `bgp_advertisement_delay_timer` (callback – but the timer was just cancelled), and
2. the end-of-initial-update path in `bgp_process_main_one` (only reachable when `update-delay` is also configured),

peers will permanently stop receiving route advertisements until a `clear bgp *` is issued to restart the cycle. The same problem exists in `no_bgp_global_advertisement_delay` which iterates all BGP instances.

The fix is to release the hold when the timer is still in-flight at deconfiguration time, provided `update-delay` post-processing is not also holding things:

```c
/* Per-instance no handler */
bgp->v_advertisement_delay = 0;
if (bgp->advertisement_delay_started && !bgp->advertisement_delay_over) {
    event_cancel(&bgp->t_advertisement_delay);
    bgp->advertisement_delay_started = 0;
    bgp->advertisement_delay_over = 0;
    if (!bgp_update_delay_active(bgp) && !bgp->main_zebra_update_hold) {
        bgp->main_peers_update_hold = 0;
        bgp_start_routeadv(bgp);
    }
} else {
    event_cancel(&bgp->t_advertisement_delay);
    bgp->advertisement_delay_started = 0;
    bgp->advertisement_delay_over = 0;
}
```

The same pattern should be applied to each instance inside `no_bgp_global_advertisement_delay`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: bgpd/bgp_vty.c
Line: 283-289

Comment:
**`updateDelayPeerUpdateResume` shown for advertisement-delay-only case**

`bgp_start_routeadv()` unconditionally stamps `bgp->update_delay_peers_resume_time` (see `bgp_fsm.c`). When advertisement-delay fires without any `update-delay` configured, `bgp_start_routeadv` is still called and populates that field. The guard added here:

```c
if (!bgp_advertisement_delay_configured(bgp) ||
    bgp->update_delay_peers_resume_time[0] != '\0')
```

…evaluates to `true` (field is non-empty after the delay fires), so `updateDelayPeerUpdateResume` is emitted in `show bgp summary json` even when no `update-delay` was ever configured. This can be confusing to operators parsing the JSON output.

Consider clearing `update_delay_peers_resume_time` alongside the other resume fields when only advertisement-delay is in use, or stamp a dedicated field in `bgp_start_routeadv` when called from the advertisement-delay path.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "tests: add topotest for bgp advertisemen..." | Re-trigger Greptile

@karthikeyav
karthikeyav force-pushed the kmuppalla/upstream-advertisement-delay branch 2 times, most recently from 0ce3c40 to 6906d2d Compare April 1, 2026 00:16
@karthikeyav

karthikeyav commented Apr 1, 2026

Copy link
Copy Markdown
Author

What's the motivation for this knob? If the local FIB is already updated, delaying the route advertisement would mean stales routes on the receiver, right?

With update-delay, both FIB programming and advertisements are deferred -- this means the restarting router cannot forward local traffic until the delay completes. advertisement-delay allows FIB programming to proceed normally so local forwarding works immediately, while holding only the advertisements to prevent the router from attracting remote traffic before it has a complete routing state.

Regarding stale routes: after a non-graceful restart, peers detect the session loss and withdraw routes to this router, so the receiver has no routes -- not stale ones. The advertisement-delay controls when this router re-announces itself, ensuring it only attracts traffic once it has fully converged. When graceful-restart is active, advertisement-delay is not started -- the GR restarter path handles route retention separately.

@karthikeyav
karthikeyav force-pushed the kmuppalla/upstream-advertisement-delay branch from 6906d2d to 423bcf9 Compare April 1, 2026 00:47

@ton31337 ton31337 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the documentation on what is the motivation for this new command (like you answered in the comments).

Comment thread bgpd/bgpd.h
uint8_t advertisement_delay_over;
uint8_t advertisement_delay_started;
uint16_t v_advertisement_delay;
char advertisement_delay_resume_time[64];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason is this a char?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Followed same pattern as the existing update_delay_*_time fields (bgpd.h#L673-L676) -- char[64] populated by frr_timestamp() for display in show bgp summary.

Comment thread bgpd/bgp_vty.c
Comment thread bgpd/bgp_route.c
Comment thread bgpd/bgp_fsm.c
bgp->main_peers_update_hold = 1;
event_add_timer(bm->master, bgp_advertisement_delay_timer, bgp, bgp->v_advertisement_delay,
&bgp->t_advertisement_delay);
zlog_info("Advertisement delay started - %d seconds for %s", bgp->v_advertisement_delay,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, put it under zlog_debug().

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fires once when the first peer reaches Established, same pattern as update-delay's zlog_info("Begin read-only mode ..."). Can I keep this at zlog_info? I'd like it in frr.log without requiring debugs.

Comment thread bgpd/bgpd.h Outdated
@karthikeyav
karthikeyav force-pushed the kmuppalla/upstream-advertisement-delay branch from 423bcf9 to bf336ba Compare April 1, 2026 23:20
@github-actions github-actions Bot added size/XXL and removed size/XL labels Apr 1, 2026
@karthikeyav

Copy link
Copy Markdown
Author

Please update the documentation on what is the motivation for this new command (like you answered in the comments).

Updated in the bgp.rst documentation section.

@ton31337

ton31337 commented Apr 8, 2026

Copy link
Copy Markdown
Member

In general, I like this "feature", and I agree we should rename an existing "update-delay" to something else more reflective in the future.

@github-actions

Copy link
Copy Markdown

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@riw777 riw777 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good ... this is a good feature

Add advertisement-delay CLI and logic that holds route advertisements to
peers for a configured number of seconds after the first peer reaches
Established state.

- Global: bgp advertisement-delay (1-3600)
- Per-instance: advertisement-delay (1-3600)
- With update-delay: both timers start at T0 (first peer Established).
  Advertisements released at max(time_FIB_ready, T0 + A).
- Without update-delay: at first peer Established, set hold and start
  advertisement-delay timer; best-path and FIB are programmed as routes
  arrive from peers; advertisements released when timer expires.
- Re-triggers on clear ip bgp *, following the same pattern as
  update-delay.
- show bgp summary: when update-delay is in progress, show advertisement
  delay state (e.g. remaining seconds or resumed time) when the
  advertisement-delay timer is running or has completed.

Signed-off-by: Karthikeya Venkat Muppalla <kmuppalla@nvidia.com>
Signed-off-by: Donald Sharp <sharpd@nvidia.com>
@karthikeyav
karthikeyav force-pushed the kmuppalla/upstream-advertisement-delay branch from bf336ba to 8ddd740 Compare April 20, 2026 16:32
Comment thread doc/user/bgp.rst Outdated
Karthikeya Venkat Muppalla added 2 commits April 20, 2026 12:40
Document the bgp advertisement-delay CLI (global and per-vrf).
Clarify that the delay applies to all AFI/SAFI, re-triggers on
clear ip bgp *, and is unrelated to the per-neighbor
advertisement-interval.

Signed-off-by: Karthikeya Venkat Muppalla <kmuppalla@nvidia.com>
Signed-off-by: Donald Sharp <sharpd@nvidia.com>
Add topotest exercising the advertisement-delay feature with a simple
r1--r2--r3 topology where r2 is the UUT.

Test cases:
- Initial convergence without any delay configured
- show bgp router json / show bgp summary json show configured value
- advertisement-delay in progress: peers Established, pfxSnt=0
- RIB is populated during delay (only advertisements are held)
- delay completes: pfxSnt>0, advertisementDelayResumeTime set
- r3 learns route from r1 via r2 after delay ends
- update-delay + advertisement-delay (ad > ud): ads held until ad-delay
- re-trigger on clear ip bgp *
- removal: routes advertised promptly without delay

Signed-off-by: Karthikeya Venkat Muppalla <kmuppalla@nvidia.com>
@karthikeyav
karthikeyav force-pushed the kmuppalla/upstream-advertisement-delay branch from 8ddd740 to cfb792a Compare April 20, 2026 19:41
@ton31337
ton31337 merged commit c46e6f2 into FRRouting:master Apr 21, 2026
23 checks passed
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.

4 participants