Commit 0258ee2815 for frr
commit 0258ee2815cf6d0fc9fc04bb23511d3cf06ba43b
Author: Chenlang Li <Cc1580155hsg@163.com>
Date: Fri Oct 23 22:53:15 2026 +0800
eigrpd: add topotest for distribute-list inbound filter crash
Regression test for the uint32_t overflow fixed in the preceding
commit. Two routers share a single link; r2 advertises 10.1.5.0/24
from its loopback. r1 starts with a per-interface inbound
distribute-list that denies everything on r1-eth0.
The test waits 25 seconds so the ten-second graceful-restart timer
fires at least once, then asserts:
- eigrpd on r1 has not crashed
- 10.1.5.0/24 is absent from r1's RIB (correctly filtered)
Before the fix eigrpd aborted at startup with:
eigrp_fsm.c:426: eigrp_fsm_event_nq_fcn(): assertion (successors) failed
Signed-off-by: Chenlang Li <Cc1580155hsg@163.com>
diff --git a/tests/topotests/eigrp_distribute_filter/r1/eigrpd.conf b/tests/topotests/eigrp_distribute_filter/r1/eigrpd.conf
new file mode 100644
index 0000000000..5b0c560bc2
--- /dev/null
+++ b/tests/topotests/eigrp_distribute_filter/r1/eigrpd.conf
@@ -0,0 +1,11 @@
+! SPDX-License-Identifier: ISC
+hostname r1
+!
+ip prefix-list DENYALL seq 5 deny any
+!
+router eigrp 1
+ network 10.1.1.0/24
+ distribute-list prefix DENYALL in r1-eth0
+!
+line vty
+!
diff --git a/tests/topotests/eigrp_distribute_filter/r1/zebra.conf b/tests/topotests/eigrp_distribute_filter/r1/zebra.conf
new file mode 100644
index 0000000000..e71c0d6d87
--- /dev/null
+++ b/tests/topotests/eigrp_distribute_filter/r1/zebra.conf
@@ -0,0 +1,10 @@
+! SPDX-License-Identifier: ISC
+hostname r1
+!
+interface r1-eth0
+ ip address 10.1.1.1/24
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/eigrp_distribute_filter/r2/eigrpd.conf b/tests/topotests/eigrp_distribute_filter/r2/eigrpd.conf
new file mode 100644
index 0000000000..b7833d6463
--- /dev/null
+++ b/tests/topotests/eigrp_distribute_filter/r2/eigrpd.conf
@@ -0,0 +1,9 @@
+! SPDX-License-Identifier: ISC
+hostname r2
+!
+router eigrp 1
+ network 10.1.1.0/24
+ network 10.1.5.0/24
+!
+line vty
+!
diff --git a/tests/topotests/eigrp_distribute_filter/r2/zebra.conf b/tests/topotests/eigrp_distribute_filter/r2/zebra.conf
new file mode 100644
index 0000000000..e1ef5b934b
--- /dev/null
+++ b/tests/topotests/eigrp_distribute_filter/r2/zebra.conf
@@ -0,0 +1,13 @@
+! SPDX-License-Identifier: ISC
+hostname r2
+!
+interface r2-eth0
+ ip address 10.1.1.2/24
+!
+interface r2-lo
+ ip address 10.1.5.2/24
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/eigrp_distribute_filter/test_eigrp_distribute_filter.py b/tests/topotests/eigrp_distribute_filter/test_eigrp_distribute_filter.py
new file mode 100644
index 0000000000..1bf629373a
--- /dev/null
+++ b/tests/topotests/eigrp_distribute_filter/test_eigrp_distribute_filter.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: ISC
+#
+# test_eigrp_distribute_filter.py
+#
+# Copyright (c) 2026 by Chenlang Li
+#
+"""
+Regression test for the uint32_t overflow in eigrp_calculate_total_metrics().
+
+When eigrpd starts with a per-interface inbound distribute-list that denies all
+routes, the filtering code sets the route delay to EIGRP_MAX_METRIC (0xffffffff).
+Before the fix, adding the interface delay wrapped the value in 32-bit arithmetic,
+producing a distance just below the feasibility-condition threshold. DUAL then
+dispatched EIGRP_FSM_EVENT_NQ_FCN and asserted because no successor existed.
+
+The bug triggered via the ten-second graceful-restart timer that fires after a
+distribute-list is applied. This test waits past that deadline and asserts that
+eigrpd is still running and that the denied route does not appear in the RIB.
+"""
+
+import os
+import sys
+import pytest
+
+pytestmark = [pytest.mark.eigrpd]
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+
+def build_topo(tgen):
+ """
+ r1 --[sw1]-- r2
+
+ r2 advertises 10.1.5.0/24 (loopback) into EIGRP AS 1.
+ r1 has a per-interface inbound distribute-list that denies everything on r1-eth0.
+ """
+ tgen.add_router("r1")
+ tgen.add_router("r2")
+
+ switch = tgen.add_switch("sw1")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["r2"])
+
+
+def setup_module(module):
+ tgen = Topogen(build_topo, module.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+ for rname, router in router_list.items():
+ router.load_config(
+ TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
+ )
+ router.load_config(
+ TopoRouter.RD_EIGRP, os.path.join(CWD, "{}/eigrpd.conf".format(rname))
+ )
+
+ tgen.start_router()
+
+
+def teardown_module(_mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_eigrp_distribute_filter_no_crash():
+ """
+ Wait 25 seconds past startup so the ten-second distribute-list GR timer
+ fires at least once. Assert that eigrpd on r1 is still alive and that
+ the denied prefix 10.1.5.0/24 is absent from the RIB.
+
+ Before the fix eigrpd aborted with:
+ eigrpd/eigrp_fsm.c:426: eigrp_fsm_event_nq_fcn(): assertion (successors) failed
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info(
+ "Waiting 25 s for the distribute-list GR timer to fire (fires at ~10 s)"
+ )
+ topotest.sleep(25, "Waiting for EIGRP distribute-list GR timer")
+
+ r1 = tgen.gears["r1"]
+
+ # eigrpd must still be running — any crash shows up as a router failure
+ assert not tgen.routers_have_failure(), (
+ "eigrpd crashed after distribute-list GR timer fired: {}".format(tgen.errors)
+ )
+
+ # 10.1.5.0/24 must be absent from r1's RIB; the filter denies it inbound
+ rib = r1.vtysh_cmd("show ip route json", isjson=True)
+ assert "10.1.5.0/24" not in rib, (
+ "10.1.5.0/24 should be filtered by the inbound distribute-list on r1 "
+ "but it appears in the RIB: {}".format(rib.get("10.1.5.0/24"))
+ )
+
+ logger.info(
+ "PASS: eigrpd survived the GR timer and 10.1.5.0/24 is correctly filtered"
+ )
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))