From: Qihang <q.h.hack.winter@gmail.com>
To: pablo@netfilter.org, fw@strlen.de
Cc: phil@nwl.cc, vladbu@nvidia.com, ozsh@nvidia.com,
netfilter-devel@vger.kernel.org, coreteam@netfilter.org,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
Qihang <q.h.hack.winter@gmail.com>,
stable@vger.kernel.org
Subject: [PATCH net] netfilter: nf_flow_table: defer pernet stats release to last flowtable teardown
Date: Thu, 5 Nov 2026 14:55:09 +0800 [thread overview]
Message-ID: <20261105065509.94627-1-q.h.hack.winter@gmail.com> (raw)
The pernet flowtable statistics (net->ft.stat) are released when the
netns exits, but the flowtables attached to a dying netns are torn down
asynchronously afterwards: the tc conntrack action frees its flowtable
from an RCU-delayed workqueue item (tcf_ct_flow_table_cleanup_work()),
and nf_tables releases flowtables from the transaction release work.
nf_flow_table_free() ends with a garbage-collection pass that queues
offload works; flow_offload_queue_work() increments the pernet counters
when scheduling them and the handlers decrement the counters again once
they complete. The flushes in nf_flow_table_free() only drain those
workqueues when hardware offload is in use, so after free_percpu() the
stray workers still this_cpu_inc()/this_cpu_dec() into freed percpu
memory.
Account flowtables at init time and defer the release of the statistics
to the last nf_flow_table_free() of a dismantled netns, after draining
the offload workqueues. A netns without flowtables keeps the
synchronous release.
Fixes: b038177636f8 ("netfilter: nf_flow_table: count pending offload workqueue tasks")
Cc: stable@vger.kernel.org
Signed-off-by: Qihang <q.h.hack.winter@gmail.com>
---
include/net/netfilter/nf_flow_table.h | 1 +
include/net/netns/flow_table.h | 11 +++++++
net/netfilter/nf_flow_table_core.c | 44 ++++++++++++++++++++++++++-
net/netfilter/nf_flow_table_offload.c | 7 +++++
4 files changed, 62 insertions(+), 1 deletion(-)
diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
index 5b22d31cb1407..91ffa3072a0cc 100644
--- a/include/net/netfilter/nf_flow_table.h
+++ b/include/net/netfilter/nf_flow_table.h
@@ -376,6 +376,7 @@ void nf_flow_offload_stats(struct nf_flowtable *flowtable,
void nf_flow_table_offload_flush(struct nf_flowtable *flowtable);
void nf_flow_table_offload_flush_cleanup(struct nf_flowtable *flowtable);
+void nf_flow_table_offload_drain(void);
int nf_flow_table_offload_setup(struct nf_flowtable *flowtable,
struct net_device *dev,
diff --git a/include/net/netns/flow_table.h b/include/net/netns/flow_table.h
index 1c5fc657e2675..6d2dc0d1f06c7 100644
--- a/include/net/netns/flow_table.h
+++ b/include/net/netns/flow_table.h
@@ -2,6 +2,9 @@
#ifndef __NETNS_FLOW_TABLE_H
#define __NETNS_FLOW_TABLE_H
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
struct nf_flow_table_stat {
unsigned int count_wq_add;
unsigned int count_wq_del;
@@ -9,6 +12,14 @@ struct nf_flow_table_stat {
};
struct netns_ft {
+ /* Protects nr_ft and exiting. */
+ spinlock_t lock;
+ /* Number of flowtables attached to this netns. */
+ int nr_ft;
+ /* Set at netns exit; the statistics are then released by the
+ * last flowtable teardown.
+ */
+ bool exiting;
struct nf_flow_table_stat __percpu *stat;
};
#endif
diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
index 0d72dc8ae47a1..de0a9f0e4f7b9 100644
--- a/net/netfilter/nf_flow_table_core.c
+++ b/net/netfilter/nf_flow_table_core.c
@@ -715,6 +715,7 @@ EXPORT_SYMBOL_GPL(nf_flow_dnat_port);
int nf_flow_table_init(struct nf_flowtable *flowtable)
{
+ struct net *net;
int err;
INIT_DELAYED_WORK(&flowtable->gc_work, nf_flow_offload_work_gc);
@@ -733,6 +734,13 @@ int nf_flow_table_init(struct nf_flowtable *flowtable)
list_add(&flowtable->list, &flowtables);
mutex_unlock(&flowtable_lock);
+ net = read_pnet(&flowtable->net);
+ if (net) {
+ spin_lock(&net->ft.lock);
+ net->ft.nr_ft++;
+ spin_unlock(&net->ft.lock);
+ }
+
return 0;
}
EXPORT_SYMBOL_GPL(nf_flow_table_init);
@@ -771,6 +779,8 @@ EXPORT_SYMBOL_GPL(nf_flow_table_cleanup);
void nf_flow_table_free(struct nf_flowtable *flow_table)
{
+ struct net *net;
+
mutex_lock(&flowtable_lock);
list_del(&flow_table->list);
mutex_unlock(&flowtable_lock);
@@ -781,19 +791,51 @@ void nf_flow_table_free(struct nf_flowtable *flow_table)
nf_flow_table_iterate(flow_table, nf_flow_table_do_cleanup, NULL);
nf_flow_table_gc_run(flow_table);
nf_flow_table_offload_flush_cleanup(flow_table);
+
+ /* The gc pass above can still queue offload works whose handlers
+ * decrement the pernet statistics. Drain them all.
+ */
+ nf_flow_table_offload_drain();
rhashtable_destroy(&flow_table->rhashtable);
+
+ net = read_pnet(&flow_table->net);
+ if (net) {
+ bool release;
+
+ spin_lock(&net->ft.lock);
+ release = net->ft.exiting && !--net->ft.nr_ft;
+ spin_unlock(&net->ft.lock);
+
+ /* Released by the last flowtable teardown, see
+ * nf_flow_table_fini_net().
+ */
+ if (release)
+ free_percpu(net->ft.stat);
+ }
}
EXPORT_SYMBOL_GPL(nf_flow_table_free);
static int nf_flow_table_init_net(struct net *net)
{
+ spin_lock_init(&net->ft.lock);
net->ft.stat = alloc_percpu(struct nf_flow_table_stat);
return net->ft.stat ? 0 : -ENOMEM;
}
static void nf_flow_table_fini_net(struct net *net)
{
- free_percpu(net->ft.stat);
+ bool release;
+
+ spin_lock(&net->ft.lock);
+ net->ft.exiting = true;
+ release = !net->ft.nr_ft;
+ spin_unlock(&net->ft.lock);
+
+ /* Pending flowtables still update the statistics from workqueue
+ * context; the last one releases them, see nf_flow_table_free().
+ */
+ if (release)
+ free_percpu(net->ft.stat);
}
static int nf_flow_table_pernet_init(struct net *net)
diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
index 4365859220e6a..6ac28802ef632 100644
--- a/net/netfilter/nf_flow_table_offload.c
+++ b/net/netfilter/nf_flow_table_offload.c
@@ -1180,6 +1180,13 @@ void nf_flow_table_offload_flush(struct nf_flowtable *flowtable)
}
}
+void nf_flow_table_offload_drain(void)
+{
+ flush_workqueue(nf_flow_offload_add_wq);
+ flush_workqueue(nf_flow_offload_del_wq);
+ flush_workqueue(nf_flow_offload_stats_wq);
+}
+
static int nf_flow_table_block_setup(struct nf_flowtable *flowtable,
struct flow_block_offload *bo,
enum flow_block_command cmd)
--
2.54.0 (Apple Git-157)
next reply other threads:[~2026-11-05 6:55 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 6:55 Qihang [this message]
2026-11-05 7:03 ` [PATCH net] netfilter: nf_flow_table: defer pernet stats release to last flowtable teardown netdev-bot+sinfo
2026-11-09 7:27 ` netdev-bot+sashiko
2026-11-10 0:50 ` Pablo Neira Ayuso
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261105065509.94627-1-q.h.hack.winter@gmail.com \
--to=q.h.hack.winter@gmail.com \
--cc=coreteam@netfilter.org \
--cc=fw@strlen.de \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=netfilter-devel@vger.kernel.org \
--cc=ozsh@nvidia.com \
--cc=pablo@netfilter.org \
--cc=phil@nwl.cc \
--cc=stable@vger.kernel.org \
--cc=vladbu@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.