Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next 0/9] Netfilter updates for net-next
@ 2026-07-31 15:33 Pablo Neira Ayuso
  2026-07-31 15:33 ` [PATCH net-next 1/9] netfilter: conncount: normalize tuple and zone on successful ct lookup Pablo Neira Ayuso
                   ` (9 more replies)
  0 siblings, 10 replies; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-31 15:33 UTC (permalink / raw)
  To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms

Hi,

The following patchset contains Netfilter updates for net-next:

1) Update conncount to use the original tuple after ct lookup to ensure
   consistent counting, from Fernando F. Mancera.

2) Remove redundant net_device field in info structure that helps
   parse the flowtable path discovery.

3) Move net_device to flowtable check to the flowtable discovery
   path parser. This is preparation work to pass the tunnel dst_entry
   via .fill_forward_path.

4) Update DSA .fill_forward_path to break at the user DSA, since
   the conduit DSA is not used in the datapath. This slighly simplifies
   the flowtable path discovery parser.

5) Do not advance index in the path stack prematurely, otherwise
   it points to uninitialized slots on error. Not an issue currently
   but it could be once tunnel dst_entry is passed via .fill_forward_path.

6) Pass the tunnel dst_entry via dev_fill_forward_path().

7) Update ipip and ip6ip6 tunnels to pass the dst_entry through
   dev_fill_forward_path().

8) Call skb_valid_dst() before accessing skb_dst() to ensure dst_entry
   is not a template.

9) Use UNACK timeout when RST packet does not match the expected
   window while in ESTABLISHED state, the existing approach the CLOSE
   state timeout which is only 10 seconds. Adopt a more conservative
   timeout by default for this case.

Please, pull these changes from:

  git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next.git nf-next-26-07-31

Thanks.

----------------------------------------------------------------

The following changes since commit 2fbade66245059c78daeaccfce13ecf499fffb51:

  Merge branch 'net-mctp-usb-add-support-for-mctp-over-usb-v1-1' (2026-07-30 16:55:02 -0700)

are available in the Git repository at:

  git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next.git tags/nf-next-26-07-31

for you to fetch changes up to bf80e6802273a900311b44e47c9b1a59c6d2473c:

  netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets (2026-07-31 14:47:03 +0200)

----------------------------------------------------------------
netfilter pull request 26-07-31

----------------------------------------------------------------
Fernando Fernandez Mancera (1):
      netfilter: conncount: normalize tuple and zone on successful ct lookup

Minghao Zhang (1):
      netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets

Pablo Neira Ayuso (7):
      netfilter: flowtable: consolidate net_device field in nft_forward_info struct
      netfilter: flowtable: consolidate flowtable device check
      net: dsa: stop at the user device in .fill_forward_path
      net: do not advance stack index from dev_fwd_path()
      net: pass dst via net_device_path in dev_fill_forward_path()
      netfilter: flowtable: release tunnel route on error when building forward path
      netfilter: nf_tables: call skb_valid_dst() before skb_dst()

 drivers/net/ethernet/airoha/airoha_ppe.c        |  10 ++-
 drivers/net/ethernet/mediatek/mtk_ppe_offload.c |  10 ++-
 include/linux/netdevice.h                       |   2 +
 net/core/dev.c                                  |  44 +++++++---
 net/dsa/user.c                                  |   3 +-
 net/ipv4/ipip.c                                 |   2 +-
 net/ipv4/netfilter/nf_reject_ipv4.c             |   6 +-
 net/ipv6/ip6_tunnel.c                           |   4 +-
 net/ipv6/netfilter/nf_reject_ipv6.c             |   8 +-
 net/netfilter/nf_conncount.c                    |   2 +
 net/netfilter/nf_conntrack_proto_tcp.c          |   5 +-
 net/netfilter/nf_flow_table_path.c              | 103 ++++++++----------------
 net/netfilter/nft_meta.c                        |   6 +-
 net/netfilter/nft_rt.c                          |   6 +-
 net/netfilter/nft_xfrm.c                        |   9 ++-
 15 files changed, 121 insertions(+), 99 deletions(-)

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

* [PATCH net-next 1/9] netfilter: conncount: normalize tuple and zone on successful ct lookup
  2026-07-31 15:33 [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
@ 2026-07-31 15:33 ` Pablo Neira Ayuso
  2026-07-31 15:33 ` [PATCH net-next 2/9] netfilter: flowtable: consolidate net_device field in nft_forward_info struct Pablo Neira Ayuso
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-31 15:33 UTC (permalink / raw)
  To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms

From: Fernando Fernandez Mancera <fmancera@suse.de>

When get_ct_or_tuple_from_skb() falls back to looking for a connection
via nf_conntrack_find_get(), a successful lookup sets ct but leaves
tuple and zone unupdated.

If the packet belongs to a reply flow, tuple will remain in the reply
direction. As conncount relies on the original direction tuple to count
the connections consistenly, passing an unnormalized reply tuple could
lead to problems.

Fix this by making sure that tuple and zone are normalized.

Suggested-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 net/netfilter/nf_conncount.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c
index e9ea6d9466e7..85487f92af50 100644
--- a/net/netfilter/nf_conncount.c
+++ b/net/netfilter/nf_conncount.c
@@ -158,6 +158,8 @@ static bool get_ct_or_tuple_from_skb(struct net *net,
 		return true;
 
 	found_ct = nf_ct_tuplehash_to_ctrack(h);
+	*tuple = found_ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
+	*zone = nf_ct_zone(found_ct);
 	*refcounted = true;
 	*ct = found_ct;
 
-- 
2.47.3


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

* [PATCH net-next 2/9] netfilter: flowtable: consolidate net_device field in nft_forward_info struct
  2026-07-31 15:33 [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
  2026-07-31 15:33 ` [PATCH net-next 1/9] netfilter: conncount: normalize tuple and zone on successful ct lookup Pablo Neira Ayuso
@ 2026-07-31 15:33 ` Pablo Neira Ayuso
  2026-07-31 15:33 ` [PATCH net-next 3/9] netfilter: flowtable: consolidate flowtable device check Pablo Neira Ayuso
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-31 15:33 UTC (permalink / raw)
  To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms

info->indev and info->outdev refer to the same device, a single
info->dev field is sufficient.

While at it, remove unused router parameter from the flowtable path
discovery function.

Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 net/netfilter/nf_flow_table_path.c | 20 ++++++++------------
 1 file changed, 8 insertions(+), 12 deletions(-)

diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index 98c03b487f52..261bb44d08eb 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -42,8 +42,7 @@ static bool nft_is_valid_ether_device(const struct net_device *dev)
 	return true;
 }
 
-static int nft_dev_fill_forward_path(const struct nf_flow_route *route,
-				     const struct dst_entry *dst_cache,
+static int nft_dev_fill_forward_path(const struct dst_entry *dst_cache,
 				     const struct nf_conn *ct,
 				     enum ip_conntrack_dir dir, u8 *ha,
 				     struct net_device_path_stack *stack)
@@ -76,8 +75,7 @@ static int nft_dev_fill_forward_path(const struct nf_flow_route *route,
 }
 
 struct nft_forward_info {
-	const struct net_device *indev;
-	const struct net_device *outdev;
+	const struct net_device *dev;
 	struct id {
 		__u16	id;
 		__be16	proto;
@@ -109,7 +107,7 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
 		case DEV_PATH_VLAN:
 		case DEV_PATH_PPPOE:
 		case DEV_PATH_TUN:
-			info->indev = path->dev;
+			info->dev = path->dev;
 			if (is_zero_ether_addr(info->h_source))
 				memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
 
@@ -179,10 +177,9 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
 			return -1;
 		}
 	}
-	info->outdev = info->indev;
 
 	if (nf_flowtable_hw_offload(flowtable) &&
-	    nft_is_valid_ether_device(info->indev))
+	    nft_is_valid_ether_device(info->dev))
 		info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
 
 	return 0;
@@ -255,17 +252,16 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
 	unsigned char ha[ETH_ALEN];
 	int i;
 
-	if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) < 0 ||
+	if (nft_dev_fill_forward_path(dst, ct, dir, ha, &stack) < 0 ||
 	    nft_dev_path_info(&stack, &info, ha, &ft->data) < 0)
 		return -ENOENT;
 
-	if (!nft_flowtable_find_dev(info.indev, ft))
+	if (!nft_flowtable_find_dev(info.dev, ft))
 		return -ENOENT;
 
-	if (info.outdev)
-		route->tuple[dir].out.ifindex = info.outdev->ifindex;
+	route->tuple[!dir].in.ifindex = info.dev->ifindex;
+	route->tuple[dir].out.ifindex = info.dev->ifindex;
 
-	route->tuple[!dir].in.ifindex = info.indev->ifindex;
 	for (i = 0; i < info.num_encaps; i++) {
 		route->tuple[!dir].in.encap[i].id = info.encap[i].id;
 		route->tuple[!dir].in.encap[i].proto = info.encap[i].proto;
-- 
2.47.3


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

* [PATCH net-next 3/9] netfilter: flowtable: consolidate flowtable device check
  2026-07-31 15:33 [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
  2026-07-31 15:33 ` [PATCH net-next 1/9] netfilter: conncount: normalize tuple and zone on successful ct lookup Pablo Neira Ayuso
  2026-07-31 15:33 ` [PATCH net-next 2/9] netfilter: flowtable: consolidate net_device field in nft_forward_info struct Pablo Neira Ayuso
@ 2026-07-31 15:33 ` Pablo Neira Ayuso
  2026-07-31 15:33 ` [PATCH net-next 4/9] net: dsa: stop at the user device in .fill_forward_path Pablo Neira Ayuso
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-31 15:33 UTC (permalink / raw)
  To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms

Check that device belongs to the flowtable right after the flowtable
discovery path. This is a preparation patch to obtain the dst entry
from the .fill_forward_path in tunnels.

No functional changes are intended.

Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 net/netfilter/nf_flow_table_path.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index 261bb44d08eb..8f04a4487897 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -90,9 +90,12 @@ struct nft_forward_info {
 	enum flow_offload_xmit_type xmit_type;
 };
 
+static bool nft_flowtable_find_dev(const struct net_device *dev,
+				   struct nft_flowtable *ft);
+
 static int nft_dev_path_info(const struct net_device_path_stack *stack,
 			     struct nft_forward_info *info,
-			     unsigned char *ha, struct nf_flowtable *flowtable)
+			     unsigned char *ha, struct nft_flowtable *ft)
 {
 	const struct net_device_path *path;
 	int i;
@@ -178,10 +181,13 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
 		}
 	}
 
-	if (nf_flowtable_hw_offload(flowtable) &&
+	if (nf_flowtable_hw_offload(&ft->data) &&
 	    nft_is_valid_ether_device(info->dev))
 		info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
 
+	if (!nft_flowtable_find_dev(info->dev, ft))
+		return -1;
+
 	return 0;
 }
 
@@ -253,10 +259,7 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
 	int i;
 
 	if (nft_dev_fill_forward_path(dst, ct, dir, ha, &stack) < 0 ||
-	    nft_dev_path_info(&stack, &info, ha, &ft->data) < 0)
-		return -ENOENT;
-
-	if (!nft_flowtable_find_dev(info.dev, ft))
+	    nft_dev_path_info(&stack, &info, ha, ft) < 0)
 		return -ENOENT;
 
 	route->tuple[!dir].in.ifindex = info.dev->ifindex;
-- 
2.47.3


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

* [PATCH net-next 4/9] net: dsa: stop at the user device in .fill_forward_path
  2026-07-31 15:33 [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
                   ` (2 preceding siblings ...)
  2026-07-31 15:33 ` [PATCH net-next 3/9] netfilter: flowtable: consolidate flowtable device check Pablo Neira Ayuso
@ 2026-07-31 15:33 ` Pablo Neira Ayuso
  2026-07-31 15:33 ` [PATCH net-next 5/9] net: do not advance stack index from dev_fwd_path() Pablo Neira Ayuso
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-31 15:33 UTC (permalink / raw)
  To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms

The flowtable path discovery stops at the DSA user device when setting
up the forward path. Let's just report there is no more devices after
the DSA user port through the .fill_forward_path interface.

No functional changes are intended.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 net/dsa/user.c                     | 3 +--
 net/netfilter/nf_flow_table_path.c | 7 ++-----
 2 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/net/dsa/user.c b/net/dsa/user.c
index 03c7af6abe18..4065c6ee6fc6 100644
--- a/net/dsa/user.c
+++ b/net/dsa/user.c
@@ -2547,14 +2547,13 @@ static int dsa_user_fill_forward_path(struct net_device_path_ctx *ctx,
 				      struct net_device_path *path)
 {
 	struct dsa_port *dp = dsa_user_to_port(ctx->dev);
-	struct net_device *conduit = dsa_port_to_conduit(dp);
 	struct dsa_port *cpu_dp = dp->cpu_dp;
 
 	path->dev = ctx->dev;
 	path->type = DEV_PATH_DSA;
 	path->dsa.proto = cpu_dp->tag_ops->proto;
 	path->dsa.port = dp->index;
-	ctx->dev = conduit;
+	ctx->dev = NULL;
 
 	return 0;
 }
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index 8f04a4487897..004dc75ac357 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -114,12 +114,9 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
 			if (is_zero_ether_addr(info->h_source))
 				memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
 
-			if (path->type == DEV_PATH_ETHERNET)
+			if (path->type == DEV_PATH_ETHERNET ||
+			    path->type == DEV_PATH_DSA)
 				break;
-			if (path->type == DEV_PATH_DSA) {
-				i = stack->num_paths;
-				break;
-			}
 
 			/* DEV_PATH_VLAN, DEV_PATH_PPPOE and DEV_PATH_TUN */
 			if (path->type == DEV_PATH_TUN) {
-- 
2.47.3


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

* [PATCH net-next 5/9] net: do not advance stack index from dev_fwd_path()
  2026-07-31 15:33 [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
                   ` (3 preceding siblings ...)
  2026-07-31 15:33 ` [PATCH net-next 4/9] net: dsa: stop at the user device in .fill_forward_path Pablo Neira Ayuso
@ 2026-07-31 15:33 ` Pablo Neira Ayuso
  2026-07-31 15:33 ` [PATCH net-next 6/9] net: pass dst via net_device_path in dev_fill_forward_path() Pablo Neira Ayuso
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-31 15:33 UTC (permalink / raw)
  To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms

Update stack index from dev_fill_forward_path() instead, once the
forward path slot has been populated.

Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 net/core/dev.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/net/core/dev.c b/net/core/dev.c
index c1c1be1a6962..429a55fff667 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -742,12 +742,10 @@ EXPORT_SYMBOL_GPL(dev_fill_metadata_dst);
 
 static struct net_device_path *dev_fwd_path(struct net_device_path_stack *stack)
 {
-	int k = stack->num_paths++;
-
-	if (k >= NET_DEVICE_PATH_STACK_MAX)
+	if (stack->num_paths + 1 > NET_DEVICE_PATH_STACK_MAX)
 		return NULL;
 
-	return &stack->path[k];
+	return &stack->path[stack->num_paths];
 }
 
 int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
@@ -773,6 +771,7 @@ int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
 		if (ret < 0)
 			return -1;
 
+		stack->num_paths++;
 		if (WARN_ON_ONCE(last_dev == ctx.dev))
 			return -1;
 	}
@@ -785,6 +784,7 @@ int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
 		return -1;
 	path->type = DEV_PATH_ETHERNET;
 	path->dev = ctx.dev;
+	stack->num_paths++;
 
 	return ret;
 }
-- 
2.47.3


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

* [PATCH net-next 6/9] net: pass dst via net_device_path in dev_fill_forward_path()
  2026-07-31 15:33 [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
                   ` (4 preceding siblings ...)
  2026-07-31 15:33 ` [PATCH net-next 5/9] net: do not advance stack index from dev_fwd_path() Pablo Neira Ayuso
@ 2026-07-31 15:33 ` Pablo Neira Ayuso
  2026-07-31 15:34 ` [PATCH net-next 7/9] netfilter: flowtable: release tunnel route on error when building forward path Pablo Neira Ayuso
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-31 15:33 UTC (permalink / raw)
  To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms

Add dst_entry to tunnel device path, this will allow us to remove
a duplicated route lookup.

This is a preparation patch to retrieve the tunnel route directly
from the .fill_forward_path. This new dst_entry in the tunnel will be
used by a follow up patch.

Since dst_release() works fine on NULL interface, this is still
noop until the flowtable starts using this.

Add a new dev_fill_forward_path_release() function to drop the refcount
on the tunnel device route and use it in case of error out. Export it so
to drop the refcount on the tunnel route at a later stage.

Adjust existing drivers that recycle dev_fill_forward_path() to call
dev_fill_forward_path_release() for safety reasons.

Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 drivers/net/ethernet/airoha/airoha_ppe.c      | 10 ++++--
 .../net/ethernet/mediatek/mtk_ppe_offload.c   | 10 ++++--
 include/linux/netdevice.h                     |  2 ++
 net/core/dev.c                                | 36 ++++++++++++++++---
 4 files changed, 47 insertions(+), 11 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 33ddf0d07855..a03af9750573 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -296,14 +296,18 @@ static int airoha_ppe_get_wdma_info(struct net_device *dev, const u8 *addr,
 		return err;
 
 	path = &stack.path[stack.num_paths - 1];
-	if (path->type != DEV_PATH_MTK_WDMA)
-		return -EINVAL;
+	if (path->type != DEV_PATH_MTK_WDMA) {
+		err = -EINVAL;
+		goto err_out;
+	}
 
 	info->idx = path->mtk_wdma.wdma_idx;
 	info->bss = path->mtk_wdma.bss;
 	info->wcid = path->mtk_wdma.wcid;
+err_out:
+	dev_fill_forward_path_release(&stack);
 
-	return 0;
+	return err;
 }
 
 static int airoha_get_dsa_port(struct net_device **dev)
diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
index cc8c4ef8038f..771d9118f94a 100644
--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
@@ -108,16 +108,20 @@ mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_i
 		return err;
 
 	path = &stack.path[stack.num_paths - 1];
-	if (path->type != DEV_PATH_MTK_WDMA)
-		return -1;
+	if (path->type != DEV_PATH_MTK_WDMA) {
+		err = -EINVAL;
+		goto err_out;
+	}
 
 	info->wdma_idx = path->mtk_wdma.wdma_idx;
 	info->queue = path->mtk_wdma.queue;
 	info->bss = path->mtk_wdma.bss;
 	info->wcid = path->mtk_wdma.wcid;
 	info->amsdu = path->mtk_wdma.amsdu;
+err_out:
+	dev_fill_forward_path_release(&stack);
 
-	return 0;
+	return err;
 }
 
 
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 8db25b79573e..62cfad7e6b79 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -892,6 +892,7 @@ struct net_device_path {
 			u8		h_dest[ETH_ALEN];
 		} encap;
 		struct {
+			struct dst_entry *dst;
 			union {
 				struct in_addr	src_v4;
 				struct in6_addr	src_v6;
@@ -3427,6 +3428,7 @@ int dev_get_iflink(const struct net_device *dev);
 int dev_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb);
 int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
 			  struct net_device_path_stack *stack);
+void dev_fill_forward_path_release(struct net_device_path_stack *stack);
 struct net_device *dev_get_by_name(struct net *net, const char *name);
 struct net_device *dev_get_by_name_rcu(struct net *net, const char *name);
 struct net_device *__dev_get_by_name(struct net *net, const char *name);
diff --git a/net/core/dev.c b/net/core/dev.c
index 429a55fff667..e50ed677de72 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -748,6 +748,27 @@ static struct net_device_path *dev_fwd_path(struct net_device_path_stack *stack)
 	return &stack->path[stack->num_paths];
 }
 
+void dev_fill_forward_path_release(struct net_device_path_stack *stack)
+{
+	struct net_device_path *path;
+	int k;
+
+	if (stack->num_paths == 0)
+		return;
+
+	for (k = stack->num_paths - 1; k >= 0; k--) {
+		path = &stack->path[k];
+		switch (path->type) {
+		case DEV_PATH_TUN:
+			dst_release(path->tun.dst);
+			break;
+		default:
+			break;
+		}
+	}
+}
+EXPORT_SYMBOL_GPL(dev_fill_forward_path_release);
+
 int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
 			  struct net_device_path_stack *stack)
 {
@@ -764,16 +785,16 @@ int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
 		last_dev = ctx.dev;
 		path = dev_fwd_path(stack);
 		if (!path)
-			return -1;
+			goto err_out;
 
 		memset(path, 0, sizeof(struct net_device_path));
 		ret = ctx.dev->netdev_ops->ndo_fill_forward_path(&ctx, path);
 		if (ret < 0)
-			return -1;
+			goto err_out;
 
 		stack->num_paths++;
 		if (WARN_ON_ONCE(last_dev == ctx.dev))
-			return -1;
+			goto err_out;
 	}
 
 	if (!ctx.dev)
@@ -781,12 +802,17 @@ int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
 
 	path = dev_fwd_path(stack);
 	if (!path)
-		return -1;
+		goto err_out;
+
 	path->type = DEV_PATH_ETHERNET;
 	path->dev = ctx.dev;
 	stack->num_paths++;
 
-	return ret;
+	return 0;
+err_out:
+	dev_fill_forward_path_release(stack);
+
+	return -1;
 }
 EXPORT_SYMBOL_GPL(dev_fill_forward_path);
 
-- 
2.47.3


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

* [PATCH net-next 7/9] netfilter: flowtable: release tunnel route on error when building forward path
  2026-07-31 15:33 [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
                   ` (5 preceding siblings ...)
  2026-07-31 15:33 ` [PATCH net-next 6/9] net: pass dst via net_device_path in dev_fill_forward_path() Pablo Neira Ayuso
@ 2026-07-31 15:34 ` Pablo Neira Ayuso
  2026-08-05  0:19   ` Jakub Kicinski
  2026-07-31 15:34 ` [PATCH net-next 8/9] netfilter: nf_tables: call skb_valid_dst() before skb_dst() Pablo Neira Ayuso
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-31 15:34 UTC (permalink / raw)
  To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms

nft_flow_tunnel_update_route() can lazy fail, leaving an incomplete
forward path set ip. The route lookup also happens twice, once from
dev_fill_forward_path() and again in this aforementioned function.

Update ipip and ip6ip6 not to release the dst_entry and pass it on
via the tunnel forward path information.

In case of failure when setting up the forwarding path, release the
tunnel dst that was provided via dev_fill_forward_path().

Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 net/ipv4/ipip.c                    |  2 +-
 net/ipv6/ip6_tunnel.c              |  4 +-
 net/netfilter/nf_flow_table_path.c | 65 ++++++++----------------------
 3 files changed, 21 insertions(+), 50 deletions(-)

diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c
index 0831f6b81717..fb7d96f99b06 100644
--- a/net/ipv4/ipip.c
+++ b/net/ipv4/ipip.c
@@ -376,10 +376,10 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx,
 	path->tun.src_v4.s_addr = tiph->saddr;
 	path->tun.dst_v4.s_addr = tiph->daddr;
 	path->tun.l3_proto = IPPROTO_IPIP;
+	path->tun.dst = &rt->dst;
 	path->dev = ctx->dev;
 
 	ctx->dev = rt->dst.dev;
-	ip_rt_put(rt);
 
 	return 0;
 }
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index 97c3f61d627b..d80020bc2620 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -1870,12 +1870,14 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx,
 		path->tun.src_v6 = fl6.saddr;
 		path->tun.dst_v6 = fl6.daddr;
 		path->tun.l3_proto = IPPROTO_IPV6;
+		path->tun.dst = dst;
 		path->dev = ctx->dev;
 		ctx->dev = dst->dev;
 	}
 
 	err = dst->error;
-	dst_release(dst);
+	if (err)
+		dst_release(dst);
 
 	return err;
 }
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index 004dc75ac357..56219b02e122 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -82,6 +82,7 @@ struct nft_forward_info {
 	} encap[NF_FLOW_TABLE_ENCAP_MAX];
 	u8 num_encaps;
 	struct flow_offload_tunnel tun;
+	struct dst_entry *tun_dst;
 	u8 num_tuns;
 	u8 ingress_vlans;
 	u8 h_source[ETH_ALEN];
@@ -93,7 +94,7 @@ struct nft_forward_info {
 static bool nft_flowtable_find_dev(const struct net_device *dev,
 				   struct nft_flowtable *ft);
 
-static int nft_dev_path_info(const struct net_device_path_stack *stack,
+static int nft_dev_path_info(struct net_device_path_stack *stack,
 			     struct nft_forward_info *info,
 			     unsigned char *ha, struct nft_flowtable *ft)
 {
@@ -121,15 +122,16 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
 			/* DEV_PATH_VLAN, DEV_PATH_PPPOE and DEV_PATH_TUN */
 			if (path->type == DEV_PATH_TUN) {
 				if (info->num_tuns)
-					return -1;
+					goto err_out;
 
 				info->tun.src_v6 = path->tun.src_v6;
 				info->tun.dst_v6 = path->tun.dst_v6;
 				info->tun.l3_proto = path->tun.l3_proto;
+				info->tun_dst = path->tun.dst;
 				info->num_tuns++;
 			} else {
 				if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX)
-					return -1;
+					goto err_out;
 
 				info->encap[info->num_encaps].id =
 					path->encap.id;
@@ -150,13 +152,13 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
 			switch (path->bridge.vlan_mode) {
 			case DEV_PATH_BR_VLAN_UNTAG_HW:
 				if (info->num_encaps == 0)
-					return -1;
+					goto err_out;
 
 				info->ingress_vlans |= BIT(info->num_encaps - 1);
 				break;
 			case DEV_PATH_BR_VLAN_TAG:
 				if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX)
-					return -1;
+					goto err_out;
 
 				info->encap[info->num_encaps].id = path->bridge.vlan_id;
 				info->encap[info->num_encaps].proto = path->bridge.vlan_proto;
@@ -164,7 +166,7 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
 				break;
 			case DEV_PATH_BR_VLAN_UNTAG:
 				if (info->num_encaps == 0)
-					return -1;
+					goto err_out;
 
 				info->num_encaps--;
 				break;
@@ -174,7 +176,7 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
 			info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
 			break;
 		default:
-			return -1;
+			goto err_out;
 		}
 	}
 
@@ -183,9 +185,13 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
 		info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
 
 	if (!nft_flowtable_find_dev(info->dev, ft))
-		return -1;
+		goto err_out;
 
 	return 0;
+err_out:
+	dev_fill_forward_path_release(stack);
+
+	return -1;
 }
 
 static bool nft_flowtable_find_dev(const struct net_device *dev,
@@ -205,44 +211,6 @@ static bool nft_flowtable_find_dev(const struct net_device *dev,
 	return found;
 }
 
-static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt,
-					struct flow_offload_tunnel *tun,
-					struct nf_flow_route *route,
-					enum ip_conntrack_dir dir)
-{
-	struct dst_entry *cur_dst = route->tuple[dir].dst;
-	struct dst_entry *tun_dst = NULL;
-	struct flowi fl = {};
-
-	switch (nft_pf(pkt)) {
-	case NFPROTO_IPV4:
-		fl.u.ip4.daddr = tun->dst_v4.s_addr;
-		fl.u.ip4.saddr = tun->src_v4.s_addr;
-		fl.u.ip4.flowi4_iif = nft_in(pkt)->ifindex;
-		fl.u.ip4.flowi4_dscp = ip4h_dscp(ip_hdr(pkt->skb));
-		fl.u.ip4.flowi4_mark = pkt->skb->mark;
-		fl.u.ip4.flowi4_flags = FLOWI_FLAG_ANYSRC;
-		break;
-	case NFPROTO_IPV6:
-		fl.u.ip6.daddr = tun->dst_v6;
-		fl.u.ip6.saddr = tun->src_v6;
-		fl.u.ip6.flowi6_iif = nft_in(pkt)->ifindex;
-		fl.u.ip6.flowlabel = ip6_flowinfo(ipv6_hdr(pkt->skb));
-		fl.u.ip6.flowi6_mark = pkt->skb->mark;
-		fl.u.ip6.flowi6_flags = FLOWI_FLAG_ANYSRC;
-		break;
-	}
-
-	nf_route(nft_net(pkt), &tun_dst, &fl, false, nft_pf(pkt));
-	if (!tun_dst)
-		return -ENOENT;
-
-	route->tuple[dir].dst = tun_dst;
-	dst_release(cur_dst);
-
-	return 0;
-}
-
 static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
 				struct nf_flow_route *route,
 				const struct nf_conn *ct,
@@ -267,12 +235,13 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
 		route->tuple[!dir].in.encap[i].proto = info.encap[i].proto;
 	}
 
-	if (info.num_tuns &&
-	    !nft_flow_tunnel_update_route(pkt, &info.tun, route, dir)) {
+	if (info.num_tuns) {
 		route->tuple[!dir].in.tun.src_v6 = info.tun.dst_v6;
 		route->tuple[!dir].in.tun.dst_v6 = info.tun.src_v6;
 		route->tuple[!dir].in.tun.l3_proto = info.tun.l3_proto;
 		route->tuple[!dir].in.num_tuns = info.num_tuns;
+		dst_release(route->tuple[dir].dst);
+		route->tuple[dir].dst = info.tun_dst;
 	}
 
 	route->tuple[!dir].in.num_encaps = info.num_encaps;
-- 
2.47.3


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

* [PATCH net-next 8/9] netfilter: nf_tables: call skb_valid_dst() before skb_dst()
  2026-07-31 15:33 [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
                   ` (6 preceding siblings ...)
  2026-07-31 15:34 ` [PATCH net-next 7/9] netfilter: flowtable: release tunnel route on error when building forward path Pablo Neira Ayuso
@ 2026-07-31 15:34 ` Pablo Neira Ayuso
  2026-07-31 15:34 ` [PATCH net-next 9/9] netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets Pablo Neira Ayuso
  2026-08-05 17:40 ` [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
  9 siblings, 0 replies; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-31 15:34 UTC (permalink / raw)
  To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms

When fetching the dst_entry from the skb, check if it valid, ie. this is
not a template dst, for extensions that can be used from the netdev
ingress and egress chains.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 net/ipv4/netfilter/nf_reject_ipv4.c | 6 ++++--
 net/ipv6/netfilter/nf_reject_ipv6.c | 8 ++++++--
 net/netfilter/nft_meta.c            | 6 ++++--
 net/netfilter/nft_rt.c              | 6 ++++--
 net/netfilter/nft_xfrm.c            | 9 ++++++++-
 5 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c
index 4626dc46808f..59ec465a9df9 100644
--- a/net/ipv4/netfilter/nf_reject_ipv4.c
+++ b/net/ipv4/netfilter/nf_reject_ipv4.c
@@ -8,6 +8,7 @@
 #include <net/tcp.h>
 #include <net/route.h>
 #include <net/dst.h>
+#include <net/dst_metadata.h>
 #include <net/netfilter/ipv4/nf_reject.h>
 #include <linux/netfilter_ipv4.h>
 #include <linux/netfilter_bridge.h>
@@ -263,6 +264,7 @@ static int nf_reject_fill_skb_dst(struct sk_buff *skb_in)
 	if (!dst)
 		return -1;
 
+	skb_dst_drop(skb_in);
 	skb_dst_set(skb_in, dst);
 	return 0;
 }
@@ -279,7 +281,7 @@ void nf_send_reset(struct net *net, struct sock *sk, struct sk_buff *oldskb,
 	if (!oth)
 		return;
 
-	if (!skb_dst(oldskb) && nf_reject_fill_skb_dst(oldskb) < 0)
+	if (!skb_valid_dst(oldskb) && nf_reject_fill_skb_dst(oldskb) < 0)
 		return;
 
 	if (skb_rtable(oldskb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
@@ -352,7 +354,7 @@ void nf_send_unreach(struct sk_buff *skb_in, int code, int hook)
 	if (iph->frag_off & htons(IP_OFFSET))
 		return;
 
-	if (!skb_dst(skb_in) && nf_reject_fill_skb_dst(skb_in) < 0)
+	if (!skb_valid_dst(skb_in) && nf_reject_fill_skb_dst(skb_in) < 0)
 		return;
 
 	if (skb_csum_unnecessary(skb_in) ||
diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c
index ef5b7e85cffa..07cdaa10da0d 100644
--- a/net/ipv6/netfilter/nf_reject_ipv6.c
+++ b/net/ipv6/netfilter/nf_reject_ipv6.c
@@ -8,6 +8,7 @@
 #include <net/ip6_route.h>
 #include <net/ip6_fib.h>
 #include <net/ip6_checksum.h>
+#include <net/dst_metadata.h>
 #include <net/netfilter/ipv6/nf_reject.h>
 #include <linux/netfilter_ipv6.h>
 #include <linux/netfilter_bridge.h>
@@ -304,6 +305,7 @@ static int nf_reject6_fill_skb_dst(struct sk_buff *skb_in)
 	if (!dst)
 		return -1;
 
+	skb_dst_drop(skb_in);
 	skb_dst_set(skb_in, dst);
 	return 0;
 }
@@ -336,10 +338,12 @@ void nf_send_reset6(struct net *net, struct sock *sk, struct sk_buff *oldskb,
 	fl6.fl6_sport = otcph->dest;
 	fl6.fl6_dport = otcph->source;
 
-	if (!skb_dst(oldskb)) {
+	if (!skb_valid_dst(oldskb)) {
 		nf_ip6_route(net, &dst, flowi6_to_flowi(&fl6), false);
 		if (!dst)
 			return;
+
+		skb_dst_drop(oldskb);
 		skb_dst_set(oldskb, dst);
 	}
 
@@ -440,7 +444,7 @@ void nf_send_unreach6(struct net *net, struct sk_buff *skb_in,
 	if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL)
 		skb_in->dev = net->loopback_dev;
 
-	if (!skb_dst(skb_in) && nf_reject6_fill_skb_dst(skb_in) < 0)
+	if (!skb_valid_dst(skb_in) && nf_reject6_fill_skb_dst(skb_in) < 0)
 		return;
 
 	icmpv6_send(skb_in, ICMPV6_DEST_UNREACH, code, 0);
diff --git a/net/netfilter/nft_meta.c b/net/netfilter/nft_meta.c
index 0a43e0787a68..01cfbaa36525 100644
--- a/net/netfilter/nft_meta.c
+++ b/net/netfilter/nft_meta.c
@@ -20,6 +20,7 @@
 #include <net/dst.h>
 #include <net/ip.h>
 #include <net/sock.h>
+#include <net/dst_metadata.h>
 #include <net/tcp_states.h> /* for TCP_TIME_WAIT */
 #include <net/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables_core.h>
@@ -279,11 +280,12 @@ static bool nft_meta_get_eval_ifname(enum nft_meta_keys key, u32 *dest,
 static noinline bool
 nft_meta_get_eval_rtclassid(const struct sk_buff *skb, u32 *dest)
 {
-	const struct dst_entry *dst = skb_dst(skb);
+	const struct dst_entry *dst;
 
-	if (!dst)
+	if (!skb_valid_dst(skb))
 		return false;
 
+	dst = skb_dst(skb);
 	*dest = dst->tclassid;
 	return true;
 }
diff --git a/net/netfilter/nft_rt.c b/net/netfilter/nft_rt.c
index aeb0094eafd8..841c863a08db 100644
--- a/net/netfilter/nft_rt.c
+++ b/net/netfilter/nft_rt.c
@@ -8,6 +8,7 @@
 #include <linux/netfilter.h>
 #include <linux/netfilter/nf_tables.h>
 #include <net/dst.h>
+#include <net/dst_metadata.h>
 #include <net/ip6_route.h>
 #include <net/route.h>
 #include <net/netfilter/nf_tables.h>
@@ -59,10 +60,11 @@ void nft_rt_get_eval(const struct nft_expr *expr,
 	u32 *dest = &regs->data[priv->dreg];
 	const struct dst_entry *dst;
 
-	dst = skb_dst(skb);
-	if (!dst)
+	if (!skb_valid_dst(skb))
 		goto err;
 
+	dst = skb_dst(skb);
+
 	switch (priv->key) {
 #ifdef CONFIG_IP_ROUTE_CLASSID
 	case NFT_RT_CLASSID:
diff --git a/net/netfilter/nft_xfrm.c b/net/netfilter/nft_xfrm.c
index 8cec43064319..c8bba697f993 100644
--- a/net/netfilter/nft_xfrm.c
+++ b/net/netfilter/nft_xfrm.c
@@ -12,6 +12,7 @@
 #include <linux/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables_core.h>
 #include <net/netfilter/nf_tables.h>
+#include <net/dst_metadata.h>
 #include <linux/in.h>
 #include <net/xfrm.h>
 
@@ -177,9 +178,15 @@ static void nft_xfrm_get_eval_out(const struct nft_xfrm *priv,
 				  struct nft_regs *regs,
 				  const struct nft_pktinfo *pkt)
 {
-	const struct dst_entry *dst = skb_dst(pkt->skb);
+	const struct dst_entry *dst;
 	int i;
 
+	if (!skb_valid_dst(pkt->skb)) {
+		regs->verdict.code = NFT_BREAK;
+		return;
+	}
+
+	dst = skb_dst(pkt->skb);
 	for (i = 0; dst && dst->xfrm;
 	     dst = ((const struct xfrm_dst *)dst)->child, i++) {
 		if (i < priv->spnum)
-- 
2.47.3


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

* [PATCH net-next 9/9] netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets
  2026-07-31 15:33 [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
                   ` (7 preceding siblings ...)
  2026-07-31 15:34 ` [PATCH net-next 8/9] netfilter: nf_tables: call skb_valid_dst() before skb_dst() Pablo Neira Ayuso
@ 2026-07-31 15:34 ` Pablo Neira Ayuso
  2026-08-05  0:19   ` Jakub Kicinski
  2026-08-05 17:40 ` [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
  9 siblings, 1 reply; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-31 15:34 UTC (permalink / raw)
  To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms

From: Minghao Zhang <zhangmh25@mails.tsinghua.edu.cn>

Commit be0502a3f2e9 ("netfilter: conntrack: tcp: only close if RST
matches exact sequence") keeps an established conntrack entry in
ESTABLISHED when an in-window RST does not match the expected sequence
number exactly, so the endpoint can validate the RST with a challenge
ACK.

The timeout selection nevertheless uses the CLOSE timeout for every RST
packet.  The bug is that timeout selection is based on the packet type,
not on the state transition result: even when RST validation keeps
new_state in ESTABLISHED, the timeout is still forced to
TCP_CONNTRACK_CLOSE.

Linux TCP independently rate limits challenge ACKs per socket.  A second
non-exact RST can therefore arrive after the first challenge ACK has
restored the timeout but before the rate limit expires.  The second RST
lowers the timeout to 10 seconds again while the endpoint suppresses the
second challenge ACK, allowing the conntrack entry to expire while both
TCP endpoints remain established.

Using the ESTABLISHED timeout for such RSTs would avoid this short
expiration window, but it could also retain stale entries for the
five-day default because conntrack cannot reliably match the endpoint's
exact TCP state.

Use the UNACK timeout for RST packets that leave the conntrack entry in
TCP_CONNTRACK_ESTABLISHED.  Exact-match RSTs and accepted RST packet
trains still fall through to timeouts[new_state], which preserves the
CLOSE timeout when conntrack accepts the RST as closing the flow.

This avoids the aggressive 10-second expiration window for non-exact
RSTs while preserving the short timeout for RSTs that conntrack accepts
as closing the flow.

Suggested-by: Florian Westphal <fw@strlen.de>
Reported-by: Minghao Zhang <zhangmh25@mails.tsinghua.edu.cn>
Reported-by: Jianjun Chen <jianjun@tsinghua.edu.cn>
Signed-off-by: Minghao Zhang <zhangmh25@mails.tsinghua.edu.cn>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 net/netfilter/nf_conntrack_proto_tcp.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c
index ceeed3d7fe52..723e946a78f4 100644
--- a/net/netfilter/nf_conntrack_proto_tcp.c
+++ b/net/netfilter/nf_conntrack_proto_tcp.c
@@ -1281,8 +1281,9 @@ int nf_conntrack_tcp_packet(struct nf_conn *ct,
 	if (ct->proto.tcp.retrans >= tn->tcp_max_retrans &&
 	    timeouts[new_state] > timeouts[TCP_CONNTRACK_RETRANS])
 		timeout = timeouts[TCP_CONNTRACK_RETRANS];
-	else if (unlikely(index == TCP_RST_SET))
-		timeout = timeouts[TCP_CONNTRACK_CLOSE];
+	else if (unlikely(index == TCP_RST_SET &&
+			  new_state == TCP_CONNTRACK_ESTABLISHED))
+		timeout = timeouts[TCP_CONNTRACK_UNACK];
 	else if ((ct->proto.tcp.seen[0].flags | ct->proto.tcp.seen[1].flags) &
 		 IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED &&
 		 timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK])
-- 
2.47.3


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

* Re: [PATCH net-next 7/9] netfilter: flowtable: release tunnel route on error when building forward path
  2026-07-31 15:34 ` [PATCH net-next 7/9] netfilter: flowtable: release tunnel route on error when building forward path Pablo Neira Ayuso
@ 2026-08-05  0:19   ` Jakub Kicinski
  2026-08-05  8:06     ` Pablo Neira Ayuso
  0 siblings, 1 reply; 15+ messages in thread
From: Jakub Kicinski @ 2026-08-05  0:19 UTC (permalink / raw)
  To: pablo
  Cc: Jakub Kicinski, netfilter-devel, davem, netdev, pabeni, edumazet,
	fw, horms

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
netfilter: flowtable: release tunnel route on error when building forward path

This drops the second route lookup in nft_flow_tunnel_update_route() and
instead passes the dst_entry obtained by dev_fill_forward_path() through
the tunnel forward path information, so ipip and ip6ip6 no longer release
it.  On failure while building the forward path the tunnel dst is released
via dev_fill_forward_path_release().

> diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c
> index 0831f6b81717c..fb7d96f99b06b 100644
> --- a/net/ipv4/ipip.c
> +++ b/net/ipv4/ipip.c
> @@ -376,10 +376,10 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx,
>  	path->tun.src_v4.s_addr = tiph->saddr;
>  	path->tun.dst_v4.s_addr = tiph->daddr;
>  	path->tun.l3_proto = IPPROTO_IPIP;
> +	path->tun.dst = &rt->dst;
>  	path->dev = ctx->dev;
>  
>  	ctx->dev = rt->dst.dev;
> -	ip_rt_put(rt);
>  
>  	return 0;
>  }
> diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
> index 97c3f61d627bb..d80020bc2620e 100644
> --- a/net/ipv6/ip6_tunnel.c
> +++ b/net/ipv6/ip6_tunnel.c
> @@ -1870,12 +1870,14 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx,
>  		path->tun.src_v6 = fl6.saddr;
>  		path->tun.dst_v6 = fl6.daddr;
>  		path->tun.l3_proto = IPPROTO_IPV6;
> +		path->tun.dst = dst;
>  		path->dev = ctx->dev;
>  		ctx->dev = dst->dev;
>  	}
>  
>  	err = dst->error;
> -	dst_release(dst);
> +	if (err)
> +		dst_release(dst);
>  
>  	return err;
>  }
> diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
> index 004dc75ac357a..56219b02e1227 100644
> --- a/net/netfilter/nf_flow_table_path.c
> +++ b/net/netfilter/nf_flow_table_path.c

[ ... ]

> @@ -205,44 +211,6 @@ static bool nft_flowtable_find_dev(const struct net_device *dev,
>  	return found;
>  }
>  
> -static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt,
> -					struct flow_offload_tunnel *tun,
> -					struct nf_flow_route *route,
> -					enum ip_conntrack_dir dir)
> -{
> -	struct dst_entry *cur_dst = route->tuple[dir].dst;
> -	struct dst_entry *tun_dst = NULL;
> -	struct flowi fl = {};
> -
> -	switch (nft_pf(pkt)) {

[ ... ]

> -	nf_route(nft_net(pkt), &tun_dst, &fl, false, nft_pf(pkt));
> -	if (!tun_dst)
> -		return -ENOENT;
> -
> -	route->tuple[dir].dst = tun_dst;
> -	dst_release(cur_dst);
> -
> -	return 0;
> -}
> -
>  static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
>  				struct nf_flow_route *route,
>  				const struct nf_conn *ct,
> @@ -267,12 +235,13 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
>  		route->tuple[!dir].in.encap[i].proto = info.encap[i].proto;
>  	}
>  
> -	if (info.num_tuns &&
> -	    !nft_flow_tunnel_update_route(pkt, &info.tun, route, dir)) {
> +	if (info.num_tuns) {
>  		route->tuple[!dir].in.tun.src_v6 = info.tun.dst_v6;
>  		route->tuple[!dir].in.tun.dst_v6 = info.tun.src_v6;
>  		route->tuple[!dir].in.tun.l3_proto = info.tun.l3_proto;
>  		route->tuple[!dir].in.num_tuns = info.num_tuns;
> +		dst_release(route->tuple[dir].dst);
> +		route->tuple[dir].dst = info.tun_dst;
>  	}

[Severity: High]
Can the dst installed here end up belonging to a different address family
than the flow it is attached to?

The removed nft_flow_tunnel_update_route() derived the replacement dst from
nf_route(nft_net(pkt), &tun_dst, &fl, false, nft_pf(pkt)), so the dst family
always matched the flow's l3proto.  The dst that now arrives in
info.tun_dst follows the tunnel underlay instead:

net/ipv4/ipip.c:ipip_fill_forward_path() always stores an IPv4 rtable:

	path->tun.l3_proto = IPPROTO_IPIP;
	path->tun.dst = &rt->dst;

net/ipv6/ip6_tunnel.c:ip6_tnl_fill_forward_path() always stores an IPv6
rt6_info:

	path->tun.l3_proto = IPPROTO_IPV6;
	path->tun.dst = dst;

Neither handler consults the tunnel payload proto, and neither
nft_dev_path_info() nor nft_dev_forward_path() checks the family before
storing it in route->tuple[dir].dst.

The consumers cast that dst according to the inner flow family.  In
net/netfilter/nf_flow_table_ip.c:nf_flow_offload_ipv6_hook():

	rt = dst_rt6_info(tuplehash->tuple.dst_cache);
	...
	neigh = ip_neigh_gw6(rt->dst.dev, rt6_nexthop(rt, ip6_daddr));

and in nf_flow_offload_ip_hook():

	rt = dst_rtable(tuplehash->tuple.dst_cache);
	... rt_nexthop(rt, ip_daddr) ...

net/netfilter/nf_flow_table_core.c:flow_offload_dst_cookie() does the same:

	if (flow_tuple->l3proto == NFPROTO_IPV6)
		return rt6_get_cookie(dst_rt6_info(flow_tuple->dst_cache));

For an ip6_tunnel in ip4ip6 mode (t->parms.proto == IPPROTO_IPIP) carrying
IPv4 flows, ip6_tnl_fill_forward_path() still reports l3_proto =
IPPROTO_IPV6 and hands out an rt6_info, which nf_flow_offload_ip_hook()
then reads through dst_rtable() and rt_nexthop()/ip_mtu_locked().  That
configuration needs no misconfiguration to reach.

The mirror case is an IPv6 flow routed out an ipip device: an ipip device
with mtu 1480 gets an inet6_dev at NETDEV_REGISTER, so fib6_nh_init()
accepts "ip -6 route add ... dev tunlX", and nft_dev_fill_forward_path()
deliberately skips the ether-device requirement for tunnels, so
ipip_fill_forward_path() hands back an rtable that becomes the dst_cache of
an IPv6 flow.  rt6_nexthop() then reads rt6i_flags and rt6i_gateway past
the end of a struct rtable allocated from the ip_dst_cache slab, and the
result is used as the IPv6 next hop passed to ip_neigh_gw6().

nf_flow_dst_check() does not catch this either: dst->ops->check() for the
stored rtable is ipv4_dst_check(), which only tests rt_is_expired().

Would it make sense to validate info.tun.l3_proto (or the dst's
ops->family) against the flow's l3proto before installing info.tun_dst?

[Severity: Low]
Should this carry a Fixes: tag?

Before this change the tunnel information was only installed when the
second lookup succeeded:

	if (info.num_tuns &&
	    !nft_flow_tunnel_update_route(pkt, &info.tun, route, dir)) {

while route->tuple[!dir].in.ifindex, route->tuple[dir].out.ifindex, the
encaps, h_source/h_dest and xmit_type had already been written and
nft_dev_forward_path() still returned 0.  On nf_route() failure the flow was
offloaded with the underlay device as output but num_tuns == 0, so inner
packets left the underlay without IPIP/IP6IP6 encapsulation, which matches
the "incomplete forward path" described in the commit message.

git blame points that construct at d30301ba4b07ac ("netfilter: flowtable:
tunnel route update"), with the tunnel forward path support itself from
ab427db1788581, so the bug is in released kernels rather than in-flight
code from this series.  A Fixes: d30301ba4b07ac tag would help stable
tracking.

It may also be worth noting in the changelog that this is not standalone
backportable: neither dev_fill_forward_path_release() nor the
net_device_path tun.dst field exist before the earlier "net: pass dst via
net_device_path in dev_fill_forward_path()" commit in this series.

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

* Re: [PATCH net-next 9/9] netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets
  2026-07-31 15:34 ` [PATCH net-next 9/9] netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets Pablo Neira Ayuso
@ 2026-08-05  0:19   ` Jakub Kicinski
  2026-08-05  7:54     ` Pablo Neira Ayuso
  0 siblings, 1 reply; 15+ messages in thread
From: Jakub Kicinski @ 2026-08-05  0:19 UTC (permalink / raw)
  To: pablo
  Cc: Jakub Kicinski, netfilter-devel, davem, netdev, pabeni, edumazet,
	fw, horms

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets

This changes the timeout selection in nf_conntrack_tcp_packet() so that an
RST which leaves the entry in TCP_CONNTRACK_ESTABLISHED (the non-exact
sequence case from be0502a3f2e9) gets timeouts[TCP_CONNTRACK_UNACK] instead
of timeouts[TCP_CONNTRACK_CLOSE].  RSTs that conntrack accepts as closing
the flow are meant to keep using timeouts[new_state].

A few questions below.

>     Use the UNACK timeout for RST packets that leave the conntrack entry in
>     TCP_CONNTRACK_ESTABLISHED.  Exact-match RSTs and accepted RST packet
>     trains still fall through to timeouts[new_state], which preserves the
>     CLOSE timeout when conntrack accepts the RST as closing the flow.

[Severity: Low]
Should this carry a Fixes: tag?  The changelog describes a user-visible
failure (an ESTABLISHED entry expiring after 10 seconds while both TCP
endpoints stay established), and it names be0502a3f2e9 in prose only, so
stable tooling has nothing machine-readable to key on.

There is also a wording tension: be0502a3f2e9's own changelog presented the
CLOSE timeout for non-exact RSTs as intentional ("With patch, timeout is
lowered like CLOSE, but connection stays in ESTABLISHED state" / "If no
challenge ACK is received, the connection will time out after CLOSE
seconds").  Either adding

    Fixes: be0502a3f2e94211 ("netfilter: conntrack: tcp: only close if RST matches exact sequence")

or rewording this as a deliberate policy refinement rather than a bug fix
would make the two consistent.

[Severity: Low]
Is there a test that observes this change?  The only in-tree artifact
covering the inexact in-window RST path,
tools/testing/selftests/net/netfilter/packetdrill/conntrack_rst_invalid.pkt,
checks only the state string:

    // in-window but not exact match
    +0.0 < R	42:42(0)	win 0
    +0 `conntrack -f $NFCT_IP_VERSION -L -p tcp --dport 8080 2>/dev/null |grep -q ESTABLISHED`

The timeout is now the sole observable difference for that scenario, and it
is never inspected.

On top of that, the segment labelled "in-window but not exact match" has seq
before seen[!dir].td_maxack (the peer had acked 3001), so
nf_conntrack_tcp_packet() rejects it at the "invalid rst" check and never
reaches the patched timeout selection.  The test's own counter assertion
agrees:

    // Expect four invalid RSTs
    +0 `$xtables -v -S INPUT  | grep INVALID | grep -q -- "-c 4 "`

So the suite passes identically with and without this change.

> diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c
> index ceeed3d7fe520..723e946a78f40 100644
> --- a/net/netfilter/nf_conntrack_proto_tcp.c
> +++ b/net/netfilter/nf_conntrack_proto_tcp.c
> @@ -1281,8 +1281,9 @@ int nf_conntrack_tcp_packet(struct nf_conn *ct,
>  	if (ct->proto.tcp.retrans >= tn->tcp_max_retrans &&
>  	    timeouts[new_state] > timeouts[TCP_CONNTRACK_RETRANS])
>  		timeout = timeouts[TCP_CONNTRACK_RETRANS];
> -	else if (unlikely(index == TCP_RST_SET))
> -		timeout = timeouts[TCP_CONNTRACK_CLOSE];
> +	else if (unlikely(index == TCP_RST_SET &&
> +			  new_state == TCP_CONNTRACK_ESTABLISHED))
> +		timeout = timeouts[TCP_CONNTRACK_UNACK];

[Severity: Medium]
Can this new branch lengthen an entry's lifetime rather than shorten it?
Every sibling branch in this chain only assigns when the replacement is
smaller, for example the RETRANS branch just above and the UNACK branch just
below:

    if (ct->proto.tcp.retrans >= tn->tcp_max_retrans &&
        timeouts[new_state] > timeouts[TCP_CONNTRACK_RETRANS])
        timeout = timeouts[TCP_CONNTRACK_RETRANS];
    ...
    else if ((ct->proto.tcp.seen[0].flags | ct->proto.tcp.seen[1].flags) &
         IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED &&
         timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK])
        timeout = timeouts[TCP_CONNTRACK_UNACK];

The new branch has no such guard.  Since this branch is only reachable via
"new_state = old_state;" in case TCP_CONNTRACK_CLOSE, new_state is
ESTABLISHED here, so the effective comparison is timeouts[UNACK] against
timeouts[ESTABLISHED].

With the defaults in tcp_timeouts (ESTABLISHED 5 DAYS, UNACK 5 MINS) this is
always a lowering.  But both values are independently tunable with no
cross-validation:

    net/netfilter/nf_conntrack_standalone.c:
    [NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_ESTABLISHED] = {
        .procname	= "nf_conntrack_tcp_timeout_established",
        .maxlen		= sizeof(unsigned int),
        .mode		= 0644,
        .proc_handler	= proc_dointvec_jiffies,
    },

and tcp_timeout_nlattr_to_obj() copies CTA_TIMEOUT_TCP_* verbatim.  So an
administrator lowering nf_conntrack_tcp_timeout_established (120 s is a
common hardening/NAT-appliance setting) inverts the relation.

__nf_ct_refresh_acct() then writes the expiry absolutely rather than
clamping it:

    net/netfilter/nf_conntrack_core.c:__nf_ct_refresh_acct() {
        ...
        if (nf_ct_is_confirmed(ct))
            extra_jiffies += nfct_time_stamp;

        if (READ_ONCE(ct->timeout) != extra_jiffies)
            WRITE_ONCE(ct->timeout, extra_jiffies);
        ...
    }

In that configuration, an in-window RST whose sequence does not match
td_maxack pushes the entry past the configured ESTABLISHED policy, and a
periodic stream of such RSTs can renew it indefinitely, holding entries
against nf_conntrack_max.  Would the usual form keep the intent while
avoiding this?

    timeout = min(timeouts[new_state], timeouts[TCP_CONNTRACK_UNACK]);

or the matching guard:

    else if (unlikely(index == TCP_RST_SET &&
                      new_state == TCP_CONNTRACK_ESTABLISHED) &&
             timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK])

>  	else if ((ct->proto.tcp.seen[0].flags | ct->proto.tcp.seen[1].flags) &
>  		 IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED &&
>  		 timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK])

[Severity: Low]
For an RST that conntrack does accept as closing the flow, is the CLOSE
timeout really preserved?  Previously such packets took the unconditional
timeouts[TCP_CONNTRACK_CLOSE] assignment; now they fall through into the
DATA_UNACKNOWLEDGED and last_win == 0 branches:

    else if ((ct->proto.tcp.seen[0].flags | ct->proto.tcp.seen[1].flags) &
         IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED &&
         timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK])
        timeout = timeouts[TCP_CONNTRACK_UNACK];
    else if (ct->proto.tcp.last_win == 0 &&
         timeouts[new_state] > timeouts[TCP_CONNTRACK_RETRANS])
        timeout = timeouts[TCP_CONNTRACK_RETRANS];

Those fire precisely when nf_conntrack_tcp_timeout_close is raised above
nf_conntrack_tcp_timeout_unacknowledged or the retrans timeout, and both
preconditions are reachable for a closing RST: IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED
is commonly set on a flow that carried data, and ct->proto.tcp.last_win is
sticky since it is only refreshed for ACK-only segments.

This also diverges from the other closing path in the same file, which does
not cap:

    nf_conntrack_tcp_set_closing() {
        ...
        timeout = timeouts[TCP_CONNTRACK_CLOSE];
        WRITE_ONCE(ct->timeout, timeout + nfct_time_stamp);
        ...
    }

Given that, is the changelog sentence "Exact-match RSTs and accepted RST
packet trains still fall through to timeouts[new_state], which preserves the
CLOSE timeout when conntrack accepts the RST as closing the flow" accurate
for non-default close timeouts?  Restoring an explicit CLOSE assignment for
new_state == TCP_CONNTRACK_CLOSE, or adjusting the wording, would resolve
it.

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

* Re: [PATCH net-next 9/9] netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets
  2026-08-05  0:19   ` Jakub Kicinski
@ 2026-08-05  7:54     ` Pablo Neira Ayuso
  0 siblings, 0 replies; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-05  7:54 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: netfilter-devel, davem, netdev, pabeni, edumazet, fw, horms

On Tue, Aug 04, 2026 at 05:19:07PM -0700, Jakub Kicinski wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> ---
> netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets
> 
> This changes the timeout selection in nf_conntrack_tcp_packet() so that an
> RST which leaves the entry in TCP_CONNTRACK_ESTABLISHED (the non-exact
> sequence case from be0502a3f2e9) gets timeouts[TCP_CONNTRACK_UNACK] instead
> of timeouts[TCP_CONNTRACK_CLOSE].  RSTs that conntrack accepts as closing
> the flow are meant to keep using timeouts[new_state].
> 
> A few questions below.
> 
> >     Use the UNACK timeout for RST packets that leave the conntrack entry in
> >     TCP_CONNTRACK_ESTABLISHED.  Exact-match RSTs and accepted RST packet
> >     trains still fall through to timeouts[new_state], which preserves the
> >     CLOSE timeout when conntrack accepts the RST as closing the flow.
> 
> [Severity: Low]
> Should this carry a Fixes: tag?
 
No, and it is intentional.

We do not want updates on the conntrack state machine to automatically
end up in -stable.

Do we have to write this in the cover letter from this time on?

> [...] The changelog describes a user-visible
> failure (an ESTABLISHED entry expiring after 10 seconds while both TCP
> endpoints stay established), and it names be0502a3f2e9 in prose only, so
> stable tooling has nothing machine-readable to key on.
>
> There is also a wording tension: be0502a3f2e9's own changelog presented the
> CLOSE timeout for non-exact RSTs as intentional ("With patch, timeout is
> lowered like CLOSE, but connection stays in ESTABLISHED state" / "If no
> challenge ACK is received, the connection will time out after CLOSE
> seconds").  Either adding
> 
>     Fixes: be0502a3f2e94211 ("netfilter: conntrack: tcp: only close if RST matches exact sequence")
> 
> or rewording this as a deliberate policy refinement rather than a bug fix
> would make the two consistent.
> 
> [Severity: Low]
> Is there a test that observes this change?

No, it is just a timeout update in conntrack, there is nothing the
packetdrill test can do about this.

> The only in-tree artifact covering the inexact in-window RST path,
> tools/testing/selftests/net/netfilter/packetdrill/conntrack_rst_invalid.pkt,
> checks only the state string:
> 
>     // in-window but not exact match
>     +0.0 < R	42:42(0)	win 0
>     +0 `conntrack -f $NFCT_IP_VERSION -L -p tcp --dport 8080 2>/dev/null |grep -q ESTABLISHED`
> 
> The timeout is now the sole observable difference for that scenario, and it
> is never inspected.
> 
> On top of that, the segment labelled "in-window but not exact match" has seq
> before seen[!dir].td_maxack (the peer had acked 3001), so
> nf_conntrack_tcp_packet() rejects it at the "invalid rst" check and never
> reaches the patched timeout selection.  The test's own counter assertion
> agrees:
> 
>     // Expect four invalid RSTs
>     +0 `$xtables -v -S INPUT  | grep INVALID | grep -q -- "-c 4 "`
> 
> So the suite passes identically with and without this change.

Yes, that is expected.

> > diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c
> > index ceeed3d7fe520..723e946a78f40 100644
> > --- a/net/netfilter/nf_conntrack_proto_tcp.c
> > +++ b/net/netfilter/nf_conntrack_proto_tcp.c
> > @@ -1281,8 +1281,9 @@ int nf_conntrack_tcp_packet(struct nf_conn *ct,
> >  	if (ct->proto.tcp.retrans >= tn->tcp_max_retrans &&
> >  	    timeouts[new_state] > timeouts[TCP_CONNTRACK_RETRANS])
> >  		timeout = timeouts[TCP_CONNTRACK_RETRANS];
> > -	else if (unlikely(index == TCP_RST_SET))
> > -		timeout = timeouts[TCP_CONNTRACK_CLOSE];
> > +	else if (unlikely(index == TCP_RST_SET &&
> > +			  new_state == TCP_CONNTRACK_ESTABLISHED))
> > +		timeout = timeouts[TCP_CONNTRACK_UNACK];
> 
> [Severity: Medium]
> Can this new branch lengthen an entry's lifetime rather than shorten it?

Yes, it is indeed extending the timeout rather intentionally.

> Every sibling branch in this chain only assigns when the replacement is
> smaller, for example the RETRANS branch just above and the UNACK branch just
> below:
> 
>     if (ct->proto.tcp.retrans >= tn->tcp_max_retrans &&
>         timeouts[new_state] > timeouts[TCP_CONNTRACK_RETRANS])
>         timeout = timeouts[TCP_CONNTRACK_RETRANS];
>     ...
>     else if ((ct->proto.tcp.seen[0].flags | ct->proto.tcp.seen[1].flags) &
>          IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED &&
>          timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK])
>         timeout = timeouts[TCP_CONNTRACK_UNACK];
> 
> The new branch has no such guard.  Since this branch is only reachable via
> "new_state = old_state;" in case TCP_CONNTRACK_CLOSE, new_state is
> ESTABLISHED here, so the effective comparison is timeouts[UNACK] against
> timeouts[ESTABLISHED].
> 
> With the defaults in tcp_timeouts (ESTABLISHED 5 DAYS, UNACK 5 MINS) this is
> always a lowering.

Yes, and it is intentional, this is lowering the timeout indeed.

Actually, it is lowering it in a less "aggressive" way, instead of
using CLOSE timeout it uses the UNACK timeout (5 MINS) to avoid

> But both values are independently tunable with no cross-validation:
> 
>     net/netfilter/nf_conntrack_standalone.c:
>     [NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_ESTABLISHED] = {
>         .procname	= "nf_conntrack_tcp_timeout_established",
>         .maxlen		= sizeof(unsigned int),
>         .mode		= 0644,
>         .proc_handler	= proc_dointvec_jiffies,
>     },
> 
> and tcp_timeout_nlattr_to_obj() copies CTA_TIMEOUT_TCP_* verbatim.  So an
> administrator lowering nf_conntrack_tcp_timeout_established (120 s is a
> common hardening/NAT-appliance setting) inverts the relation.
> 
> __nf_ct_refresh_acct() then writes the expiry absolutely rather than
> clamping it:
> 
>     net/netfilter/nf_conntrack_core.c:__nf_ct_refresh_acct() {
>         ...
>         if (nf_ct_is_confirmed(ct))
>             extra_jiffies += nfct_time_stamp;
> 
>         if (READ_ONCE(ct->timeout) != extra_jiffies)
>             WRITE_ONCE(ct->timeout, extra_jiffies);
>         ...
>     }
> 
> In that configuration, an in-window RST whose sequence does not match
> td_maxack pushes the entry past the configured ESTABLISHED policy, and a
> periodic stream of such RSTs can renew it indefinitely, holding entries
> against nf_conntrack_max.  Would the usual form keep the intent while
> avoiding this?
> 
>     timeout = min(timeouts[new_state], timeouts[TCP_CONNTRACK_UNACK]);
> 
> or the matching guard:
> 
>     else if (unlikely(index == TCP_RST_SET &&
>                       new_state == TCP_CONNTRACK_ESTABLISHED) &&
>              timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK])
> 
> >  	else if ((ct->proto.tcp.seen[0].flags | ct->proto.tcp.seen[1].flags) &
> >  		 IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED &&
> >  		 timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK])
> 
> [Severity: Low]
> For an RST that conntrack does accept as closing the flow, is the CLOSE
> timeout really preserved?

No, and it is intentional. We are changing to the UNACK timeout which
is less aggresive.

> Previously such packets took the unconditional
> timeouts[TCP_CONNTRACK_CLOSE] assignment; now they fall through into the
> DATA_UNACKNOWLEDGED and last_win == 0 branches:
> 
>     else if ((ct->proto.tcp.seen[0].flags | ct->proto.tcp.seen[1].flags) &
>          IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED &&
>          timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK])
>         timeout = timeouts[TCP_CONNTRACK_UNACK];
>     else if (ct->proto.tcp.last_win == 0 &&
>          timeouts[new_state] > timeouts[TCP_CONNTRACK_RETRANS])
>         timeout = timeouts[TCP_CONNTRACK_RETRANS];
> 
> Those fire precisely when nf_conntrack_tcp_timeout_close is raised above
> nf_conntrack_tcp_timeout_unacknowledged or the retrans timeout, and both
> preconditions are reachable for a closing RST: IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED
> is commonly set on a flow that carried data, and ct->proto.tcp.last_win is
> sticky since it is only refreshed for ACK-only segments.
> 
> This also diverges from the other closing path in the same file, which does
> not cap:
> 
>     nf_conntrack_tcp_set_closing() {
>         ...
>         timeout = timeouts[TCP_CONNTRACK_CLOSE];
>         WRITE_ONCE(ct->timeout, timeout + nfct_time_stamp);
>         ...
>     }
> 
> Given that, is the changelog sentence "Exact-match RSTs and accepted RST
> packet trains still fall through to timeouts[new_state], which preserves the
> CLOSE timeout when conntrack accepts the RST as closing the flow" accurate
> for non-default close timeouts?  Restoring an explicit CLOSE assignment for
> new_state == TCP_CONNTRACK_CLOSE, or adjusting the wording, would resolve
> it.

Not sure what wording adjustment need to be done here.

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

* Re: [PATCH net-next 7/9] netfilter: flowtable: release tunnel route on error when building forward path
  2026-08-05  0:19   ` Jakub Kicinski
@ 2026-08-05  8:06     ` Pablo Neira Ayuso
  0 siblings, 0 replies; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-05  8:06 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: netfilter-devel, davem, netdev, pabeni, edumazet, fw, horms

Hi,

On Tue, Aug 04, 2026 at 05:19:06PM -0700, Jakub Kicinski wrote:
[...]
> [Severity: High]
> Can the dst installed here end up belonging to a different address family
> than the flow it is attached to?

Yes, the extra check to ensure IPv4 over IPv6 does not end up with the
wrong route is really required, LLM is right here.

But such check will last not long because Lorenzo's Bianconi has been
working on ip over ipv6 series for the tunneling.

I think this is not a reason to stall this net-next series, and
I think this can be done it a follow up?

Reading the two comments on patches in this net-next PR, this is the
only one that is really an issue and I think it can be addressed in a
follow up.

> The removed nft_flow_tunnel_update_route() derived the replacement dst from
> nf_route(nft_net(pkt), &tun_dst, &fl, false, nft_pf(pkt)), so the dst family
> always matched the flow's l3proto.  The dst that now arrives in
> info.tun_dst follows the tunnel underlay instead:
> 
> net/ipv4/ipip.c:ipip_fill_forward_path() always stores an IPv4 rtable:
> 
> 	path->tun.l3_proto = IPPROTO_IPIP;
> 	path->tun.dst = &rt->dst;
> 
> net/ipv6/ip6_tunnel.c:ip6_tnl_fill_forward_path() always stores an IPv6
> rt6_info:
> 
> 	path->tun.l3_proto = IPPROTO_IPV6;
> 	path->tun.dst = dst;
> 
> Neither handler consults the tunnel payload proto, and neither
> nft_dev_path_info() nor nft_dev_forward_path() checks the family before
> storing it in route->tuple[dir].dst.
> 
> The consumers cast that dst according to the inner flow family.  In
> net/netfilter/nf_flow_table_ip.c:nf_flow_offload_ipv6_hook():
> 
> 	rt = dst_rt6_info(tuplehash->tuple.dst_cache);
> 	...
> 	neigh = ip_neigh_gw6(rt->dst.dev, rt6_nexthop(rt, ip6_daddr));
> 
> and in nf_flow_offload_ip_hook():
> 
> 	rt = dst_rtable(tuplehash->tuple.dst_cache);
> 	... rt_nexthop(rt, ip_daddr) ...
> 
> net/netfilter/nf_flow_table_core.c:flow_offload_dst_cookie() does the same:
> 
> 	if (flow_tuple->l3proto == NFPROTO_IPV6)
> 		return rt6_get_cookie(dst_rt6_info(flow_tuple->dst_cache));
> 
> For an ip6_tunnel in ip4ip6 mode (t->parms.proto == IPPROTO_IPIP) carrying
> IPv4 flows, ip6_tnl_fill_forward_path() still reports l3_proto =
> IPPROTO_IPV6 and hands out an rt6_info, which nf_flow_offload_ip_hook()
> then reads through dst_rtable() and rt_nexthop()/ip_mtu_locked().  That
> configuration needs no misconfiguration to reach.
> 
> The mirror case is an IPv6 flow routed out an ipip device: an ipip device
> with mtu 1480 gets an inet6_dev at NETDEV_REGISTER, so fib6_nh_init()
> accepts "ip -6 route add ... dev tunlX", and nft_dev_fill_forward_path()
> deliberately skips the ether-device requirement for tunnels, so
> ipip_fill_forward_path() hands back an rtable that becomes the dst_cache of
> an IPv6 flow.  rt6_nexthop() then reads rt6i_flags and rt6i_gateway past
> the end of a struct rtable allocated from the ip_dst_cache slab, and the
> result is used as the IPv6 next hop passed to ip_neigh_gw6().
> 
> nf_flow_dst_check() does not catch this either: dst->ops->check() for the
> stored rtable is ipv4_dst_check(), which only tests rt_is_expired().
> 
> Would it make sense to validate info.tun.l3_proto (or the dst's
> ops->family) against the flow's l3proto before installing info.tun_dst?
> 
> [Severity: Low]
> Should this carry a Fixes: tag?

No, and it is intentional.

I already noted this rare bug while working on this series which
results in an incorrect fastpath in case route goes away while flow
offload entry is being set up, but these tunneling support is recent,
and I really preferred to go for net-next.

That was a deliberate decision. Patches with Fixes: are picked more
quickly into -stable and I did not deem this to be urgent.

> Before this change the tunnel information was only installed when the
> second lookup succeeded:
> 
> 	if (info.num_tuns &&
> 	    !nft_flow_tunnel_update_route(pkt, &info.tun, route, dir)) {
> 
> while route->tuple[!dir].in.ifindex, route->tuple[dir].out.ifindex, the
> encaps, h_source/h_dest and xmit_type had already been written and
> nft_dev_forward_path() still returned 0.  On nf_route() failure the flow was
> offloaded with the underlay device as output but num_tuns == 0, so inner
> packets left the underlay without IPIP/IP6IP6 encapsulation, which matches
> the "incomplete forward path" described in the commit message.
> 
> git blame points that construct at d30301ba4b07ac ("netfilter: flowtable:
> tunnel route update"), with the tunnel forward path support itself from
> ab427db1788581, so the bug is in released kernels rather than in-flight
> code from this series.  A Fixes: d30301ba4b07ac tag would help stable
> tracking.
> 
> It may also be worth noting in the changelog that this is not standalone
> backportable: neither dev_fill_forward_path_release() nor the
> net_device_path tun.dst field exist before the earlier "net: pass dst via
> net_device_path in dev_fill_forward_path()" commit in this series.

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

* Re: [PATCH net-next 0/9] Netfilter updates for net-next
  2026-07-31 15:33 [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
                   ` (8 preceding siblings ...)
  2026-07-31 15:34 ` [PATCH net-next 9/9] netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets Pablo Neira Ayuso
@ 2026-08-05 17:40 ` Pablo Neira Ayuso
  9 siblings, 0 replies; 15+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-05 17:40 UTC (permalink / raw)
  To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms

Hi,

After reading sashiko's comment, I think this nf-next PR can be
included upstream and the only real issue can be addressed in a follow
up.

Thanks.

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

end of thread, other threads:[~2026-08-05 17:40 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31 15:33 [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso
2026-07-31 15:33 ` [PATCH net-next 1/9] netfilter: conncount: normalize tuple and zone on successful ct lookup Pablo Neira Ayuso
2026-07-31 15:33 ` [PATCH net-next 2/9] netfilter: flowtable: consolidate net_device field in nft_forward_info struct Pablo Neira Ayuso
2026-07-31 15:33 ` [PATCH net-next 3/9] netfilter: flowtable: consolidate flowtable device check Pablo Neira Ayuso
2026-07-31 15:33 ` [PATCH net-next 4/9] net: dsa: stop at the user device in .fill_forward_path Pablo Neira Ayuso
2026-07-31 15:33 ` [PATCH net-next 5/9] net: do not advance stack index from dev_fwd_path() Pablo Neira Ayuso
2026-07-31 15:33 ` [PATCH net-next 6/9] net: pass dst via net_device_path in dev_fill_forward_path() Pablo Neira Ayuso
2026-07-31 15:34 ` [PATCH net-next 7/9] netfilter: flowtable: release tunnel route on error when building forward path Pablo Neira Ayuso
2026-08-05  0:19   ` Jakub Kicinski
2026-08-05  8:06     ` Pablo Neira Ayuso
2026-07-31 15:34 ` [PATCH net-next 8/9] netfilter: nf_tables: call skb_valid_dst() before skb_dst() Pablo Neira Ayuso
2026-07-31 15:34 ` [PATCH net-next 9/9] netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets Pablo Neira Ayuso
2026-08-05  0:19   ` Jakub Kicinski
2026-08-05  7:54     ` Pablo Neira Ayuso
2026-08-05 17:40 ` [PATCH net-next 0/9] Netfilter updates for net-next Pablo Neira Ayuso

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