From e53932f0e5de19bff8740454ec6952df343d45bb Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Mon, 27 Jul 2026 21:41:29 +0200 Subject: [PATCH 1/9] netfilter: conncount: normalize tuple and zone on successful ct lookup 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 Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso --- 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; From f19fd12143db730163ee5aa347bdc246e69f797e Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:30 +0200 Subject: [PATCH 2/9] netfilter: flowtable: consolidate net_device field in nft_forward_info struct 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 Signed-off-by: Pablo Neira Ayuso --- 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; From df1705f289ba27ef951a87f20d41a109e7d3f40c Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:37 +0200 Subject: [PATCH 3/9] netfilter: flowtable: consolidate flowtable device check 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 Signed-off-by: Pablo Neira Ayuso --- 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; From 5be6e044bea648cc0cc083899c72252f7f2227fd Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:43 +0200 Subject: [PATCH 4/9] net: dsa: stop at the user device in .fill_forward_path 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 --- 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) { From 5deda60c56eeeab25beb10cf4d48e07587076b11 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:46 +0200 Subject: [PATCH 5/9] net: do not advance stack index from dev_fwd_path() Update stack index from dev_fill_forward_path() instead, once the forward path slot has been populated. Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- 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; } From 0ad8404e776698de4dac5cc0df3be68d344e741c Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:48 +0200 Subject: [PATCH 6/9] net: pass dst via net_device_path in dev_fill_forward_path() 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 Signed-off-by: Pablo Neira Ayuso --- 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); From 806273fcaffb82fdea93b12ee281c3ed4b0b8e76 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:55 +0200 Subject: [PATCH 7/9] netfilter: flowtable: release tunnel route on error when building forward path 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 Signed-off-by: Pablo Neira Ayuso --- 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; From 689db98e535bf8efb5bc9c36f4fd3de3bd715eb6 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 10:40:06 +0200 Subject: [PATCH 8/9] netfilter: nf_tables: call skb_valid_dst() before skb_dst() 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 --- 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 #include #include +#include #include #include #include @@ -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 #include #include +#include #include #include #include @@ -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 #include #include +#include #include /* for TCP_TIME_WAIT */ #include #include @@ -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 #include #include +#include #include #include #include @@ -59,10 +60,11 @@ void nft_rt_get_eval(const struct nft_expr *expr, u32 *dest = ®s->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 #include #include +#include #include #include @@ -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) From bf80e6802273a900311b44e47c9b1a59c6d2473c Mon Sep 17 00:00:00 2001 From: Minghao Zhang Date: Wed, 29 Jul 2026 15:46:52 +0000 Subject: [PATCH 9/9] netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets 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 Reported-by: Minghao Zhang Reported-by: Jianjun Chen Signed-off-by: Minghao Zhang Signed-off-by: Pablo Neira Ayuso --- 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])