Re: [PATCH v12 nf-next 7/7] netfilter: nft_flow_offload: Add bridgeflow to nft_flow_offload_eval()

Pablo Neira Ayuso <[email protected]>
Newsgroups gmane.linux.network.bridge,gmane.linux.network,gmane.comp.security.firewalls.netfilter.devel
Message-ID <ak4d89hkh0Jvcp2W@chamomile>
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 <[email protected]>
> ---
>  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
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.