Skip to content

Commit bbf019e

Browse files
authored
Merge pull request #22095 from donaldsharp/revert_flagging
Revert "bgpd: do not flag old best as multipath when it is also the n…
2 parents b9ae528 + ff0508c commit bbf019e

2 files changed

Lines changed: 92 additions & 9 deletions

File tree

bgpd/bgp_mpath.c

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -459,16 +459,12 @@ void bgp_path_info_mpath_update(struct bgp *bgp, struct bgp_dest *dest,
459459

460460
if (old_best) {
461461
old_mpath_count = bgp_path_info_mpath_count(dest);
462-
/* Only mark old best as multipath when there is a new best path
463-
* AND it is a different path. When new_best is NULL (e.g. only
464-
* path became invalid/holddown) the old path is not "another
465-
* ECMP path" and should not show as multipath. When new_best
466-
* equals old_best the path is still the bestpath and must not
467-
* carry BGP_PATH_MULTIPATH; the walk-loop below only sets the
468-
* flag on non-best siblings and never clears it on new_best,
469-
* so once set on the (still) best path it would persist.
462+
/* Only mark old best as multipath when we have a new best path.
463+
* When new_best is NULL (e.g. only path became invalid/holddown),
464+
* the old path is not "another ECMP path" and should not show
465+
* as multipath.
470466
*/
471-
if (new_best && new_best != old_best && old_mpath_count == 1)
467+
if (new_best && old_mpath_count == 1)
472468
SET_FLAG(old_best->flags, BGP_PATH_MULTIPATH);
473469
old_cum_bw = bgp_path_info_mpath_cumbw(dest);
474470
bgp_path_info_mpath_count_set(dest, 0);

tests/topotests/bgp_conditional_advertisement_track_peer/test_bgp_conditional_advertisement_track_peer.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import os
1717
import sys
1818
import json
19+
import time
1920
import pytest
2021
import functools
2122

@@ -134,6 +135,92 @@ def _bgp_check_conditional_static_routes_from_r2():
134135
_, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
135136
assert result is None, "R1 SHOULD receive 172.16.255.2/32 from R2"
136137

138+
# Once the conditional advertisement has been observed on R1, confirm the
139+
# route remains stable across at least one conditional-advertisement scanner
140+
# cycle on R2 (configured at 5s in r2/bgpd.conf). A regression on R2 that
141+
# makes the scanner advertise the prefix and then a downstream code path
142+
# withdraw it again every cycle (for example, mpath bookkeeping incorrectly
143+
# flagging BGP_PATH_MULTIPATH_CHG on the still-best path, triggering
144+
# group_announce_route() through deny-all route-map out) shows up here as
145+
# either:
146+
# (a) R1 losing 172.16.255.2/32 during the wait, or
147+
# (b) R1's per-prefix BGP dest version moving forward, because the route
148+
# was removed and re-added in the RIB at least once.
149+
# Either condition causes the assertion below to fail deterministically,
150+
# instead of leaving this test as a probabilistic poller that catches the
151+
# flap only when its `wait=1` poll lands in the ~100ms window where the
152+
# route happens to be present.
153+
def _r1_prefix_version():
154+
output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast json"))
155+
paths = (output.get("routes") or {}).get("172.16.255.2/32")
156+
return paths[0].get("version") if paths else None
157+
158+
def _r2_cond_adv_timer_remain():
159+
# `bgpTimerUntilConditionalAdvertisementsSec` is sourced from
160+
# `event_timer_remain_second(bgp->t_condition_check)` in bgpd; it
161+
# counts down each second toward 0 and then jumps back up to
162+
# `bgp_conditional-advertisement timer` (5s in r2/bgpd.conf) when the
163+
# scanner re-arms its own timer at the top of
164+
# `bgp_conditional_adv_timer()`.
165+
output = json.loads(r2.vtysh_cmd("show bgp neighbors 192.168.1.1 json"))
166+
return (
167+
output.get("192.168.1.1", {})
168+
.get("bgpTimerUntilConditionalAdvertisementsSec")
169+
)
170+
171+
initial_version = _r1_prefix_version()
172+
assert initial_version is not None, (
173+
"R1 lost 172.16.255.2/32 immediately after observing it; "
174+
"R2 is flapping the conditional advertisement"
175+
)
176+
177+
initial_timer = _r2_cond_adv_timer_remain()
178+
assert initial_timer is not None, (
179+
"R2 is not reporting bgpTimerUntilConditionalAdvertisementsSec toward "
180+
"192.168.1.1; the conditional-advertisement scanner does not appear "
181+
"to be scheduled"
182+
)
183+
184+
# Wait for one full scanner cycle to complete on R2 by watching the
185+
# remaining-time value tick down and then jump back up. Using R2's own
186+
# running state is more reliable than a fixed sleep: the test waits exactly
187+
# as long as R2's scanner says it needs, and no longer.
188+
cycle_deadline = time.time() + 25 # >= 4 cycles, well above any single 5s window
189+
previous_timer = initial_timer
190+
cycle_fired = False
191+
while time.time() < cycle_deadline:
192+
time.sleep(0.5)
193+
current_timer = _r2_cond_adv_timer_remain()
194+
if current_timer is None:
195+
# The scanner field disappeared mid-test; treat as failure below.
196+
break
197+
if current_timer > previous_timer:
198+
# Timer wrapped back up -> the scanner just fired one cycle.
199+
cycle_fired = True
200+
break
201+
previous_timer = current_timer
202+
203+
assert cycle_fired, (
204+
"R2's conditional-advertisement scanner did not fire within 25s "
205+
"(initial bgpTimerUntilConditionalAdvertisementsSec={}, last={}); "
206+
"scanner appears stuck".format(initial_timer, previous_timer)
207+
)
208+
209+
final_version = _r1_prefix_version()
210+
assert final_version is not None, (
211+
"R1 lost 172.16.255.2/32 during a conditional-advertisement scanner "
212+
"cycle; R2 is flapping the conditional advertisement "
213+
"(advertise/withdraw cycle)"
214+
)
215+
assert final_version == initial_version, (
216+
"R1's 172.16.255.2/32 per-prefix BGP dest version moved from {} to {} "
217+
"across a single R2 conditional-advertisement scanner cycle; the route "
218+
"was withdrawn and re-added at least once while the exist-map condition "
219+
"was still met (conditional advertisement is flapping on R2)".format(
220+
initial_version, final_version
221+
)
222+
)
223+
137224
step("Disable session between R2 and R3 again")
138225
r3.vtysh_cmd(
139226
"""

0 commit comments

Comments
 (0)