From: Pablo Neira Ayuso <pablo@netfilter.org>
To: Eric Woudstra <ericwouds@gmail.com>
Cc: Andrew Lunn <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Simon Horman <horms@kernel.org>, Florian Westphal <fw@strlen.de>,
Phil Sutter <phil@nwl.cc>,
Nikolay Aleksandrov <razor@blackwall.org>,
Ido Schimmel <idosch@nvidia.com>,
Kuniyuki Iwashima <kuniyu@google.com>,
Stanislav Fomichev <sdf.kernel@gmail.com>,
Samiullah Khawaja <skhawaja@google.com>,
Hangbin Liu <liuhangbin@gmail.com>,
Krishna Kumar <krikku@gmail.com>,
Martin Karsten <mkarsten@uwaterloo.ca>,
netdev@vger.kernel.org, netfilter-devel@vger.kernel.org,
bridge@lists.linux.dev
Subject: Re: [PATCH v12 nf-next 7/7] netfilter: nft_flow_offload: Add bridgeflow to nft_flow_offload_eval()
Date: Wed, 8 Jul 2026 11:52:51 +0200 [thread overview]
Message-ID: <ak4d89hkh0Jvcp2W@chamomile> (raw)
In-Reply-To: <20260707091045.967678-8-ericwouds@gmail.com>
Hi,
On Tue, Jul 07, 2026 at 11:10:45AM +0200, Eric Woudstra wrote:
> Edit nft_flow_offload_eval() to make it possible to handle a flowtable of
> the nft bridge family.
>
> Use nft_flow_offload_bridge_init() to fill the flow tuples. It uses
> nft_dev_fill_bridge_path() in each direction.
I decided to add a bit more boiler plate in my proposal to detach the
inet and bridge flowtable dataplanes.
More comments below.
> Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
> ---
> include/net/netfilter/nf_flow_table.h | 5 +
> net/netfilter/nf_flow_table_path.c | 126 ++++++++++++++++++++++++++
> net/netfilter/nft_flow_offload.c | 20 +++-
> 3 files changed, 146 insertions(+), 5 deletions(-)
>
> diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
> index 5c6e3b65ae85b..a109eda5250c7 100644
> --- a/include/net/netfilter/nf_flow_table.h
> +++ b/include/net/netfilter/nf_flow_table.h
> @@ -305,6 +305,11 @@ nf_flow_table_offload_del_cb(struct nf_flowtable *flow_table,
> void flow_offload_route_init(struct flow_offload *flow,
> struct nf_flow_route *route);
>
> +int flow_offload_bridge_init(struct flow_offload *flow,
> + const struct nft_pktinfo *pkt,
> + enum ip_conntrack_dir dir,
> + struct nft_flowtable *ft);
> +
> int flow_offload_add(struct nf_flowtable *flow_table, struct flow_offload *flow);
> void flow_offload_refresh(struct nf_flowtable *flow_table,
> struct flow_offload *flow, bool force);
> diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
> index 2b6ebb594a9ee..cdd6a822cb811 100644
> --- a/net/netfilter/nf_flow_table_path.c
> +++ b/net/netfilter/nf_flow_table_path.c
> @@ -1,6 +1,7 @@
> // SPDX-License-Identifier: GPL-2.0-only
> #include <linux/kernel.h>
> #include <linux/module.h>
> +#include <linux/if_vlan.h>
> #include <linux/init.h>
> #include <linux/etherdevice.h>
> #include <linux/netlink.h>
> @@ -365,3 +366,128 @@ int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct,
> return -ENOENT;
> }
> EXPORT_SYMBOL_GPL(nft_flow_route);
> +
> +static int nft_dev_fill_bridge_path(struct flow_offload *flow,
> + struct nft_flowtable *ft,
> + enum ip_conntrack_dir dir,
> + const struct net_device *src_dev,
> + const struct net_device *dst_dev,
> + unsigned char *src_ha,
> + unsigned char *dst_ha)
> +{
> + struct flow_offload_tuple_rhash *th = flow->tuplehash;
> + struct net_device_path_ctx ctx = {};
> + struct net_device_path_stack stack;
> + struct nft_forward_info info = {};
> + int i, j = 0;
> +
> + for (i = th[dir].tuple.encap_num - 1; i >= 0 ; i--) {
> + if (info.num_encaps >= NF_FLOW_TABLE_ENCAP_MAX)
> + return -1;
> +
> + if (th[dir].tuple.in_vlan_ingress & BIT(i))
> + continue;
> +
> + info.encap[info.num_encaps].id = th[dir].tuple.encap[i].id;
> + info.encap[info.num_encaps].proto = th[dir].tuple.encap[i].proto;
> + info.num_encaps++;
> +
> + if (th[dir].tuple.encap[i].proto == htons(ETH_P_PPP_SES))
> + continue;
> +
> + if (ctx.num_vlans >= NET_DEVICE_PATH_VLAN_MAX)
> + return -1;
> + ctx.vlan[ctx.num_vlans].id = th[dir].tuple.encap[i].id;
> + ctx.vlan[ctx.num_vlans].proto = th[dir].tuple.encap[i].proto;
> + ctx.num_vlans++;
> + }
I am not sure why this is needed, in my approach I simplified this,
but maybe I broke bridge vlan filtering. I am not sure what test
coverage you made.
> + ctx.dev = src_dev;
> + ether_addr_copy(ctx.daddr, dst_ha);
> +
> + if (dev_fill_bridge_path(&ctx, &stack) < 0)
> + return -1;
> +
> + nft_dev_path_info(&stack, &info, dst_ha, &ft->data);
> +
> + if (!info.indev || info.indev != dst_dev)
> + return -1;
> +
> + th[!dir].tuple.iifidx = info.indev->ifindex;
> + for (i = info.num_encaps - 1; i >= 0; i--) {
> + th[!dir].tuple.encap[j].id = info.encap[i].id;
> + th[!dir].tuple.encap[j].proto = info.encap[i].proto;
> + if (info.ingress_vlans & BIT(i))
> + th[!dir].tuple.in_vlan_ingress |= BIT(j);
> + j++;
> + }
> + th[!dir].tuple.encap_num = info.num_encaps;
> +
> + th[dir].tuple.mtu = dst_dev->mtu;
> + ether_addr_copy(th[dir].tuple.out.h_source, src_ha);
> + ether_addr_copy(th[dir].tuple.out.h_dest, dst_ha);
> + th[dir].tuple.out.ifidx = info.outdev->ifindex;
> + th[dir].tuple.xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
> +
> + return 0;
> +}
> +
> +int flow_offload_bridge_init(struct flow_offload *flow,
> + const struct nft_pktinfo *pkt,
> + enum ip_conntrack_dir dir,
> + struct nft_flowtable *ft)
> +{
> + const struct net_device *in_dev, *out_dev;
> + struct ethhdr *eth = eth_hdr(pkt->skb);
> + struct flow_offload_tuple *tuple;
> + int err, i = 0;
> +
> + in_dev = nft_in(pkt);
> + if (!in_dev || !nft_flowtable_find_dev(in_dev, ft))
> + return -1;
> +
> + out_dev = nft_out(pkt);
> + if (!out_dev || !nft_flowtable_find_dev(out_dev, ft))
> + return -1;
> +
> + tuple = &flow->tuplehash[!dir].tuple;
> +
> + if (skb_vlan_tag_present(pkt->skb)) {
> + tuple->encap[i].id = skb_vlan_tag_get(pkt->skb);
> + tuple->encap[i].proto = pkt->skb->vlan_proto;
> + i++;
> + }
> +
> + switch (eth_hdr(pkt->skb)->h_proto) {
> + case htons(ETH_P_8021Q): {
> + struct vlan_hdr *vhdr = (struct vlan_hdr *)(skb_mac_header(pkt->skb)
> + + sizeof(struct ethhdr));
> + tuple->encap[i].id = ntohs(vhdr->h_vlan_TCI);
> + tuple->encap[i].proto = htons(ETH_P_8021Q);
> + i++;
> + break;
> + }
> + case htons(ETH_P_PPP_SES): {
> + struct pppoe_hdr *phdr = (struct pppoe_hdr *)(skb_mac_header(pkt->skb)
> + + sizeof(struct ethhdr));
> +
> + tuple->encap[i].id = ntohs(phdr->sid);
> + tuple->encap[i].proto = htons(ETH_P_PPP_SES);
> + i++;
> + break;
> + }
> + }
> + tuple->encap_num = i;
I am not sure these lines above can work. The VLAN tag might be
already gone by when the packet is observed from the bridge/forward
hook. I think populating the encap fields of the tuple by using the
observed packet is not good to go.
> + err = nft_dev_fill_bridge_path(flow, ft, !dir, out_dev, in_dev,
> + eth->h_dest, eth->h_source);
> + if (err < 0)
> + return err;
> +
> + err = nft_dev_fill_bridge_path(flow, ft, dir, in_dev, out_dev,
> + eth->h_source, eth->h_dest);
> + if (err < 0)
> + return err;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(flow_offload_bridge_init);
> diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c
> index 0be62841155b6..d0d63ef7cecd5 100644
> --- a/net/netfilter/nft_flow_offload.c
> +++ b/net/netfilter/nft_flow_offload.c
> @@ -53,6 +53,7 @@ static void nft_flow_offload_eval(const struct nft_expr *expr,
> {
> struct nft_flow_offload *priv = nft_expr_priv(expr);
> struct nf_flowtable *flowtable = &priv->flowtable->data;
> + bool routing = flowtable->type->family != NFPROTO_BRIDGE;
> struct tcphdr _tcph, *tcph = NULL;
> struct nf_flow_route route = {};
> enum ip_conntrack_info ctinfo;
> @@ -109,14 +110,21 @@ static void nft_flow_offload_eval(const struct nft_expr *expr,
> goto out;
>
> dir = CTINFO2DIR(ctinfo);
> - if (nft_flow_route(pkt, ct, &route, dir, priv->flowtable) < 0)
> - goto err_flow_route;
> + if (routing) {
> + if (nft_flow_route(pkt, ct, &route, dir, priv->flowtable) < 0)
> + goto err_flow_route;
> + }
As said, I am leaning towards adding a bit more boilerplate code to
separate the bridge and inet flowtable datapaths.
> flow = flow_offload_alloc(ct);
> if (!flow)
> goto err_flow_alloc;
>
> - flow_offload_route_init(flow, &route);
> + if (routing)
> + flow_offload_route_init(flow, &route);
> + else
> + if (flow_offload_bridge_init(flow, pkt, dir, priv->flowtable) < 0)
> + goto err_flow_add;
> +
> if (tcph)
> flow_offload_ct_tcp(ct);
>
> @@ -164,8 +172,10 @@ static void nft_flow_offload_eval(const struct nft_expr *expr,
> err_flow_add:
> flow_offload_free(flow);
> err_flow_alloc:
> - dst_release(route.tuple[dir].dst);
> - dst_release(route.tuple[!dir].dst);
> + if (routing) {
> + dst_release(route.tuple[dir].dst);
> + dst_release(route.tuple[!dir].dst);
> + }
> err_flow_route:
> clear_bit(IPS_OFFLOAD_BIT, &ct->status);
> out:
> --
> 2.53.0
>
next prev parent reply other threads:[~2026-07-08 9:52 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-07 9:10 [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath Eric Woudstra
2026-07-07 9:10 ` [PATCH v12 nf-next 1/7] bridge: Add filling forward path from port to port Eric Woudstra
2026-07-09 9:41 ` Nikolay Aleksandrov
2026-07-07 9:10 ` [PATCH v12 nf-next 2/7] net: core: dev: Add dev_fill_bridge_path() Eric Woudstra
2026-07-07 9:10 ` [PATCH v12 nf-next 3/7] netfilter: nf_flow_table_offload: Add nf_flow_rule_bridge() Eric Woudstra
2026-07-08 9:48 ` Pablo Neira Ayuso
2026-07-08 18:36 ` Eric Woudstra
2026-07-10 9:26 ` Pablo Neira Ayuso
2026-07-10 15:16 ` Eric Woudstra
2026-07-13 16:37 ` Pablo Neira Ayuso
2026-07-07 9:10 ` [PATCH v12 nf-next 4/7] netfilter: nf_flow_table_inet: Add nf_flowtable_type flowtable_bridge Eric Woudstra
2026-07-07 9:10 ` [PATCH v12 nf-next 5/7] netfilter: nft_flow_offload: nft_flow_offload_eval: check thoff==0 Eric Woudstra
2026-07-08 9:54 ` Pablo Neira Ayuso
2026-07-07 9:10 ` [PATCH v12 nf-next 6/7] netfilter: nft_flow_offload: Add NFPROTO_BRIDGE to validate Eric Woudstra
2026-07-07 9:10 ` [PATCH v12 nf-next 7/7] netfilter: nft_flow_offload: Add bridgeflow to nft_flow_offload_eval() Eric Woudstra
2026-07-08 9:52 ` Pablo Neira Ayuso [this message]
2026-07-08 19:14 ` Eric Woudstra
2026-07-08 9:47 ` [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath Pablo Neira Ayuso
2026-07-09 9:34 ` Nikolay Aleksandrov
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=ak4d89hkh0Jvcp2W@chamomile \
--to=pablo@netfilter.org \
--cc=andrew+netdev@lunn.ch \
--cc=bridge@lists.linux.dev \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=ericwouds@gmail.com \
--cc=fw@strlen.de \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=krikku@gmail.com \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=liuhangbin@gmail.com \
--cc=mkarsten@uwaterloo.ca \
--cc=netdev@vger.kernel.org \
--cc=netfilter-devel@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=phil@nwl.cc \
--cc=razor@blackwall.org \
--cc=sdf.kernel@gmail.com \
--cc=skhawaja@google.com \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.