[PATCH v2 net] netfilter: flowtable: fix offloaded ct timeout never being extended
Adrian Bente
adibente at gmail.com
Thu May 28 00:08:51 PDT 2026
OpenWrt has recently migrated many platforms to kernel 6.18. On the
MediaTek platform, which supports hardware network offloading, WiFi
connections accelerated via the WED path were observed to drop after
roughly 300 seconds.
After several debugging sessions, assisted by the Claude LLM, the
problem was narrowed down as follows:
nf_flow_table_extend_ct_timeout() extends ct->timeout for offloaded
flows using:
cmpxchg(&ct->timeout, expires, new_timeout);
'expires' comes from nf_ct_expires(ct) and is a relative value, while
ct->timeout holds an absolute timestamp. The two are never equal, so
the cmpxchg always fails and the timeout is never extended.
This goes unnoticed for most flows, but a long-lived hardware (WED)
offloaded flow on MediaTek MT7986 eventually has ct->timeout decay to
zero, the conntrack entry is reaped and the connection breaks.
Open-code the relative value from a single READ_ONCE(ct->timeout)
snapshot and compare against that same absolute snapshot in the
cmpxchg, so the timeout extension actually takes effect while the
datapath remains authoritative if it updates ct->timeout concurrently.
Suggested-by: Florian Westphal <fw at strlen.de>
Fixes: 03428ca5cee9 ("netfilter: conntrack: rework offload nf_conn timeout extension logic")
Cc: stable at vger.kernel.org
Signed-off-by: Adrian Bente <adibente at gmail.com>
---
v2:
- open-code expires from an absolute READ_ONCE(ct->timeout) snapshot
instead of the relative nf_ct_expires() value (Florian Westphal)
- change min_timeout to s32 to keep the comparison signed
- initialise new_timeout to 1 instead of true
net/netfilter/nf_flow_table_core.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
index 3313f82..a2d08e5 100644
--- a/net/netfilter/nf_flow_table_core.c
+++ b/net/netfilter/nf_flow_table_core.c
@@ -500,8 +500,13 @@
*/
static void nf_flow_table_extend_ct_timeout(struct nf_conn *ct)
{
- static const u32 min_timeout = 5 * 60 * HZ;
- u32 expires = nf_ct_expires(ct);
+ static const s32 min_timeout = 5 * 60 * HZ;
+ u32 ct_timeout = READ_ONCE(ct->timeout);
+ s32 expires;
+
+ expires = ct_timeout - nfct_time_stamp;
+ if (expires <= 0) /* already expired */
+ return;
/* normal case: large enough timeout, nothing to do. */
if (likely(expires >= min_timeout))
@@ -519,7 +524,7 @@ static void nf_flow_table_extend_ct_timeout(struct nf_conn *ct)
if (nf_ct_is_confirmed(ct) &&
test_bit(IPS_OFFLOAD_BIT, &ct->status)) {
u8 l4proto = nf_ct_protonum(ct);
- u32 new_timeout = true;
+ u32 new_timeout = 1;
switch (l4proto) {
case IPPROTO_UDP:
@@ -544,7 +549,7 @@ static void nf_flow_table_extend_ct_timeout(struct nf_conn *ct)
*/
if (new_timeout) {
new_timeout += nfct_time_stamp;
- cmpxchg(&ct->timeout, expires, new_timeout);
+ cmpxchg(&ct->timeout, ct_timeout, new_timeout);
}
}
--
2.43.0
More information about the linux-arm-kernel
mailing list