Commit cea6abfd7c for frr

commit cea6abfd7ccce1128712337318c3b3d288b8c1eb
Author: Abdul Wasey <w453y.me@gmail.com>
Date:   Fri Nov 6 00:56:35 2026 +0000

    zebra: delete the old nexthops on BSD after an interface goes down

    When an interface goes down, zebra_nhg_check_valid() and
    zebra_nhg_set_valid() clear NEXTHOP_FLAG_ACTIVE on the nexthops of
    the routes using it, while those routes are still in the kernel. The
    routing socket dataplane then removes the old route with one
    RTM_DELETE per nexthop, but kernel_rtm() skipped every nexthop
    without ACTIVE, so nothing was deleted. The route through the dead
    interface stayed in the FIB next to the new one, or with
    net.route.multipath=0 kept the new one from being installed.

    Only skip inactive nexthops when adding. Deleting a gateway that is
    not in the kernel returns ESRCH, which is already ignored for
    RTM_DELETE.

    Fixes: #22418
    Signed-off-by: Abdul Wasey <w453y.me@gmail.com>

diff --git a/zebra/rt_socket.c b/zebra/rt_socket.c
index c91f3d07b9..7ee228da2a 100644
--- a/zebra/rt_socket.c
+++ b/zebra/rt_socket.c
@@ -108,10 +108,15 @@ static int kernel_rtm(int cmd, const struct prefix *p,
 	/* Make gateway. */
 	for (ALL_NEXTHOPS_PTR(ng, nexthop)) {
 		/*
-		 * We only want to use the actual good nexthops
+		 * We only want to install the actual good nexthops.  When
+		 * deleting, an interface going down has already cleared
+		 * ACTIVE on the nexthops that are still in the kernel, so
+		 * try all of them; a gateway that isn't there just gives
+		 * ESRCH.
 		 */
-		if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE) ||
-		    !CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE))
+		if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE))
+			continue;
+		if (cmd != RTM_DELETE && !CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE))
 			continue;

 		smplsp = NULL;