From: Jakub Kicinski <kuba@kernel.org>
To: pablo@netfilter.org
Cc: Jakub Kicinski <kuba@kernel.org>,
netfilter-devel@vger.kernel.org, davem@davemloft.net,
netdev@vger.kernel.org, pabeni@redhat.com, edumazet@google.com,
fw@strlen.de, horms@kernel.org
Subject: Re: [PATCH net-next 7/9] netfilter: flowtable: release tunnel route on error when building forward path
Date: Tue, 4 Aug 2026 17:19:06 -0700 [thread overview]
Message-ID: <20260805001906.3985830-1-kuba@kernel.org> (raw)
In-Reply-To: <20260731153402.851224-8-pablo@netfilter.org>
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.
next prev parent reply other threads:[~2026-08-05 0:19 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260805001906.3985830-1-kuba@kernel.org \
--to=kuba@kernel.org \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=fw@strlen.de \
--cc=horms@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=netfilter-devel@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=pablo@netfilter.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox