Netdev List
 help / color / mirror / Atom feed
* [PATCH nf] netfilter: xt_nat: allow IP_CT_RELATED_REPLY in DNAT targets
@ 2026-11-06  6:56 xujiahao
  2026-11-06  7:32 ` Florian Westphal
  2026-11-10  2:18 ` [PATCH nf v2] netfilter: conntrack: attach generated TCP resets as ESTABLISHED xujiahao
  0 siblings, 2 replies; 3+ messages in thread
From: xujiahao @ 2026-11-06  6:56 UTC (permalink / raw)
  To: Pablo Neira Ayuso, Florian Westphal
  Cc: Phil Sutter, netfilter-devel, netdev, linux-kernel, xujiahao,
	syzbot+2e819f6641708bb6ab9d

The DNAT targets warn when the conntrack info is neither IP_CT_NEW nor
IP_CT_RELATED, while the corresponding SNAT targets additionally accept
IP_CT_RELATED_REPLY:

  WARN_ON(!(ct != NULL &&
           (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED)));

A TCP reset generated by nf_send_reset() on the reject path is attached
to the conntrack entry of the rejected packet and marked
IP_CT_RELATED_REPLY.  It traverses the LOCAL_OUT NAT hook, where
nf_nat_inet_fn() lets IP_CT_RELATED_REPLY packets whose destination NAT
mapping is not initialized yet run the OUTPUT NAT chain.  When that
chain holds an xtables DNAT target, the target is reached with
IP_CT_RELATED_REPLY and warns:

  WARNING: net/netfilter/xt_nat.c:154 at xt_dnat_target_v2+0x97/0xc0

The same packet is accepted by the SNAT side, so the assertion is
incomplete rather than the packet being unexpected.  Align the DNAT
targets with the SNAT targets.

Reported-by: syzbot+2e819f6641708bb6ab9d@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=2e819f6641708bb6ab9d
Signed-off-by: xujiahao <xujiahao@cmss.chinamobile.com>
---
 net/netfilter/xt_nat.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/net/netfilter/xt_nat.c b/net/netfilter/xt_nat.c
index 51c7f7ce8..e565fb341 100644
--- a/net/netfilter/xt_nat.c
+++ b/net/netfilter/xt_nat.c
@@ -84,7 +84,8 @@ xt_dnat_target_v0(struct sk_buff *skb, const struct xt_action_param *par)
 
 	ct = nf_ct_get(skb, &ctinfo);
 	WARN_ON(!(ct != NULL &&
-		 (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED)));
+		 (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||
+		  ctinfo == IP_CT_RELATED_REPLY)));
 
 	xt_nat_convert_range(&range, &mr->range[0]);
 	return nf_nat_setup_info(ct, &range, NF_NAT_MANIP_DST);
@@ -119,7 +120,8 @@ xt_dnat_target_v1(struct sk_buff *skb, const struct xt_action_param *par)
 
 	ct = nf_ct_get(skb, &ctinfo);
 	WARN_ON(!(ct != NULL &&
-		 (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED)));
+		 (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||
+		  ctinfo == IP_CT_RELATED_REPLY)));
 
 	memcpy(&range, range_v1, sizeof(*range_v1));
 	memset(&range.base_proto, 0, sizeof(range.base_proto));
@@ -151,7 +153,8 @@ xt_dnat_target_v2(struct sk_buff *skb, const struct xt_action_param *par)
 
 	ct = nf_ct_get(skb, &ctinfo);
 	WARN_ON(!(ct != NULL &&
-		 (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED)));
+		 (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||
+		  ctinfo == IP_CT_RELATED_REPLY)));
 
 	return nf_nat_setup_info(ct, range, NF_NAT_MANIP_DST);
 }
-- 
2.43.0




^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH nf] netfilter: xt_nat: allow IP_CT_RELATED_REPLY in DNAT targets
  2026-11-06  6:56 [PATCH nf] netfilter: xt_nat: allow IP_CT_RELATED_REPLY in DNAT targets xujiahao
@ 2026-11-06  7:32 ` Florian Westphal
  2026-11-10  2:18 ` [PATCH nf v2] netfilter: conntrack: attach generated TCP resets as ESTABLISHED xujiahao
  1 sibling, 0 replies; 3+ messages in thread
From: Florian Westphal @ 2026-11-06  7:32 UTC (permalink / raw)
  To: xujiahao
  Cc: Pablo Neira Ayuso, Phil Sutter, netfilter-devel, netdev,
	linux-kernel, syzbot+2e819f6641708bb6ab9d

xujiahao <xujiahao@cmss.chinamobile.com> wrote:
> The DNAT targets warn when the conntrack info is neither IP_CT_NEW nor
> IP_CT_RELATED, while the corresponding SNAT targets additionally accept
> IP_CT_RELATED_REPLY:
> 
>   WARN_ON(!(ct != NULL &&
>            (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED)));
> 
> A TCP reset generated by nf_send_reset() on the reject path is attached
> to the conntrack entry of the rejected packet and marked
> IP_CT_RELATED_REPLY.

I don't think thats correct.  nf_conntrack_attach() assumes ICMP.

TCP should just set IP_CT_ESTABLISHED, we're 'emulating' a reset
packet coming from peer, so we should not create a fresh/new nat
rewrite.

I think this should be fixed in nf_conntrack_attach() to check for
TCP and then set IP_CT_ESTABLISHED state for nskb.

> It traverses the LOCAL_OUT NAT hook, where
> nf_nat_inet_fn() lets IP_CT_RELATED_REPLY packets whose destination NAT
> mapping is not initialized yet run the OUTPUT NAT chain.  When that
> chain holds an xtables DNAT target, the target is reached with
> IP_CT_RELATED_REPLY and warns:
> 
>   WARNING: net/netfilter/xt_nat.c:154 at xt_dnat_target_v2+0x97/0xc0

This warning is correct. We should not do a DNAT mapping for
IP_CT_RELATED_REPLY on TCP, it would cause packet rewrite.

The IP_CT_RELATED_REPLY warning is old, from a time when DNAT was
restricted to PRE_ROUTING hooks, which is likely why its only
in the SNAT handler (which was restricted to POST_ROUTING only).

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH nf v2] netfilter: conntrack: attach generated TCP resets as ESTABLISHED
  2026-11-06  6:56 [PATCH nf] netfilter: xt_nat: allow IP_CT_RELATED_REPLY in DNAT targets xujiahao
  2026-11-06  7:32 ` Florian Westphal
@ 2026-11-10  2:18 ` xujiahao
  1 sibling, 0 replies; 3+ messages in thread
From: xujiahao @ 2026-11-10  2:18 UTC (permalink / raw)
  To: Pablo Neira Ayuso, netfilter-devel
  Cc: Florian Westphal, Phil Sutter, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-kernel,
	syzbot+2e819f6641708bb6ab9d, xujiahao

nf_conntrack_attach() marks every packet it attaches as related to the
conntrack entry of the packet which caused it: IP_CT_RELATED_REPLY when
that packet was in the original direction, IP_CT_RELATED otherwise.

That is right for the ICMP errors built by icmp.c and icmpv6.c, but not
for the TCP resets built by nf_send_reset(): a reset is a packet of the
connection itself and carries the state of the connection, just like the
equivalent reset the TCP stack emits.

A locally generated reset therefore currently leaves through the
LOCAL_OUT nat hook as IP_CT_RELATED_REPLY.  As no nat mapping is
initialized for it, nf_nat_inet_fn() evaluates the nat OUTPUT chain, so a
DNAT target reachable from that chain rewrites the reset and warns:

  WARNING: net/netfilter/xt_nat.c:154 at xt_dnat_target_v2+0x97/0xc0

Pass the protocol of the generated packet to nf_ct_attach() and mark TCP
resets as ESTABLISHED/ESTABLISHED_REPLY.  They then take the default
branch of nf_nat_inet_fn(), which only applies the mapping already
installed on the connection.  ICMP errors keep IP_CT_RELATED/_REPLY.

Passing u8 l4proto rather than a boolean keeps the call sites readable,
as nf_ct_attach(new, skb, IPPROTO_TCP) says which packet is built.

Suggested-by: Florian Westphal <fw@strlen.de>
Reported-by: syzbot+2e819f6641708bb6ab9d@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=2e819f6641708bb6ab9d
Signed-off-by: xujiahao <xujiahao@cmss.chinamobile.com>
---
 include/linux/netfilter.h           |  8 +++++---
 net/ipv4/icmp.c                     |  2 +-
 net/ipv4/netfilter/nf_reject_ipv4.c |  2 +-
 net/ipv6/icmp.c                     |  2 +-
 net/ipv6/netfilter/nf_reject_ipv6.c |  2 +-
 net/netfilter/core.c                |  4 ++--
 net/netfilter/nf_conntrack_core.c   | 27 +++++++++++++++++++++------
 7 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h
index e99afc141..c9771578f 100644
--- a/include/linux/netfilter.h
+++ b/include/linux/netfilter.h
@@ -454,13 +454,14 @@ nf_nat_decode_session(struct sk_buff *skb, struct flowi *fl, u_int8_t family)
 #if IS_ENABLED(CONFIG_NF_CONNTRACK)
 #include <linux/netfilter/nf_conntrack_zones_common.h>
 
-void nf_ct_attach(struct sk_buff *, const struct sk_buff *);
+void nf_ct_attach(struct sk_buff *, const struct sk_buff *, u8 l4proto);
 void nf_ct_set_closing(struct nf_conntrack *nfct);
 struct nf_conntrack_tuple;
 bool nf_ct_get_tuple_skb(struct nf_conntrack_tuple *dst_tuple,
 			 const struct sk_buff *skb);
 #else
-static inline void nf_ct_attach(struct sk_buff *new, struct sk_buff *skb) {}
+static inline void nf_ct_attach(struct sk_buff *new, struct sk_buff *skb,
+				u8 l4proto) {}
 static inline void nf_ct_set_closing(struct nf_conntrack *nfct) {}
 struct nf_conntrack_tuple;
 static inline bool nf_ct_get_tuple_skb(struct nf_conntrack_tuple *dst_tuple,
@@ -478,7 +479,8 @@ struct nf_ct_hook {
 	void (*destroy)(struct nf_conntrack *);
 	bool (*get_tuple_skb)(struct nf_conntrack_tuple *,
 			      const struct sk_buff *);
-	void (*attach)(struct sk_buff *nskb, const struct sk_buff *skb);
+	void (*attach)(struct sk_buff *nskb, const struct sk_buff *skb,
+		       u8 l4proto);
 	void (*set_closing)(struct nf_conntrack *nfct);
 	int (*confirm)(struct sk_buff *skb);
 	u32 (*get_id)(const struct nf_conntrack *nfct);
diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
index 90c0e22c2..e01e32941 100644
--- a/net/ipv4/icmp.c
+++ b/net/ipv4/icmp.c
@@ -375,7 +375,7 @@ static int icmp_glue_bits(void *from, char *to, int offset, int len, int odd,
 	if (icmp_param->data.icmph.type <= NR_ICMP_TYPES &&
 	    icmp_pointers[array_index_nospec(icmp_param->data.icmph.type,
 					     NR_ICMP_TYPES + 1)].error)
-		nf_ct_attach(skb, icmp_param->skb);
+		nf_ct_attach(skb, icmp_param->skb, IPPROTO_ICMP);
 	return 0;
 }
 
diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c
index 7cb33af67..2b7251b4a 100644
--- a/net/ipv4/netfilter/nf_reject_ipv4.c
+++ b/net/ipv4/netfilter/nf_reject_ipv4.c
@@ -308,7 +308,7 @@ void nf_send_reset(struct net *net, struct sock *sk, struct sk_buff *oldskb,
 	if (nskb->len > dst4_mtu(skb_dst(nskb)))
 		goto free_nskb;
 
-	nf_ct_attach(nskb, oldskb);
+	nf_ct_attach(nskb, oldskb, IPPROTO_TCP);
 	nf_ct_set_closing(skb_nfct(oldskb));
 
 #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c
index a95b03518..f95800d54 100644
--- a/net/ipv6/icmp.c
+++ b/net/ipv6/icmp.c
@@ -327,7 +327,7 @@ static int icmpv6_getfrag(void *from, char *to, int offset, int len, int odd, st
 				      to, len);
 	skb->csum = csum_block_add(skb->csum, csum, odd);
 	if (!(msg->type & ICMPV6_INFOMSG_MASK))
-		nf_ct_attach(skb, org_skb);
+		nf_ct_attach(skb, org_skb, IPPROTO_ICMP);
 	return 0;
 }
 
diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c
index 5e08d8529..7c0035964 100644
--- a/net/ipv6/netfilter/nf_reject_ipv6.c
+++ b/net/ipv6/netfilter/nf_reject_ipv6.c
@@ -377,7 +377,7 @@ void nf_send_reset6(struct net *net, struct sock *sk, struct sk_buff *oldskb,
 	nf_reject_ip6hdr_put(nskb, oldskb, IPPROTO_TCP, ip6_dst_hoplimit(dst));
 	nf_reject_ip6_tcphdr_put(nskb, oldskb, otcph, otcplen);
 
-	nf_ct_attach(nskb, oldskb);
+	nf_ct_attach(nskb, oldskb, IPPROTO_TCP);
 	nf_ct_set_closing(skb_nfct(oldskb));
 
 #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
diff --git a/net/netfilter/core.c b/net/netfilter/core.c
index 44c856c1e..a6ac17237 100644
--- a/net/netfilter/core.c
+++ b/net/netfilter/core.c
@@ -702,7 +702,7 @@ EXPORT_SYMBOL_GPL(nf_nat_hook);
  * tracking in use: without this, connection may not be in hash table, and hence
  * manufactured ICMP or RST packets will not be associated with it.
  */
-void nf_ct_attach(struct sk_buff *new, const struct sk_buff *skb)
+void nf_ct_attach(struct sk_buff *new, const struct sk_buff *skb, u8 l4proto)
 {
 	const struct nf_ct_hook *ct_hook;
 
@@ -710,7 +710,7 @@ void nf_ct_attach(struct sk_buff *new, const struct sk_buff *skb)
 		rcu_read_lock();
 		ct_hook = rcu_dereference(nf_ct_hook);
 		if (ct_hook)
-			ct_hook->attach(new, skb);
+			ct_hook->attach(new, skb, l4proto);
 		rcu_read_unlock();
 	}
 }
diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
index 931b76aac..5cb068e14 100644
--- a/net/netfilter/nf_conntrack_core.c
+++ b/net/netfilter/nf_conntrack_core.c
@@ -2178,17 +2178,32 @@ EXPORT_SYMBOL_GPL(nf_ct_port_nlattr_tuple_size);
 #endif
 
 /* Used by ipt_REJECT and ip6t_REJECT. */
-static void nf_conntrack_attach(struct sk_buff *nskb, const struct sk_buff *skb)
+static void nf_conntrack_attach(struct sk_buff *nskb, const struct sk_buff *skb,
+				u8 l4proto)
 {
 	struct nf_conn *ct;
 	enum ip_conntrack_info ctinfo;
 
-	/* This ICMP is in reverse direction to the packet which caused it */
 	ct = nf_ct_get(skb, &ctinfo);
-	if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL)
-		ctinfo = IP_CT_RELATED_REPLY;
-	else
-		ctinfo = IP_CT_RELATED;
+	if (l4proto == IPPROTO_TCP) {
+		/*
+		 * A reset we generate for an existing TCP connection belongs
+		 * to that connection, it is not an error related to it, so it
+		 * carries the state of the connection.  Attaching it as
+		 * related would let it take a fresh NAT mapping on its way
+		 * out, i.e. rewrite the reset.
+		 */
+		if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL)
+			ctinfo = IP_CT_ESTABLISHED_REPLY;
+		else
+			ctinfo = IP_CT_ESTABLISHED;
+	} else {
+		/* Errors are in reverse direction to the packet that caused them. */
+		if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL)
+			ctinfo = IP_CT_RELATED_REPLY;
+		else
+			ctinfo = IP_CT_RELATED;
+	}
 
 	/* Attach to new skbuff, and increment count */
 	nf_ct_set(nskb, ct, ctinfo);
-- 
2.43.0




^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-11-10  2:40 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-06  6:56 [PATCH nf] netfilter: xt_nat: allow IP_CT_RELATED_REPLY in DNAT targets xujiahao
2026-11-06  7:32 ` Florian Westphal
2026-11-10  2:18 ` [PATCH nf v2] netfilter: conntrack: attach generated TCP resets as ESTABLISHED xujiahao

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox