Commit 9f5408e938 for frr
commit 9f5408e938425c2c4eac3edd7467f8da3667c478
Author: Donatas Abraitis <donatas@opensourcerouting.org>
Date: Wed Oct 28 16:15:51 2026 +0200
bgpd: Count only advertised paths against PATHS-LIMIT capability
Paths filtered by subgroup_announce_check() were included, but it might be
filtered which is then becomes "wrong count".
Let's check the status of subgroup_announce_check().
Signed-off-by: Donatas Abraitis <donatas@opensourcerouting.org>
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index db14caa812..9ac79da872 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -4050,18 +4050,18 @@ void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest,
/*
* A new route/change in bestpath of an existing route. Evaluate the path
- * for advertisement to the subgroup.
+ * for advertisement to the subgroup. Returns true if the path is advertised.
*/
-void subgroup_process_announce_selected(struct update_subgroup *subgrp,
- struct bgp_path_info *selected,
- struct bgp_dest *dest, afi_t afi,
- safi_t safi, uint32_t addpath_tx_id)
+bool subgroup_process_announce_selected(struct update_subgroup *subgrp,
+ struct bgp_path_info *selected, struct bgp_dest *dest,
+ afi_t afi, safi_t safi, uint32_t addpath_tx_id)
{
const struct prefix *p;
struct peer *onlypeer, *peer;
struct attr attr = { 0 }, *pattr = &attr;
struct bgp *bgp;
bool advertise;
+ bool announced = false;
p = bgp_dest_get_prefix(dest);
bgp = SUBGRP_INST(subgrp);
@@ -4075,7 +4075,7 @@ void subgroup_process_announce_selected(struct update_subgroup *subgrp,
/* First update is deferred until ORF or ROUTE-REFRESH is received */
if (onlypeer && CHECK_FLAG(onlypeer->af_sflags[afi][safi],
PEER_STATUS_ORF_WAIT_REFRESH))
- return;
+ return false;
memset(&attr, 0, sizeof(attr));
/* It's initialized in bgp_announce_check() */
@@ -4101,6 +4101,8 @@ void subgroup_process_announce_selected(struct update_subgroup *subgrp,
selected))
bgp_attr_flush(pattr);
+ announced = true;
+
/* Remove paths from Adj-RIB-Out if it's not a best (selected) path.
* Why should we keep Adj-RIB-Out with stale paths?
*/
@@ -4124,8 +4126,10 @@ void subgroup_process_announce_selected(struct update_subgroup *subgrp,
bgp_adj_out_unset_subgroup(dest, subgrp, addpath_tx_id);
bgp_attr_flush(pattr);
}
- } else
+ } else {
bgp_attr_flush(pattr);
+ announced = true;
+ }
} else {
bgp_adj_out_unset_subgroup(dest, subgrp, addpath_tx_id);
bgp_attr_flush(pattr);
@@ -4136,6 +4140,8 @@ void subgroup_process_announce_selected(struct update_subgroup *subgrp,
else {
bgp_adj_out_unset_subgroup(dest, subgrp, addpath_tx_id);
}
+
+ return announced;
}
/*
diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h
index adeabe36bb..e8d8e77a35 100644
--- a/bgpd/bgp_route.h
+++ b/bgpd/bgp_route.h
@@ -1046,10 +1046,9 @@ extern void route_vty_out_overlay(struct vty *vty, const struct prefix *p,
extern void bgp_notify_conditional_adv_scanner(struct update_subgroup *subgrp);
-extern void subgroup_process_announce_selected(struct update_subgroup *subgrp,
+extern bool subgroup_process_announce_selected(struct update_subgroup *subgrp,
struct bgp_path_info *selected,
- struct bgp_dest *dest, afi_t afi,
- safi_t safi,
+ struct bgp_dest *dest, afi_t afi, safi_t safi,
uint32_t addpath_tx_id);
extern bool subgroup_announce_check(struct bgp_dest *dest,
diff --git a/bgpd/bgp_updgrp_adv.c b/bgpd/bgp_updgrp_adv.c
index 91a78517b1..295eefd241 100644
--- a/bgpd/bgp_updgrp_adv.c
+++ b/bgpd/bgp_updgrp_adv.c
@@ -98,86 +98,89 @@ subgrp_announce_addpath_best_selected(struct bgp_dest *dest,
afi_t afi = SUBGRP_AFI(subgrp);
safi_t safi = SUBGRP_SAFI(subgrp);
struct peer *peer = SUBGRP_PEER(subgrp);
- enum bgp_path_selection_reason reason;
- char pfx_buf[PREFIX2STR_BUFFER] = {};
- int paths_eq = 0;
- struct list *list = list_new();
struct bgp_path_info *pi = NULL;
+ struct bgp_path_info *best = NULL;
uint16_t paths_count = 0;
uint16_t paths_limit = peer->addpath_paths_limit[afi][safi].receive;
- if (peer->addpath_type[afi][safi] == BGP_ADDPATH_BEST_SELECTED) {
- paths_limit =
- paths_limit
- ? MIN(paths_limit,
- peer->addpath_best_selected[afi][safi])
- : peer->addpath_best_selected[afi][safi];
+ for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) {
+ if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) {
+ best = pi;
+ break;
+ }
+ }
- while (paths_count++ < paths_limit) {
- struct bgp_path_info *exist = NULL;
+ if (best && subgroup_process_announce_selected(subgrp, best, dest, afi, safi,
+ bgp_addpath_id_for_peer(peer, afi, safi,
+ &best->tx_addpath)))
+ paths_count++;
- for (pi = bgp_dest_get_bgp_path_info(dest); pi;
- pi = pi->next) {
- if (listnode_lookup(list, pi))
- continue;
+ if (peer->addpath_type[afi][safi] == BGP_ADDPATH_BEST_SELECTED) {
+ enum bgp_path_selection_reason reason;
+ char pfx_buf[PREFIX2STR_BUFFER] = {};
+ int paths_eq = 0;
+ struct list *list = list_new();
+ uint16_t selected_limit = peer->addpath_best_selected[afi][safi];
+ uint16_t selected_count = 0;
+ uint16_t remaining = paths_limit ? paths_limit - paths_count : selected_limit;
+
+ /* Pick N next best paths excluding the bestpath */
+ while (listcount(list) < selected_limit && selected_count < remaining) {
+ struct bgp_path_info *exist = NULL;
- if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED))
+ for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) {
+ if (pi == best || listnode_lookup(list, pi))
continue;
- if (bgp_path_info_cmp(peer->bgp, pi, exist,
- &paths_eq, NULL, 0,
- pfx_buf, afi, safi,
- &reason))
+ if (bgp_path_info_cmp(peer->bgp, pi, exist, &paths_eq, NULL, 0,
+ pfx_buf, afi, safi, &reason))
exist = pi;
}
- if (exist)
- listnode_add(list, exist);
+ if (!exist)
+ break;
+
+ listnode_add(list, exist);
+
+ if (subgroup_process_announce_selected(
+ subgrp, exist, dest, afi, safi,
+ bgp_addpath_id_for_peer(peer, afi, safi, &exist->tx_addpath)))
+ selected_count++;
+ }
+
+ for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) {
+ if (pi == best || listnode_lookup(list, pi))
+ continue;
+
+ subgroup_process_announce_selected(subgrp, NULL, dest, afi, safi,
+ bgp_addpath_id_for_peer(peer, afi, safi,
+ &pi->tx_addpath));
}
+
+ list_delete(&list);
+ return;
}
for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) {
uint32_t id = bgp_addpath_id_for_peer(peer, afi, safi,
&pi->tx_addpath);
- if (peer->addpath_type[afi][safi] ==
- BGP_ADDPATH_BEST_SELECTED) {
- if (listnode_lookup(list, pi))
- subgroup_process_announce_selected(
- subgrp, pi, dest, afi, safi, id);
- else {
- if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED))
- continue;
+ if (pi == best)
+ continue;
- subgroup_process_announce_selected(
- subgrp, NULL, dest, afi, safi, id);
- }
- } else {
- /* No Paths-Limit involved */
- if (!paths_limit) {
- subgroup_process_announce_selected(subgrp, pi,
- dest, afi,
- safi, id);
- continue;
- }
+ /* No Paths-Limit involved */
+ if (!paths_limit) {
+ subgroup_process_announce_selected(subgrp, pi, dest, afi, safi, id);
+ continue;
+ }
- /* If we have Paths-Limit capability, we MUST
- * not send more than the number of paths expected
- * by the peer.
- */
- if (paths_count++ < paths_limit)
- subgroup_process_announce_selected(subgrp, pi,
- dest, afi,
- safi, id);
- else
- subgroup_process_announce_selected(subgrp, NULL,
- dest, afi,
- safi, id);
+ if (paths_count < paths_limit) {
+ if (subgroup_process_announce_selected(subgrp, pi, dest, afi, safi, id))
+ paths_count++;
+ } else {
+ subgroup_process_announce_selected(subgrp, NULL, dest, afi, safi, id);
}
}
-
- if (list)
- list_delete(&list);
}
static void subgrp_withdraw_stale_addpath(struct updwalk_context *ctx,
@@ -250,16 +253,6 @@ static int group_announce_route_walkcb(struct update_group *updgrp, void *arg)
subgrp_withdraw_stale_addpath(ctx, subgrp);
subgrp_announce_addpath_best_selected(ctx->dest, subgrp);
-
- /* Process the bestpath last so the
- * "show [ip] bgp neighbor x.x.x.x advertised" output shows
- * the attributes from the bestpath.
- */
- if (ctx->pi)
- subgroup_process_announce_selected(
- subgrp, ctx->pi, ctx->dest, afi, safi,
- bgp_addpath_id_for_peer(peer, afi, safi,
- &ctx->pi->tx_addpath));
} else {
/* Send withdrawals without waiting for coalesting timer
* to expire.
@@ -842,9 +835,10 @@ void subgroup_announce_table(struct update_subgroup *subgrp,
SET_FLAG(subgrp->sflags, SUBGRP_STATUS_TABLE_REPARSING);
for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) {
-
- if (addpath_capable)
+ if (addpath_capable) {
subgrp_announce_addpath_best_selected(dest, subgrp);
+ continue;
+ }
for (ri = bgp_dest_get_bgp_path_info(dest); ri; ri = ri->next) {
diff --git a/tests/topotests/bgp_addpath_paths_limit/test_bgp_addpath_paths_limit.py b/tests/topotests/bgp_addpath_paths_limit/test_bgp_addpath_paths_limit.py
index aceffc026a..f342971e1a 100644
--- a/tests/topotests/bgp_addpath_paths_limit/test_bgp_addpath_paths_limit.py
+++ b/tests/topotests/bgp_addpath_paths_limit/test_bgp_addpath_paths_limit.py
@@ -179,6 +179,165 @@ def test_bgp_pathcount_json():
), "pathCount in JSON output doesn't match expected value for r7"
+def _bgp_received_aspaths(router):
+ output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast 172.16.16.254/32 json"))
+
+ return sorted(path["aspath"]["string"] for path in output.get("paths", []))
+
+
+def _bgp_check_received_aspaths(router, expected):
+ received = _bgp_received_aspaths(router)
+ if received != sorted(expected):
+ return "Expected {}, got {}".format(sorted(expected), received)
+
+ return None
+
+
+def test_bgp_addpath_paths_limit_filtered_paths():
+ """
+ Filtered paths must not be counted against Paths-Limit.
+ """
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+
+ r2.vtysh_cmd(
+ """
+ configure terminal
+ bgp as-path access-list r1-out seq 5 deny ^6500[56]$
+ bgp as-path access-list r1-out seq 10 permit .*
+ router bgp 65002
+ address-family ipv4 unicast
+ neighbor 192.168.1.1 filter-list r1-out out
+ """
+ )
+
+ test_func = functools.partial(
+ _bgp_check_received_aspaths, r1, ["65002 65004", "65002 65003"]
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Filtered paths are counted against Paths-Limit: {}".format(
+ result
+ )
+
+ r2.vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65002
+ address-family ipv4 unicast
+ no neighbor 192.168.1.1 filter-list r1-out out
+ no bgp as-path access-list r1-out
+ """
+ )
+
+ test_func = functools.partial(
+ _bgp_check_received_aspaths, r1, ["65002 65006", "65002 65005"]
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "r1 did not receive the two best paths: {}".format(result)
+
+
+def test_bgp_addpath_paths_limit_best_selected():
+ """
+ Bestpath must be included into Paths-Limit with addpath-tx-best-selected.
+ """
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r2 = tgen.gears["r2"]
+ r7 = tgen.gears["r7"]
+
+ r2.vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65002
+ address-family ipv4 unicast
+ neighbor 192.168.7.7 addpath-tx-best-selected 3
+ """
+ )
+
+ test_func = functools.partial(
+ _bgp_check_received_aspaths,
+ r7,
+ ["65002 65006", "65002 65005", "65002 65004"],
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Bestpath is not included into Paths-Limit: {}".format(
+ result
+ )
+
+ r2.vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65002
+ address-family ipv4 unicast
+ neighbor 192.168.7.7 addpath-tx-all-paths
+ """
+ )
+
+ test_func = functools.partial(
+ _bgp_check_received_aspaths,
+ r7,
+ ["65002 65006", "65002 65005", "65002 65004"],
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "r7 did not receive the three best paths: {}".format(result)
+
+
+def test_bgp_addpath_paths_limit_best_selected_filtered():
+ """
+ Filtered candidate must not consume Paths-Limit with addpath-tx-best-selected.
+ """
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+
+ r2.vtysh_cmd(
+ """
+ configure terminal
+ bgp as-path access-list r1-out seq 5 deny ^65005$
+ bgp as-path access-list r1-out seq 10 permit .*
+ router bgp 65002
+ address-family ipv4 unicast
+ neighbor 192.168.1.1 addpath-tx-best-selected 3
+ neighbor 192.168.1.1 filter-list r1-out out
+ """
+ )
+
+ test_func = functools.partial(
+ _bgp_check_received_aspaths, r1, ["65002 65006", "65002 65004"]
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Filtered candidate consumed Paths-Limit: {}".format(result)
+
+ r2.vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65002
+ address-family ipv4 unicast
+ no neighbor 192.168.1.1 filter-list r1-out out
+ neighbor 192.168.1.1 addpath-tx-all-paths
+ no bgp as-path access-list r1-out
+ """
+ )
+
+ test_func = functools.partial(
+ _bgp_check_received_aspaths, r1, ["65002 65006", "65002 65005"]
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "r1 did not receive the two best paths: {}".format(result)
+
+
if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))