From: Ruide Cao A conntrack whose destroy event cannot be delivered is kept on the per-netns ecache list while the workqueue retries the notification. The worker stops at the first failure and retries every 10 ms without a lifetime limit. A listener that enables NETLINK_BROADCAST_ERROR and never drains its receive queue can therefore retain conntracks and keep the worker busy indefinitely. Start a per-netns deadline when redelivery begins. If the listener remains congested after the existing 15 second grace period, detach all queued entries through the normal evicted-list path and release their references. Reset the deadline when the list drains, preserving redelivery for transient congestion while preventing permanent conntrack retention. Fixes: dd7669a92c60 ("netfilter: conntrack: optional reliable conntrack event delivery") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Ruide Cao Signed-off-by: Ren Wei --- include/net/netfilter/nf_conntrack.h | 1 + net/netfilter/nf_conntrack_ecache.c | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/include/net/netfilter/nf_conntrack.h b/include/net/netfilter/nf_conntrack.h index bc42dd0e10e6..d4c646c685b4 100644 --- a/include/net/netfilter/nf_conntrack.h +++ b/include/net/netfilter/nf_conntrack.h @@ -46,6 +46,7 @@ struct nf_conntrack_net_ecache { struct delayed_work dwork; spinlock_t dying_lock; struct hlist_nulls_head dying_list; + unsigned long retry_deadline; }; struct nf_conntrack_net { diff --git a/net/netfilter/nf_conntrack_ecache.c b/net/netfilter/nf_conntrack_ecache.c index cc8d8e85169f..9280fc947da9 100644 --- a/net/netfilter/nf_conntrack_ecache.c +++ b/net/netfilter/nf_conntrack_ecache.c @@ -31,6 +31,7 @@ static DEFINE_MUTEX(nf_ct_ecache_mutex); #define DYING_NULLS_VAL ((1 << 30) + 1) #define ECACHE_MAX_JIFFIES msecs_to_jiffies(10) #define ECACHE_RETRY_JIFFIES msecs_to_jiffies(10) +#define ECACHE_RETRY_TIMEOUT (15 * HZ) enum retry_state { STATE_CONGESTED, @@ -52,8 +53,8 @@ static enum retry_state ecache_work_evict_list(struct nf_conntrack_net *cnet) { unsigned long stop = jiffies + ECACHE_MAX_JIFFIES; struct hlist_nulls_head evicted_list; - enum retry_state ret = STATE_DONE; struct nf_conntrack_tuple_hash *h; + enum retry_state ret = STATE_DONE; struct hlist_nulls_node *n; unsigned int sent; @@ -63,6 +64,22 @@ static enum retry_state ecache_work_evict_list(struct nf_conntrack_net *cnet) sent = 0; spin_lock_bh(&cnet->ecache.dying_lock); + if (!cnet->ecache.retry_deadline) + cnet->ecache.retry_deadline = jiffies + ECACHE_RETRY_TIMEOUT; + + if (time_after_eq(jiffies, cnet->ecache.retry_deadline)) { + hlist_nulls_for_each_entry_safe(h, n, &cnet->ecache.dying_list, + hnnode) { + struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(h); + + hlist_nulls_del_rcu(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode); + hlist_nulls_add_head(&ct->tuplehash[IP_CT_DIR_REPLY].hnnode, + &evicted_list); + } + ret = STATE_DONE; + goto out_unlock; + } + hlist_nulls_for_each_entry_safe(h, n, &cnet->ecache.dying_list, hnnode) { struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(h); @@ -89,6 +106,10 @@ static enum retry_state ecache_work_evict_list(struct nf_conntrack_net *cnet) } } +out_unlock: + if (hlist_nulls_empty(&cnet->ecache.dying_list)) + cnet->ecache.retry_deadline = 0; + spin_unlock_bh(&cnet->ecache.dying_lock); hlist_nulls_for_each_entry_safe(h, n, &evicted_list, hnnode) { -- 2.47.3