Commit dacf6806f5 for frr

commit dacf6806f52679152ad176ee44eebed6aba8499b
Author: Donatas Abraitis <donatas@opensourcerouting.org>
Date:   Wed Oct 28 16:40:30 2026 +0200

    bgpd: Honor PATHS-LIMIT capability's limits received via dynamic capability

    [afi][safi].received never reset when the session goes down or when the dynamic
    capability removed. An old/stale limit can be applied...

    Signed-off-by: Donatas Abraitis <donatas@opensourcerouting.org>

diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c
index 8f390e6adf..faf3578124 100644
--- a/bgpd/bgp_fsm.c
+++ b/bgpd/bgp_fsm.c
@@ -2346,6 +2346,7 @@ enum bgp_fsm_state_progress bgp_stop(struct peer_connection *connection)

 		/* peer address family capability flags*/
 		peer->af_cap[afi][safi] = 0;
+		peer->addpath_paths_limit[afi][safi].receive = 0;

 		/* peer address family status flags*/
 		/* Don't clear WAIT_EOR */
diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c
index 8053861052..f20dcf53dc 100644
--- a/bgpd/bgp_packet.c
+++ b/bgpd/bgp_packet.c
@@ -3321,12 +3321,17 @@ ignore:
 			data += CAPABILITY_CODE_PATHS_LIMIT_LEN;
 		}
 	} else {
-		FOREACH_AFI_SAFI (afi, safi)
+		FOREACH_AFI_SAFI (afi, safi) {
 			UNSET_FLAG(peer->af_cap[afi][safi],
 				   PEER_CAP_PATHS_LIMIT_AF_RCV);
+			peer->addpath_paths_limit[afi][safi].receive = 0;
+		}

 		UNSET_FLAG(peer->cap, PEER_CAP_PATHS_LIMIT_RCV);
 	}
+
+	update_group_adjust_peer_afs(peer);
+	bgp_announce_route_all(peer);
 }

 static void bgp_dynamic_capability_enhe(uint8_t *pnt, int action, struct capability_header *hdr,
diff --git a/tests/topotests/bgp_addpath_paths_limit/r2/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r2/frr.conf
index 796b4d0ba7..98be901ef4 100644
--- a/tests/topotests/bgp_addpath_paths_limit/r2/frr.conf
+++ b/tests/topotests/bgp_addpath_paths_limit/r2/frr.conf
@@ -14,6 +14,7 @@ router bgp 65002
  neighbor 192.168.1.1 remote-as external
  neighbor 192.168.7.7 remote-as external
  neighbor 192.168.7.7 timers connect 5
+ neighbor 192.168.7.7 capability dynamic
  neighbor 192.168.2.3 remote-as external
  neighbor 192.168.2.3 timers connect 5
  neighbor 192.168.2.3 weight 3
diff --git a/tests/topotests/bgp_addpath_paths_limit/r7/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r7/frr.conf
index 8c44566b2f..54f2b632ee 100644
--- a/tests/topotests/bgp_addpath_paths_limit/r7/frr.conf
+++ b/tests/topotests/bgp_addpath_paths_limit/r7/frr.conf
@@ -7,6 +7,7 @@ router bgp 65007
  no bgp ebgp-requires-policy
  neighbor 192.168.7.2 remote-as external
  neighbor 192.168.7.2 timers connect 5
+ neighbor 192.168.7.2 capability dynamic
  address-family ipv4 unicast
   neighbor 192.168.7.2 addpath-rx-paths-limit 3
  exit-address-family
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 f342971e1a..37981c1bff 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
@@ -338,6 +338,74 @@ def test_bgp_addpath_paths_limit_best_selected_filtered():
     assert result is None, "r1 did not receive the two best paths: {}".format(result)


+def test_bgp_addpath_paths_limit_dynamic_capability():
+    """
+    Paths-Limit changed via dynamic capability must be honored without reset.
+    """
+    tgen = get_topogen()
+
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    r2 = tgen.gears["r2"]
+    r7 = tgen.gears["r7"]
+
+    def _set_paths_limit(limit):
+        if limit:
+            cmd = "neighbor 192.168.7.2 addpath-rx-paths-limit {}".format(limit)
+        else:
+            cmd = "no neighbor 192.168.7.2 addpath-rx-paths-limit"
+
+        r7.vtysh_cmd(
+            """
+        configure terminal
+         router bgp 65007
+          address-family ipv4 unicast
+           {}
+        """.format(
+                cmd
+            )
+        )
+
+    def _bgp_check_paths_limit(limit, aspaths):
+        output = json.loads(r2.vtysh_cmd("show bgp neighbor 192.168.7.7 json"))
+        expected = {
+            "192.168.7.7": {
+                "bgpState": "Established",
+                "neighborCapabilities": {
+                    "dynamic": "advertisedAndReceived",
+                    "pathsLimit": {"ipv4Unicast": {"receivedPathsLimit": limit}},
+                },
+                "messageStats": {
+                    "notificationsRecv": 0,
+                    "notificationsSent": 0,
+                },
+            }
+        }
+        result = topotest.json_cmp(output, expected)
+        if result is not None:
+            return result
+
+        return _bgp_check_received_aspaths(r7, aspaths)
+
+    r2.vtysh_cmd("clear bgp 192.168.7.7 message-stats")
+
+    steps = [
+        (1, ["65002 65006"]),
+        (0, ["65002 65006", "65002 65005", "65002 65004", "65002 65003"]),
+        (3, ["65002 65006", "65002 65005", "65002 65004"]),
+    ]
+
+    for limit, aspaths in steps:
+        _set_paths_limit(limit)
+
+        test_func = functools.partial(_bgp_check_paths_limit, limit, aspaths)
+        _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+        assert result is None, "Dynamic Paths-Limit {} is not honored: {}".format(
+            limit, result
+        )
+
+
 if __name__ == "__main__":
     args = ["-s"] + sys.argv[1:]
     sys.exit(pytest.main(args))