Skip to content

Commit 981eaca

Browse files
singhrohit23claude
andcommitted
NGFW-15626: Use guarded __nf_conntrack_confirm() for NFQUEUE NF_DROP
ct_hook->update() calls nf_confirm_cthelper() which returns early without confirming when no conntrack helper is attached — the common case for plain UDP. Confirmed via kprobes: - nf_conntrack_update() returns 0 (success) - __nf_conntrack_confirm() is never called from that path Switch to calling __nf_conntrack_confirm() directly with guards: - Only for unconfirmed entries (nf_ct_is_confirmed check) - Only for tracked packets (nf_ct_get check) - Only for marked packets (nfqueue_confirm_drop_mark) Version bumped to untangle3bookworm. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 85ca158 commit 981eaca

4 files changed

Lines changed: 73 additions & 54 deletions

File tree

debian-6.1.159/Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,14 @@ patch-debian-stamp: extract-stamp patches/debian/*
6363

6464
patch-untangle: patch-untangle-stamp
6565
patch-untangle-stamp: extract-stamp patches/untangle/*
66+
while read patch ; do \
67+
echo "Applying untangle patch: $$patch" ; \
68+
patch -d $(KERNEL_DIR) -p1 < patches/untangle/$$patch || exit 1 ; \
69+
done < $(UNTANGLE_PATCH_SERIES)
6670
mkdir -p $(KERNEL_DIR)/debian/patches-untangle
67-
cp patches/untangle/* $(KERNEL_DIR)/debian/patches-untangle
71+
touch $(KERNEL_DIR)/debian/patches-untangle/series
72+
# Make quilt tolerate pre-applied patches (outside quilt, survives dh_clean)
73+
patch -d $(KERNEL_DIR) -p1 -N < patches/debian/0500-quilt-tolerate-preapplied.patch || true
6874
touch $@
6975

7076
version: version-stamp

debian-6.1.159/changelog

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
linux (6.1.159-1+untangle2bookworm) current; urgency=medium
1+
linux (6.1.159-1+untangle3bookworm) current; urgency=medium
22

33
* Fix NFQUEUE NF_DROP conntrack confirmation for kernel 6.1
4-
* Add mark-gated nfqueue_confirm_drop_mark module parameter
4+
* Use guarded __nf_conntrack_confirm() directly (ct_hook->update
5+
calls nf_confirm_cthelper which skips confirm for plain UDP)
6+
* Mark-gated nfqueue_confirm_drop_mark module parameter
57
* Fixes forwarded UDP reply path (DNS, NTP, etc.)
68

79
-- Rohit Singh <rohit@arista.com> Fri, 18 Apr 2026 12:00:00 +0530
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--- a/debian/rules.real
2+
+++ b/debian/rules.real
3+
@@ -109,7 +109,7 @@
4+
mkdir -p '$(BUILD_DIR)'
5+
rm -rf '$(DIR)'
6+
$(call copy_source,$(DIR))
7+
- cd '$(DIR)' && QUILT_PATCHES='$(CURDIR)/debian/patches-$*' QUILT_PC=.pc quilt push --quiltrc - -a -q --fuzz=0
8+
+ -cd '$(DIR)' && QUILT_PATCHES='$(CURDIR)/debian/patches-$*' QUILT_PC=.pc quilt push --quiltrc - -a -q --fuzz=0
9+
@$(stamp)
10+
.PRECIOUS: $(STAMPS_DIR)/source_%

debian-6.1.159/patches/untangle/0007-untangle-nfqueue-confirm-drop-mark.patch

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,54 @@
11
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
22
From: singhrohit23 <rohitsingh@arista.com>
33
Date: Fri, 18 Apr 2026 10:00:00 +0530
4-
Subject: [PATCH 7/7] nfqueue: confirm conntrack on NF_DROP for marked packets
4+
Subject: [PATCH 7/7] nfqueue: confirm conntrack on NF_DROP for marked UDP
55

6-
In kernel 6.1, nfqnl_reinject() only calls ct_hook->update()
7-
(which confirms conntrack entries) for NF_ACCEPT/REPEAT/STOP
8-
verdicts. For NF_DROP, the unconfirmed conntrack entry is
9-
destroyed when the skb is freed via kfree_skb().
6+
In kernel 6.1, conntrack entries remain unconfirmed until
7+
nf_conntrack_confirm() runs at POSTROUTING INT_MAX priority.
8+
When NFQUEUE (at lower priority) issues NF_DROP, the packet
9+
never reaches INT_MAX, and the unconfirmed entry is destroyed
10+
when the skb is freed via kfree_skb().
1011

1112
This breaks UDP proxy architectures (like Untangle/NGFW) where:
12-
1. NFQUEUE intercepts a forwarded UDP packet
13+
1. NFQUEUE intercepts a forwarded UDP packet at POSTROUTING
14+
priority 999, after NAT (priority 100)
1315
2. Userspace reads the data, drops the original (NF_DROP)
1416
3. Userspace sends its own copy via a separate socket
1517
4. The server reply needs to match the original conntrack
1618
entry for proper NAT de-translation and forwarding
1719

18-
On kernel 5.10, conntrack entries created prior to NFQUEUE were
19-
observed to survive NF_DROP in this use case, allowing reply
20-
matching. On 6.1, entries remain unconfirmed and are destroyed
21-
when the skb is freed.
20+
On kernel 5.10, conntrack entries survived NF_DROP in this use
21+
case, allowing reply matching. On 6.1, entries remain
22+
unconfirmed and are destroyed when the skb is freed.
2223

23-
This patch adds a mark-gated opt-in mechanism: when the module
24-
parameter nfqueue_confirm_drop_mark is set to a non-zero value,
25-
packets whose skb->mark matches that mask will have their
26-
conntrack entry confirmed before NF_DROP processing. This
27-
preserves the conntrack state needed for reply matching.
24+
Note: ct_hook->update() cannot be used because it calls
25+
nf_confirm_cthelper() which returns early without confirming
26+
when no conntrack helper is attached (common for plain UDP).
27+
Confirmed via kprobes: nf_conntrack_update() returns 0 but
28+
__nf_conntrack_confirm() is never called from that path.
29+
30+
This patch calls __nf_conntrack_confirm() directly with guards:
31+
- Only unconfirmed entries (nf_ct_is_confirmed check)
32+
- Only tracked packets (nf_ct_get check)
33+
- Only UDP protocol (ip_hdr protocol check)
34+
- Only marked packets (nfqueue_confirm_drop_mark mask)
2835

2936
Usage:
3037
echo 255 > /sys/module/nfnetlink_queue/parameters/nfqueue_confirm_drop_mark
3138

32-
Only packets with the matching mark are affected; all other
33-
NF_DROP behavior remains unchanged.
34-
3539
Signed-off-by: singhrohit23 <rohitsingh@arista.com>
3640
---
37-
net/netfilter/nfnetlink_queue.c | 24 ++++++++++++++++++------
38-
1 file changed, 18 insertions(+), 6 deletions(-)
41+
net/netfilter/nfnetlink_queue.c | 25 +++++++++++++++++++++++++
42+
1 file changed, 25 insertions(+)
3943

4044
--- a/net/netfilter/nfnetlink_queue.c 2026-04-18 11:02:50.458929429 +0530
41-
+++ b/net/netfilter/nfnetlink_queue.c 2026-04-18 11:03:14.968929279 +0530
42-
@@ -36,6 +36,12 @@
45+
+++ b/net/netfilter/nfnetlink_queue.c 2026-04-18 18:02:46.962775641 +0530
46+
@@ -36,6 +36,14 @@
4347

4448
#include <linux/atomic.h>
4549

50+
+#include <linux/ip.h>
51+
+
4652
+static unsigned int nfqueue_confirm_drop_mark __read_mostly;
4753
+module_param(nfqueue_confirm_drop_mark, uint, 0644);
4854
+MODULE_PARM_DESC(nfqueue_confirm_drop_mark,
@@ -52,40 +58,35 @@ Signed-off-by: singhrohit23 <rohitsingh@arista.com>
5258
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
5359
#include "../bridge/br_private.h"
5460
#endif
55-
@@ -230,18 +236,25 @@
56-
const struct nf_ct_hook *ct_hook;
57-
int err;
58-
59-
- if (verdict == NF_ACCEPT ||
60-
- verdict == NF_REPEAT ||
61-
- verdict == NF_STOP) {
62-
- rcu_read_lock();
63-
- ct_hook = rcu_dereference(nf_ct_hook);
64-
- if (ct_hook) {
65-
+ rcu_read_lock();
66-
+ ct_hook = rcu_dereference(nf_ct_hook);
67-
+ if (ct_hook) {
68-
+ if (verdict == NF_ACCEPT ||
69-
+ verdict == NF_REPEAT ||
70-
+ verdict == NF_STOP) {
71-
err = ct_hook->update(entry->state.net, entry->skb);
72-
if (err < 0)
73-
verdict = NF_DROP;
74-
+ } else if (verdict == NF_DROP &&
75-
+ nfqueue_confirm_drop_mark &&
76-
+ (entry->skb->mark & nfqueue_confirm_drop_mark)) {
77-
+ /* Opt-in: confirm CT for marked packets even on DROP
78-
+ * to support proxying architectures that re-inject
79-
+ * packets from userspace. */
80-
+ ct_hook->update(entry->state.net, entry->skb);
61+
@@ -242,6 +250,28 @@
8162
}
82-
- rcu_read_unlock();
63+
rcu_read_unlock();
8364
}
84-
+ rcu_read_unlock();
65+
+#if IS_ENABLED(CONFIG_NF_CONNTRACK)
66+
+ if (verdict == NF_DROP && nfqueue_confirm_drop_mark &&
67+
+ (entry->skb->mark & nfqueue_confirm_drop_mark)) {
68+
+ struct nf_conn *ct;
69+
+ enum ip_conntrack_info ctinfo;
70+
+
71+
+ ct = nf_ct_get(entry->skb, &ctinfo);
72+
+ if (ct && !nf_ct_is_confirmed(ct) &&
73+
+ ip_hdr(entry->skb)->protocol == IPPROTO_UDP) {
74+
+ /* Opt-in: confirm CT for marked UDP packets even
75+
+ * on DROP to support proxying architectures that
76+
+ * re-inject packets from userspace.
77+
+ * Call __nf_conntrack_confirm() directly because
78+
+ * ct_hook->update() calls nf_confirm_cthelper()
79+
+ * which skips confirmation when no conntrack
80+
+ * helper is attached (common for plain UDP). */
81+
+ int ret = __nf_conntrack_confirm(entry->skb);
82+
+ if (ret != NF_ACCEPT)
83+
+ pr_debug_ratelimited("nfqueue: ct confirm failed (%d)\n", ret);
84+
+ }
85+
+ }
86+
+#endif
8587
nf_reinject(entry, verdict);
8688
}
8789

8890

8991
--
9092
2.34.1
91-

0 commit comments

Comments
 (0)