[PATCH v3 net-next 5/6] ipv4: Batch rt_flush_dev() in netdev_run_todo().
flat view
COOLING11d
IN NET-NEXT
From: Kuniyuki Iwashima <kuniyu@google.com>
Date: 2026-10-01 20:48:00
Subsystem:
networking [general], networking [ipv4/ipv6], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Ido Schimmel, Linus Torvalds
Queued in net-next as ca6854799069 on 2026-10-06.
IPv4 uncached routes are linked to the global per-cpu lists, rt_uncached_list. When unregistering a netdev, rt_flush_dev() iterates over the potentially long lists to find uncached routes tied to the device and swap it with blackhole_netdev. Since it is called for every device in dying netns under RTNL, it adds O(N_dev x (N_cpu + N_route)) costs to any batched device unregistration. Let's call it once per batched device unregistration without RTNL. Note that rt_flush_dev() must be called after setting dev->reg_state to NETREG_UNREGISTERED. Otherwise, because rt_flush_dev(NULL) runs without RTNL, it could race with unregister_netdevice_many_notify() and prematurely purge routes for NETREG_UNREGISTERING dev, for which flush_all_backlogs() has not been called yet. Reported-by: Chris J Arges <redacted> Closes: https://lore.kernel.org/netdev/20260917-hash-bucket-route-lists-v3-0-30493a37b6eb@cloudflare.com/ (local) Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com> --- include/net/route.h | 6 ++++++ net/core/dev.c | 4 ++++ net/ipv4/route.c | 11 +++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/include/net/route.h b/include/net/route.h
index 6b55de2e4df8..3fccd31eb74d 100644
--- a/include/net/route.h
+++ b/include/net/route.h@@ -129,7 +129,13 @@ struct in_device; int ip_rt_init(void); void rt_cache_flush(struct net *net); +#ifdef CONFIG_INET void rt_flush_dev(struct net_device *dev); +#else +static inline void rt_flush_dev(struct net_device *dev) +{ +} +#endif static inline void inet_sk_init_flowi4(const struct inet_sock *inet, struct flowi4 *fl4)
diff --git a/net/core/dev.c b/net/core/dev.c
index a8eb382f40ca..7dba0292f052 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c@@ -129,6 +129,7 @@ #include <linux/ip.h> #include <net/ip.h> #include <net/mpls.h> +#include <net/route.h> #include <linux/ipv6.h> #include <linux/in.h> #include <linux/jhash.h>
@@ -11838,6 +11839,9 @@ void netdev_run_todo(void) linkwatch_sync_dev(dev); } + if (!list_empty(&list)) + rt_flush_dev(NULL); + cnt = 0; while (!list_empty(&list)) { dev = netdev_wait_allrefs_any(&list);
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index d7da2f1acbb5..1b641901f10d 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c@@ -1587,6 +1587,9 @@ void rt_flush_dev(struct net_device *dev) struct rtable *rt, *safe; int cpu; + if (dev && dev->dismantle) + return; + for_each_possible_cpu(cpu) { struct uncached_list *ul = &per_cpu(rt_uncached_list, cpu);
@@ -1595,10 +1598,14 @@ void rt_flush_dev(struct net_device *dev) spin_lock_bh(&ul->lock); list_for_each_entry_safe(rt, safe, &ul->head, dst.rt_uncached) { - if (rt->dst.dev != dev) + struct net_device *rt_dev = rt->dst.dev; + + if (dev ? rt_dev != dev : + READ_ONCE(rt_dev->reg_state) != NETREG_UNREGISTERED) continue; + rcu_assign_pointer(rt->dst.dev_rcu, blackhole_netdev); - netdev_ref_replace(dev, blackhole_netdev, + netdev_ref_replace(rt_dev, blackhole_netdev, &rt->dst.dev_tracker, GFP_ATOMIC); list_del_init(&rt->dst.rt_uncached); }
--
2.56.0.rc1.315.gc6ed9934b7-goog