Re: [PATCH] net: make sure final 'struct net' free in net_complete_free() is always deferred
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: 2026-08-25 13:21:46
Subsystem:
networking [general], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
On 2026/08/17 23:00, Eric Dumazet wrote:
However, the observation regarding the copy_net_ns() error path is valid: because failed namespaces never go through __put_net(), net_cleanup_work is not queued, so a failed net structure can stay on defer_free_list until another namespace is destroyed. The proper fix for that benign issue would be to directly free the struct net in the copy_net_ns() error path (since it was never active and does not need deferred freeing), rather than modifying cleanup_net().
Now that "net: fix a resource leak in copy_net_ns() error handling path"
handed over to net_passive_dec() (instead of calling kmem_cache_free() from
copy_net_ns()), it is time to fix the last problem which sashiko repeatedly mentions
due to commit 0f6ede9fbc74 ("net: defer final 'struct net' free in netns dismantle").
My understanding is that we need to wait for RCU grace period before
calling kmem_cache_free(). Then, I think we can simplify like shown below (either
pattern A or pattern B). Which pattern do you like?
Pattern A:
net/core/net_namespace.c | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index da5f881fbd3b..421398149bc2 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c@@ -511,19 +511,20 @@ static struct net *net_alloc(void) static LLIST_HEAD(defer_free_list); -static void net_complete_free(void) +static void net_complete_free(struct work_struct *work) { struct llist_node *kill_list; struct net *net, *next; - /* Get the list of namespaces to free from last round. */ kill_list = llist_del_all(&defer_free_list); - + /* Wait for an extra rcu_barrier() before final free. */ + rcu_barrier(); llist_for_each_entry_safe(net, next, kill_list, defer_free_list) kmem_cache_free(net_cachep, net); - } +static DECLARE_WORK(net_complete_free_work, net_complete_free); + void net_passive_dec(struct net *net) { if (refcount_dec_and_test(&net->passive)) {
@@ -536,8 +537,8 @@ void net_passive_dec(struct net *net) ref_tracker_dir_exit(&net->refcnt_tracker); #endif - /* Wait for an extra rcu_barrier() before final free. */ llist_add(&net->defer_free_list, &defer_free_list); + queue_work(system_long_wq, &net_complete_free_work); } }
@@ -712,8 +713,6 @@ static void cleanup_net(struct work_struct *work) */ rcu_barrier(); - net_complete_free(); - /* Finally it is safe to free my network namespace structure */ list_for_each_entry_safe(net, tmp, &net_exit_list, exit_list) { list_del_init(&net->exit_list);
--
Pattern B:
include/net/net_namespace.h | 2 +-
net/core/net_namespace.c | 18 ++++--------------
2 files changed, 5 insertions(+), 15 deletions(-)
diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index e5ee673b9fcf..9419a198faf7 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -81,7 +81,7 @@ struct net {
* or to unregister pernet ops
* (pernet_ops_rwsem write locked).
*/
- struct llist_node defer_free_list;
+ struct rcu_head complete_free_rcu;
struct llist_node cleanup_list; /* namespaces on death row */
struct list_head ptype_all;
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index da5f881fbd3b..2ce0f281922d 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -509,18 +509,11 @@ static struct net *net_alloc(void)
goto out;
}
-static LLIST_HEAD(defer_free_list);
-
-static void net_complete_free(void)
+static void net_complete_free(struct rcu_head *rcu)
{
- struct llist_node *kill_list;
- struct net *net, *next;
-
- /* Get the list of namespaces to free from last round. */
- kill_list = llist_del_all(&defer_free_list);
+ struct net *net = container_of(rcu, struct net, complete_free_rcu);
- llist_for_each_entry_safe(net, next, kill_list, defer_free_list)
- kmem_cache_free(net_cachep, net);
+ kmem_cache_free(net_cachep, net);
}
@@ -536,8 +529,7 @@ void net_passive_dec(struct net *net)
ref_tracker_dir_exit(&net->refcnt_tracker);
#endif
- /* Wait for an extra rcu_barrier() before final free. */
- llist_add(&net->defer_free_list, &defer_free_list);
+ call_rcu(&net->complete_free_rcu, net_complete_free);
}
}
@@ -712,8 +704,6 @@ static void cleanup_net(struct work_struct *work)
*/
rcu_barrier();
- net_complete_free();
-
/* Finally it is safe to free my network namespace structure */
list_for_each_entry_safe(net, tmp, &net_exit_list, exit_list) {
list_del_init(&net->exit_list);
--