rtnetlink: Add per-netns rtnl_work.

The biggest blocker to per-netns RTNL is netdev unregistration.

It starts within a single netns (e.g., during a device lookup or
netns dismantle), but it can eventually involve multiple namespaces,
such as when upper ipvlan devices reside in different netns.

This prevents us from acquiring multiple rtnl_net_lock()s beforehand.

When we encounter such a cross-netns device, we must delegate the
unregistration to the work of the netns where the device actually
resides.

Let's add per-netns rtnl_work to support the deferred netdev
unregistration.

Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
Link: https://patch.msgid.link/20260703001009.1572444-4-kuniyu@google.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Kuniyuki Iwashima 2026-07-03 00:09:14 +00:00 committed by Paolo Abeni
parent 49c26d4bf1
commit c6cfaf9783
4 changed files with 36 additions and 0 deletions

View File

@ -115,6 +115,10 @@ bool rtnl_net_is_locked(struct net *net);
bool lockdep_rtnl_net_is_held(struct net *net);
void rtnl_net_queue_work(struct net *net);
void rtnl_net_flush_workqueue(void);
void rtnl_net_work_func(struct work_struct *work);
#define rcu_dereference_rtnl_net(net, p) \
rcu_dereference_check(p, lockdep_rtnl_net_is_held(net))
#define rtnl_net_dereference(net, p) \
@ -150,6 +154,10 @@ static inline void ASSERT_RTNL_NET(struct net *net)
ASSERT_RTNL();
}
static inline void rtnl_net_flush_workqueue(void)
{
}
#define rcu_dereference_rtnl_net(net, p) \
rcu_dereference_rtnl(p)
#define rtnl_net_dereference(net, p) \

View File

@ -197,6 +197,7 @@ struct net {
#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
/* Move to a better place when the config guard is removed. */
struct mutex rtnl_mutex;
struct work_struct rtnl_work;
#endif
#if IS_ENABLED(CONFIG_VSOCKETS)
struct netns_vsock vsock;

View File

@ -422,6 +422,7 @@ static __net_init int preinit_net(struct net *net, struct user_namespace *user_n
#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
mutex_init(&net->rtnl_mutex);
lock_set_cmp_fn(&net->rtnl_mutex, rtnl_net_lock_cmp_fn, NULL);
INIT_WORK(&net->rtnl_work, rtnl_net_work_func);
#endif
INIT_LIST_HEAD(&net->ptype_all);

View File

@ -273,6 +273,26 @@ bool lockdep_rtnl_net_is_held(struct net *net)
return lockdep_rtnl_is_held() && lockdep_is_held(&net->rtnl_mutex);
}
EXPORT_SYMBOL(lockdep_rtnl_net_is_held);
static struct workqueue_struct *rtnl_net_wq;
void rtnl_net_queue_work(struct net *net)
{
queue_work(rtnl_net_wq, &net->rtnl_work);
}
void rtnl_net_flush_workqueue(void)
{
flush_workqueue(rtnl_net_wq);
}
void rtnl_net_work_func(struct work_struct *work)
{
struct net *net = container_of(work, struct net, rtnl_work);
rtnl_net_lock(net);
rtnl_net_unlock(net);
}
#else
static int rtnl_net_cmp_locks(const struct net *net_a, const struct net *net_b)
{
@ -7229,4 +7249,10 @@ void __init rtnetlink_init(void)
register_netdevice_notifier(&rtnetlink_dev_notifier);
rtnl_register_many(rtnetlink_rtnl_msg_handlers);
#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
rtnl_net_wq = create_workqueue("rtnl_net");
if (!rtnl_net_wq)
panic("Could not create rtnl_net workq");
#endif
}