mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
ipv4: Batch rt_flush_dev() in netdev_run_todo().
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 <carges@cloudflare.com> Closes: https://lore.kernel.org/netdev/20260917-hash-bucket-route-lists-v3-0-30493a37b6eb@cloudflare.com/ Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com> Reviewed-by: Ido Schimmel <idosch@nvidia.com> Tested-by: Chris J Arges <carges@cloudflare.com> Link: https://patch.msgid.link/20261001204752.2572265-6-kuniyu@google.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
4eb2e2f358
commit
ca68547990
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -11886,6 +11887,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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user