* [PATCH net] netfilter: nf_flow_table: defer pernet stats release to last flowtable teardown
@ 2026-11-05 6:55 Qihang
2026-11-05 7:03 ` netdev-bot+sinfo
0 siblings, 1 reply; 2+ messages in thread
From: Qihang @ 2026-11-05 6:55 UTC (permalink / raw)
To: pablo, fw
Cc: phil, vladbu, ozsh, netfilter-devel, coreteam, netdev,
linux-kernel, Qihang, stable
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)
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [PATCH net] netfilter: nf_flow_table: defer pernet stats release to last flowtable teardown
2026-11-05 6:55 [PATCH net] netfilter: nf_flow_table: defer pernet stats release to last flowtable teardown Qihang
@ 2026-11-05 7:03 ` netdev-bot+sinfo
0 siblings, 0 replies; 2+ messages in thread
From: netdev-bot+sinfo @ 2026-11-05 7:03 UTC (permalink / raw)
To: Qihang
Cc: pablo, fw, phil, vladbu, ozsh, netfilter-devel, coreteam, netdev,
linux-kernel, stable
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
- Whether the issue was actually triggered, or is only theoretical
(e.g. found by code inspection). If it was triggered please include
the symptoms, like the stack trace or error messages.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-11-05 7:03 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05 6:55 [PATCH net] netfilter: nf_flow_table: defer pernet stats release to last flowtable teardown Qihang
2026-11-05 7:03 ` netdev-bot+sinfo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®