* [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath
@ 2026-07-07 9:10 Eric Woudstra
2026-07-07 9:10 ` [PATCH v12 nf-next 1/7] bridge: Add filling forward path from port to port Eric Woudstra
` (7 more replies)
0 siblings, 8 replies; 14+ messages in thread
From: Eric Woudstra @ 2026-07-07 9:10 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Pablo Neira Ayuso, Florian Westphal,
Phil Sutter, Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten
Cc: netdev, netfilter-devel, bridge, Eric Woudstra
This patchset makes it possible to set up a software fastpath between
bridged interfaces. One patch adds the flow rule for the hardware
fastpath. This creates the possibility to have a hardware offloaded
fastpath between bridged interfaces. More patches are added to solve
issues found with the existing code.
To set up the fastpath, add this extra flowtable (with or
without 'flags offload'):
table bridge filter {
flowtable fb {
hook ingress priority filter
devices = { lan0, lan1, lan2, lan3, lan4, wlan0, wlan1 }
flags offload
}
chain forward {
type filter hook forward priority filter; policy accept;
ct state established flow add @fb
}
}
Creating a separate fastpath for bridges.
forward fastpath bypass
.----------------------------------------.
/ \
| IP - forwarding |
| / \ v
| / wan ...
| /
| |
| |
| brlan.1
| |
| +-------------------------------+
| | vlan 1 |
| | |
| | brlan (vlan-filtering) |
| +---------------+ |
| | DSA-SWITCH | |
| | | vlan 1 |
| | | to |
| | vlan 1 | untagged |
| +---------------+---------------+
. / \
------>lan0 wlan1
. ^ ^
. | |
. \_________________/
. bridge fastpath bypass
.
^
vlan 1 tagged packets
Note: While testing direct transmit in the software forward-fastpath,
without the capability of setting the offload flag, it is sometimes useful
to enslave the wan interface to another bridge, brwan. This will make
sure both directions of the software forward-fastpath use direct transmit,
which also happens when the offload flag is set.
Note 2: For complete testing this patch-set, see:
[PATCH v5 nf-next] selftests: netfilter: Add bridge_fastpath.sh
This mail also mentions other patches involved in this, some patches
already applied, some patches are not.
Changes in v12:
- Added Note 2, refer to selftest script
- Added patch: nft_flow_offload_eval() check thoff==0.
- flow_offload_bridge_init() removed unnecessary call to memset().
Changes in v11:
- Dropped "Introduce DEV_PATH_BR_VLAN_KEEP_HW for bridge-fastpath" from
this patch-set, it has moved to another patch-set.
- Updated nft_flow_offload_bridge_init() changing the way of accessing
headers after fixing nft_do_chain_bridge().
v10 split from patch-set: bridge-fastpath and related improvements v9
Eric Woudstra (7):
bridge: Add filling forward path from port to port
net: core: dev: Add dev_fill_bridge_path()
netfilter: nf_flow_table_offload: Add nf_flow_rule_bridge()
netfilter: nf_flow_table_inet: Add nf_flowtable_type flowtable_bridge
netfilter: nft_flow_offload: nft_flow_offload_eval: check thoff==0
netfilter: nft_flow_offload: Add NFPROTO_BRIDGE to validate
netfilter: nft_flow_offload: Add bridgeflow to nft_flow_offload_eval()
include/linux/netdevice.h | 2 +
include/net/netfilter/nf_flow_table.h | 8 ++
net/bridge/br_device.c | 19 +++-
net/bridge/br_private.h | 2 +
net/bridge/br_vlan.c | 6 +-
net/core/dev.c | 66 +++++++++++---
net/netfilter/nf_flow_table_inet.c | 13 +++
net/netfilter/nf_flow_table_offload.c | 13 +++
net/netfilter/nf_flow_table_path.c | 126 ++++++++++++++++++++++++++
net/netfilter/nft_flow_offload.c | 32 +++++--
10 files changed, 259 insertions(+), 28 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v12 nf-next 1/7] bridge: Add filling forward path from port to port
2026-07-07 9:10 [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath Eric Woudstra
@ 2026-07-07 9:10 ` Eric Woudstra
2026-07-07 9:10 ` [PATCH v12 nf-next 2/7] net: core: dev: Add dev_fill_bridge_path() Eric Woudstra
` (6 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Eric Woudstra @ 2026-07-07 9:10 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Pablo Neira Ayuso, Florian Westphal,
Phil Sutter, Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten
Cc: netdev, netfilter-devel, bridge, Eric Woudstra
If a port is passed as argument instead of the master, then:
At br_fill_forward_path(): find the master and use it to fill the
forward path.
At br_vlan_fill_forward_path_pvid(): lookup vlan group from port
instead.
Acked-by: Nikolay Aleksandrov <razor@blackwall.org>
Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
---
net/bridge/br_device.c | 19 ++++++++++++++-----
net/bridge/br_private.h | 2 ++
net/bridge/br_vlan.c | 6 +++++-
3 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index 3b9fc86293fdb..56fc80d2f5215 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -385,16 +385,25 @@ static int br_del_slave(struct net_device *dev, struct net_device *slave_dev)
static int br_fill_forward_path(struct net_device_path_ctx *ctx,
struct net_device_path *path)
{
+ struct net_bridge_port *src, *dst;
struct net_bridge_fdb_entry *f;
- struct net_bridge_port *dst;
struct net_bridge *br;
- if (netif_is_bridge_port(ctx->dev))
- return -1;
+ if (netif_is_bridge_port(ctx->dev)) {
+ struct net_device *br_dev;
+
+ br_dev = netdev_master_upper_dev_get_rcu((struct net_device *)ctx->dev);
+ if (!br_dev)
+ return -1;
- br = netdev_priv(ctx->dev);
+ src = br_port_get_rcu(ctx->dev);
+ br = netdev_priv(br_dev);
+ } else {
+ src = NULL;
+ br = netdev_priv(ctx->dev);
+ }
- br_vlan_fill_forward_path_pvid(br, ctx, path);
+ br_vlan_fill_forward_path_pvid(br, src, ctx, path);
f = br_fdb_find_rcu(br, ctx->daddr, path->bridge.vlan_id);
if (!f)
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index d55ea9516e3e3..00f5b72ff42de 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -1630,6 +1630,7 @@ bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
const struct net_bridge_vlan *range_end);
void br_vlan_fill_forward_path_pvid(struct net_bridge *br,
+ struct net_bridge_port *p,
struct net_device_path_ctx *ctx,
struct net_device_path *path);
int br_vlan_fill_forward_path_mode(struct net_bridge *br,
@@ -1799,6 +1800,7 @@ static inline int nbp_get_num_vlan_infos(struct net_bridge_port *p,
}
static inline void br_vlan_fill_forward_path_pvid(struct net_bridge *br,
+ struct net_bridge_port *p,
struct net_device_path_ctx *ctx,
struct net_device_path *path)
{
diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
index a248ce70f919f..f4a82e107ec7d 100644
--- a/net/bridge/br_vlan.c
+++ b/net/bridge/br_vlan.c
@@ -1450,6 +1450,7 @@ int br_vlan_get_pvid_rcu(const struct net_device *dev, u16 *p_pvid)
EXPORT_SYMBOL_GPL(br_vlan_get_pvid_rcu);
void br_vlan_fill_forward_path_pvid(struct net_bridge *br,
+ struct net_bridge_port *p,
struct net_device_path_ctx *ctx,
struct net_device_path *path)
{
@@ -1462,7 +1463,10 @@ void br_vlan_fill_forward_path_pvid(struct net_bridge *br,
if (!br_opt_get(br, BROPT_VLAN_ENABLED))
return;
- vg = br_vlan_group_rcu(br);
+ if (p)
+ vg = nbp_vlan_group_rcu(p);
+ else
+ vg = br_vlan_group_rcu(br);
if (idx >= 0 &&
ctx->vlan[idx].proto == br->vlan_proto) {
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v12 nf-next 2/7] net: core: dev: Add dev_fill_bridge_path()
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-07 9:10 ` 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
` (5 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Eric Woudstra @ 2026-07-07 9:10 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Pablo Neira Ayuso, Florian Westphal,
Phil Sutter, Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten
Cc: netdev, netfilter-devel, bridge, Eric Woudstra
New function dev_fill_bridge_path(), similar to dev_fill_forward_path().
It handles starting from a bridge port instead of the bridge master.
The structures ctx and nft_forward_info need to be already filled in with
the (vlan) encaps.
Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
---
include/linux/netdevice.h | 2 ++
net/core/dev.c | 66 +++++++++++++++++++++++++++++++--------
2 files changed, 55 insertions(+), 13 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 982b6e65a4be4..69b499ddefda0 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -3421,6 +3421,8 @@ void dev_remove_offload(struct packet_offload *po);
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_bridge_path(struct net_device_path_ctx *ctx,
+ struct net_device_path_stack *stack);
int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
struct net_device_path_stack *stack);
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 4b3d5cfdf6e00..f6685c1e47860 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -750,44 +750,84 @@ static struct net_device_path *dev_fwd_path(struct net_device_path_stack *stack)
return &stack->path[k];
}
-int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
- struct net_device_path_stack *stack)
+static int dev_fill_forward_path_common(struct net_device_path_ctx *ctx,
+ struct net_device_path_stack *stack)
{
const struct net_device *last_dev;
- struct net_device_path_ctx ctx = {
- .dev = dev,
- };
struct net_device_path *path;
int ret = 0;
- memcpy(ctx.daddr, daddr, sizeof(ctx.daddr));
- stack->num_paths = 0;
- while (ctx.dev && ctx.dev->netdev_ops->ndo_fill_forward_path) {
- last_dev = ctx.dev;
+ while (ctx->dev && ctx->dev->netdev_ops->ndo_fill_forward_path) {
+ last_dev = ctx->dev;
path = dev_fwd_path(stack);
if (!path)
return -1;
memset(path, 0, sizeof(struct net_device_path));
- ret = ctx.dev->netdev_ops->ndo_fill_forward_path(&ctx, path);
+ ret = ctx->dev->netdev_ops->ndo_fill_forward_path(ctx, path);
if (ret < 0)
return -1;
- if (WARN_ON_ONCE(last_dev == ctx.dev))
+ if (WARN_ON_ONCE(last_dev == ctx->dev))
return -1;
}
- if (!ctx.dev)
+ if (!ctx->dev)
return ret;
path = dev_fwd_path(stack);
if (!path)
return -1;
path->type = DEV_PATH_ETHERNET;
- path->dev = ctx.dev;
+ path->dev = ctx->dev;
return ret;
}
+
+int dev_fill_bridge_path(struct net_device_path_ctx *ctx,
+ struct net_device_path_stack *stack)
+{
+ const struct net_device *last_dev, *br_dev;
+ struct net_device_path *path;
+
+ stack->num_paths = 0;
+
+ if (!ctx->dev || !netif_is_bridge_port(ctx->dev))
+ return -1;
+
+ br_dev = netdev_master_upper_dev_get_rcu((struct net_device *)ctx->dev);
+ if (!br_dev || !br_dev->netdev_ops->ndo_fill_forward_path)
+ return -1;
+
+ last_dev = ctx->dev;
+ path = dev_fwd_path(stack);
+ if (!path)
+ return -1;
+
+ memset(path, 0, sizeof(struct net_device_path));
+ if (br_dev->netdev_ops->ndo_fill_forward_path(ctx, path) < 0)
+ return -1;
+
+ if (!ctx->dev || WARN_ON_ONCE(last_dev == ctx->dev))
+ return -1;
+
+ return dev_fill_forward_path_common(ctx, stack);
+}
+EXPORT_SYMBOL_GPL(dev_fill_bridge_path);
+
+int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
+ struct net_device_path_stack *stack)
+{
+ struct net_device_path_ctx ctx = {
+ .dev = dev,
+ };
+
+ memcpy(ctx.daddr, daddr, sizeof(ctx.daddr));
+
+ stack->num_paths = 0;
+
+ return dev_fill_forward_path_common(&ctx, stack);
+}
EXPORT_SYMBOL_GPL(dev_fill_forward_path);
/* must be called under rcu_read_lock(), as we dont take a reference */
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v12 nf-next 3/7] netfilter: nf_flow_table_offload: Add nf_flow_rule_bridge()
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-07 9:10 ` [PATCH v12 nf-next 2/7] net: core: dev: Add dev_fill_bridge_path() Eric Woudstra
@ 2026-07-07 9:10 ` Eric Woudstra
2026-07-08 9:48 ` 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
` (4 subsequent siblings)
7 siblings, 1 reply; 14+ messages in thread
From: Eric Woudstra @ 2026-07-07 9:10 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Pablo Neira Ayuso, Florian Westphal,
Phil Sutter, Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten
Cc: netdev, netfilter-devel, bridge, Eric Woudstra
Add nf_flow_rule_bridge().
It only calls the common rule and adds the redirect.
Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
---
include/net/netfilter/nf_flow_table.h | 3 +++
net/netfilter/nf_flow_table_offload.c | 13 +++++++++++++
2 files changed, 16 insertions(+)
diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
index 7b23b245a5a86..5c6e3b65ae85b 100644
--- a/include/net/netfilter/nf_flow_table.h
+++ b/include/net/netfilter/nf_flow_table.h
@@ -368,6 +368,9 @@ void nf_flow_table_offload_flush_cleanup(struct nf_flowtable *flowtable);
int nf_flow_table_offload_setup(struct nf_flowtable *flowtable,
struct net_device *dev,
enum flow_block_command cmd);
+int nf_flow_rule_bridge(struct net *net, struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule);
int nf_flow_rule_route_ipv4(struct net *net, struct flow_offload *flow,
enum flow_offload_tuple_dir dir,
struct nf_flow_rule *flow_rule);
diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
index 002ec15d988bd..5566ebda7b7d3 100644
--- a/net/netfilter/nf_flow_table_offload.c
+++ b/net/netfilter/nf_flow_table_offload.c
@@ -740,6 +740,19 @@ nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
return 0;
}
+int nf_flow_rule_bridge(struct net *net, struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
+{
+ if (nf_flow_rule_route_common(net, flow, dir, flow_rule) < 0)
+ return -1;
+
+ flow_offload_redirect(net, flow, dir, flow_rule);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(nf_flow_rule_bridge);
+
int nf_flow_rule_route_ipv4(struct net *net, struct flow_offload *flow,
enum flow_offload_tuple_dir dir,
struct nf_flow_rule *flow_rule)
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v12 nf-next 4/7] netfilter: nf_flow_table_inet: Add nf_flowtable_type flowtable_bridge
2026-07-07 9:10 [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath Eric Woudstra
` (2 preceding siblings ...)
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-07 9:10 ` 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
` (3 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Eric Woudstra @ 2026-07-07 9:10 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Pablo Neira Ayuso, Florian Westphal,
Phil Sutter, Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten
Cc: netdev, netfilter-devel, bridge, Eric Woudstra
This will allow a flowtable to be added to the nft bridge family.
Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
---
net/netfilter/nf_flow_table_inet.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/net/netfilter/nf_flow_table_inet.c b/net/netfilter/nf_flow_table_inet.c
index b0f1991719324..80b238196f29b 100644
--- a/net/netfilter/nf_flow_table_inet.c
+++ b/net/netfilter/nf_flow_table_inet.c
@@ -65,6 +65,16 @@ static int nf_flow_rule_route_inet(struct net *net,
return err;
}
+static struct nf_flowtable_type flowtable_bridge = {
+ .family = NFPROTO_BRIDGE,
+ .init = nf_flow_table_init,
+ .setup = nf_flow_table_offload_setup,
+ .action = nf_flow_rule_bridge,
+ .free = nf_flow_table_free,
+ .hook = nf_flow_offload_inet_hook,
+ .owner = THIS_MODULE,
+};
+
static struct nf_flowtable_type flowtable_inet = {
.family = NFPROTO_INET,
.init = nf_flow_table_init,
@@ -97,6 +107,7 @@ static struct nf_flowtable_type flowtable_ipv6 = {
static int __init nf_flow_inet_module_init(void)
{
+ nft_register_flowtable_type(&flowtable_bridge);
nft_register_flowtable_type(&flowtable_ipv4);
nft_register_flowtable_type(&flowtable_ipv6);
nft_register_flowtable_type(&flowtable_inet);
@@ -109,6 +120,7 @@ static void __exit nf_flow_inet_module_exit(void)
nft_unregister_flowtable_type(&flowtable_inet);
nft_unregister_flowtable_type(&flowtable_ipv6);
nft_unregister_flowtable_type(&flowtable_ipv4);
+ nft_unregister_flowtable_type(&flowtable_bridge);
}
module_init(nf_flow_inet_module_init);
@@ -118,5 +130,6 @@ MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");
MODULE_ALIAS_NF_FLOWTABLE(AF_INET);
MODULE_ALIAS_NF_FLOWTABLE(AF_INET6);
+MODULE_ALIAS_NF_FLOWTABLE(AF_BRIDGE);
MODULE_ALIAS_NF_FLOWTABLE(1); /* NFPROTO_INET */
MODULE_DESCRIPTION("Netfilter flow table mixed IPv4/IPv6 module");
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v12 nf-next 5/7] netfilter: nft_flow_offload: nft_flow_offload_eval: check thoff==0
2026-07-07 9:10 [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath Eric Woudstra
` (3 preceding siblings ...)
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 ` 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
` (2 subsequent siblings)
7 siblings, 1 reply; 14+ messages in thread
From: Eric Woudstra @ 2026-07-07 9:10 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Pablo Neira Ayuso, Florian Westphal,
Phil Sutter, Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten
Cc: netdev, netfilter-devel, bridge, Eric Woudstra
In case of flow through bridge, when evaluating traffic with double vlan,
pppoe and pppoe-in-q. In this case thoff will be valid only when meta has
been processed. If meta was not processed in nftables, thoff is zero.
Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
---
net/netfilter/nft_flow_offload.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c
index f8c7f9f631e48..4f68fb64f1657 100644
--- a/net/netfilter/nft_flow_offload.c
+++ b/net/netfilter/nft_flow_offload.c
@@ -59,7 +59,7 @@ static void nft_flow_offload_eval(const struct nft_expr *expr,
struct flow_offload *flow;
enum ip_conntrack_dir dir;
struct nf_conn *ct;
- int ret;
+ int ret, thoff;
if (nft_flow_offload_skip(pkt->skb, nft_pf(pkt)))
goto out;
@@ -70,8 +70,11 @@ static void nft_flow_offload_eval(const struct nft_expr *expr,
switch (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum) {
case IPPROTO_TCP:
- tcph = skb_header_pointer(pkt->skb, nft_thoff(pkt),
- sizeof(_tcph), &_tcph);
+ thoff = nft_thoff(pkt);
+ if (thoff == 0)
+ goto out;
+ tcph = skb_header_pointer(pkt->skb, thoff, sizeof(_tcph),
+ &_tcph);
if (unlikely(!tcph || tcph->fin || tcph->rst ||
!nf_conntrack_tcp_established(ct)))
goto out;
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v12 nf-next 6/7] netfilter: nft_flow_offload: Add NFPROTO_BRIDGE to validate
2026-07-07 9:10 [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath Eric Woudstra
` (4 preceding siblings ...)
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-07 9:10 ` 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:47 ` [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath Pablo Neira Ayuso
7 siblings, 0 replies; 14+ messages in thread
From: Eric Woudstra @ 2026-07-07 9:10 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Pablo Neira Ayuso, Florian Westphal,
Phil Sutter, Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten
Cc: netdev, netfilter-devel, bridge, Eric Woudstra
Need to add NFPROTO_BRIDGE to nft_flow_offload_validate() to support
the bridge-fastpath.
Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
---
net/netfilter/nft_flow_offload.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c
index 4f68fb64f1657..0be62841155b6 100644
--- a/net/netfilter/nft_flow_offload.c
+++ b/net/netfilter/nft_flow_offload.c
@@ -179,7 +179,8 @@ static int nft_flow_offload_validate(const struct nft_ctx *ctx,
if (ctx->family != NFPROTO_IPV4 &&
ctx->family != NFPROTO_IPV6 &&
- ctx->family != NFPROTO_INET)
+ ctx->family != NFPROTO_INET &&
+ ctx->family != NFPROTO_BRIDGE)
return -EOPNOTSUPP;
return nft_chain_validate_hooks(ctx->chain, hook_mask);
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v12 nf-next 7/7] netfilter: nft_flow_offload: Add bridgeflow to nft_flow_offload_eval()
2026-07-07 9:10 [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath Eric Woudstra
` (5 preceding siblings ...)
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 ` Eric Woudstra
2026-07-08 9:52 ` Pablo Neira Ayuso
2026-07-08 9:47 ` [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath Pablo Neira Ayuso
7 siblings, 1 reply; 14+ messages in thread
From: Eric Woudstra @ 2026-07-07 9:10 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Pablo Neira Ayuso, Florian Westphal,
Phil Sutter, Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten
Cc: netdev, netfilter-devel, bridge, Eric Woudstra
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.
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++;
+ }
+ 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;
+
+ 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;
+ }
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
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath
2026-07-07 9:10 [PATCH v12 nf-next 0/7] netfilter: Add bridge-fastpath Eric Woudstra
` (6 preceding siblings ...)
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:47 ` Pablo Neira Ayuso
7 siblings, 0 replies; 14+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-08 9:47 UTC (permalink / raw)
To: Eric Woudstra
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Florian Westphal, Phil Sutter,
Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten, netdev, netfilter-devel, bridge
Hi Eric,
On Tue, Jul 07, 2026 at 11:10:38AM +0200, Eric Woudstra wrote:
> This patchset makes it possible to set up a software fastpath between
> bridged interfaces. One patch adds the flow rule for the hardware
> fastpath. This creates the possibility to have a hardware offloaded
> fastpath between bridged interfaces. More patches are added to solve
> issues found with the existing code.
Thanks for your series.
I posted an alternative series, including one of your patches for the
bridge vlan filtering support (which is still untested on my side):
https://lore.kernel.org/netfilter-devel/20260708093250.1187068-1-pablo@netfilter.org/T/#m270aedab59bf39f1bc4452d1d8d739a2b1b0bc45
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v12 nf-next 3/7] netfilter: nf_flow_table_offload: Add nf_flow_rule_bridge()
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
0 siblings, 1 reply; 14+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-08 9:48 UTC (permalink / raw)
To: Eric Woudstra
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Florian Westphal, Phil Sutter,
Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten, netdev, netfilter-devel, bridge
Hi,
On Tue, Jul 07, 2026 at 11:10:41AM +0200, Eric Woudstra wrote:
> Add nf_flow_rule_bridge().
>
> It only calls the common rule and adds the redirect.
I decided to use the new _unsupp() function, so we don't pretend
bridge hw offload is already supported. We will need a driver before
we can add this, this stub does not provide much. I guess your goal
was just to avoid a crash here.
> Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
> Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
> ---
> include/net/netfilter/nf_flow_table.h | 3 +++
> net/netfilter/nf_flow_table_offload.c | 13 +++++++++++++
> 2 files changed, 16 insertions(+)
>
> diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
> index 7b23b245a5a86..5c6e3b65ae85b 100644
> --- a/include/net/netfilter/nf_flow_table.h
> +++ b/include/net/netfilter/nf_flow_table.h
> @@ -368,6 +368,9 @@ void nf_flow_table_offload_flush_cleanup(struct nf_flowtable *flowtable);
> int nf_flow_table_offload_setup(struct nf_flowtable *flowtable,
> struct net_device *dev,
> enum flow_block_command cmd);
> +int nf_flow_rule_bridge(struct net *net, struct flow_offload *flow,
> + enum flow_offload_tuple_dir dir,
> + struct nf_flow_rule *flow_rule);
> int nf_flow_rule_route_ipv4(struct net *net, struct flow_offload *flow,
> enum flow_offload_tuple_dir dir,
> struct nf_flow_rule *flow_rule);
> diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
> index 002ec15d988bd..5566ebda7b7d3 100644
> --- a/net/netfilter/nf_flow_table_offload.c
> +++ b/net/netfilter/nf_flow_table_offload.c
> @@ -740,6 +740,19 @@ nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
> return 0;
> }
>
> +int nf_flow_rule_bridge(struct net *net, struct flow_offload *flow,
> + enum flow_offload_tuple_dir dir,
> + struct nf_flow_rule *flow_rule)
> +{
> + if (nf_flow_rule_route_common(net, flow, dir, flow_rule) < 0)
> + return -1;
> +
> + flow_offload_redirect(net, flow, dir, flow_rule);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(nf_flow_rule_bridge);
> +
> int nf_flow_rule_route_ipv4(struct net *net, struct flow_offload *flow,
> enum flow_offload_tuple_dir dir,
> struct nf_flow_rule *flow_rule)
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v12 nf-next 7/7] netfilter: nft_flow_offload: Add bridgeflow to nft_flow_offload_eval()
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
2026-07-08 19:14 ` Eric Woudstra
0 siblings, 1 reply; 14+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-08 9:52 UTC (permalink / raw)
To: Eric Woudstra
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Florian Westphal, Phil Sutter,
Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten, netdev, netfilter-devel, bridge
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
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v12 nf-next 5/7] netfilter: nft_flow_offload: nft_flow_offload_eval: check thoff==0
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
0 siblings, 0 replies; 14+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-08 9:54 UTC (permalink / raw)
To: Eric Woudstra
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Florian Westphal, Phil Sutter,
Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten, netdev, netfilter-devel, bridge
Hi,
On Tue, Jul 07, 2026 at 11:10:43AM +0200, Eric Woudstra wrote:
> In case of flow through bridge, when evaluating traffic with double vlan,
> pppoe and pppoe-in-q. In this case thoff will be valid only when meta has
> been processed. If meta was not processed in nftables, thoff is zero.
>
> Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
> ---
> net/netfilter/nft_flow_offload.c | 9 ++++++---
> 1 file changed, 6 insertions(+), 3 deletions(-)
>
> diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c
> index f8c7f9f631e48..4f68fb64f1657 100644
> --- a/net/netfilter/nft_flow_offload.c
> +++ b/net/netfilter/nft_flow_offload.c
> @@ -59,7 +59,7 @@ static void nft_flow_offload_eval(const struct nft_expr *expr,
> struct flow_offload *flow;
> enum ip_conntrack_dir dir;
> struct nf_conn *ct;
> - int ret;
> + int ret, thoff;
>
> if (nft_flow_offload_skip(pkt->skb, nft_pf(pkt)))
> goto out;
> @@ -70,8 +70,11 @@ static void nft_flow_offload_eval(const struct nft_expr *expr,
>
> switch (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum) {
> case IPPROTO_TCP:
> - tcph = skb_header_pointer(pkt->skb, nft_thoff(pkt),
> - sizeof(_tcph), &_tcph);
> + thoff = nft_thoff(pkt);
> + if (thoff == 0)
> + goto out;
I addressed this by checking pkt->flags. I promised a helper to
Florian to improve readability here, but I still have to come back
with such patch. Basically, my assumption is that pkt->flags is unset
if no IP packet has been parsed.
> + tcph = skb_header_pointer(pkt->skb, thoff, sizeof(_tcph),
> + &_tcph);
> if (unlikely(!tcph || tcph->fin || tcph->rst ||
> !nf_conntrack_tcp_established(ct)))
> goto out;
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v12 nf-next 3/7] netfilter: nf_flow_table_offload: Add nf_flow_rule_bridge()
2026-07-08 9:48 ` Pablo Neira Ayuso
@ 2026-07-08 18:36 ` Eric Woudstra
0 siblings, 0 replies; 14+ messages in thread
From: Eric Woudstra @ 2026-07-08 18:36 UTC (permalink / raw)
To: Pablo Neira Ayuso
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Florian Westphal, Phil Sutter,
Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten, netdev, netfilter-devel, bridge
On 7/8/26 11:48 AM, Pablo Neira Ayuso wrote:
> Hi,
>
> On Tue, Jul 07, 2026 at 11:10:41AM +0200, Eric Woudstra wrote:
>> Add nf_flow_rule_bridge().
>>
>> It only calls the common rule and adds the redirect.
>
> I decided to use the new _unsupp() function, so we don't pretend
> bridge hw offload is already supported. We will need a driver before
> we can add this, this stub does not provide much. I guess your goal
> was just to avoid a crash here.
>
No, I am already using hw_offload between bridged interfaces
on the mt7986 succesfully for almost 2 years.
It works dsa-port to direct interface (lan1 to eth1 on Bananapi R3) and
between direct interfaces (eth0 to eth1 on Bananapi-R3-mini)
It can also be tested with my bridge_fastpath.sh selftest script.
This script uses veth-device pairs to test the software fastpath.
It can also use 2 real interfaces interconnected in a loop of copper,
when chosen with commandline arguments. Then it tests software- and
hardware-fastpath. It also tests many different scenarios.
So this is why I've added it, as it is already functional. If a software
fastpath is setup correctly, the hardware fastpath is also functional.
>> Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
>> Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
>> ---
>> include/net/netfilter/nf_flow_table.h | 3 +++
>> net/netfilter/nf_flow_table_offload.c | 13 +++++++++++++
>> 2 files changed, 16 insertions(+)
>>
>> diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
>> index 7b23b245a5a86..5c6e3b65ae85b 100644
>> --- a/include/net/netfilter/nf_flow_table.h
>> +++ b/include/net/netfilter/nf_flow_table.h
>> @@ -368,6 +368,9 @@ void nf_flow_table_offload_flush_cleanup(struct nf_flowtable *flowtable);
>> int nf_flow_table_offload_setup(struct nf_flowtable *flowtable,
>> struct net_device *dev,
>> enum flow_block_command cmd);
>> +int nf_flow_rule_bridge(struct net *net, struct flow_offload *flow,
>> + enum flow_offload_tuple_dir dir,
>> + struct nf_flow_rule *flow_rule);
>> int nf_flow_rule_route_ipv4(struct net *net, struct flow_offload *flow,
>> enum flow_offload_tuple_dir dir,
>> struct nf_flow_rule *flow_rule);
>> diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
>> index 002ec15d988bd..5566ebda7b7d3 100644
>> --- a/net/netfilter/nf_flow_table_offload.c
>> +++ b/net/netfilter/nf_flow_table_offload.c
>> @@ -740,6 +740,19 @@ nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
>> return 0;
>> }
>>
>> +int nf_flow_rule_bridge(struct net *net, struct flow_offload *flow,
>> + enum flow_offload_tuple_dir dir,
>> + struct nf_flow_rule *flow_rule)
>> +{
>> + if (nf_flow_rule_route_common(net, flow, dir, flow_rule) < 0)
>> + return -1;
>> +
>> + flow_offload_redirect(net, flow, dir, flow_rule);
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(nf_flow_rule_bridge);
>> +
>> int nf_flow_rule_route_ipv4(struct net *net, struct flow_offload *flow,
>> enum flow_offload_tuple_dir dir,
>> struct nf_flow_rule *flow_rule)
>> --
>> 2.53.0
>>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v12 nf-next 7/7] netfilter: nft_flow_offload: Add bridgeflow to nft_flow_offload_eval()
2026-07-08 9:52 ` Pablo Neira Ayuso
@ 2026-07-08 19:14 ` Eric Woudstra
0 siblings, 0 replies; 14+ messages in thread
From: Eric Woudstra @ 2026-07-08 19:14 UTC (permalink / raw)
To: Pablo Neira Ayuso
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Florian Westphal, Phil Sutter,
Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
Martin Karsten, netdev, netfilter-devel, bridge
On 7/8/26 11:52 AM, Pablo Neira Ayuso wrote:
> 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.
>
This part is used for handling packets that are incoming with encaps.
Previously I got the encaps from the skb and stored them in
th[dir].tuple.encap. I understand you want to do this differently.
It is also why I'm starting in the opposite direction as in you patch-set.
The code above then reads th[dir].tuple.encap and starts filling info
and ctx with encaps. It also handles encaps marked with a
in_vlan_ingress bit. Once info and ctx are filled with the encaps from
the skb, it can continue with dev_fill_bridge_path().
My selftest script bridge_fastpath.sh tests this code thoroughly in many
different scenarios. Incoming vlan, q-in-q, ad, pppoe, pppoe-in-q. Also
on a dsa-port with the in_vlan_ingress bit set.
>> + 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.
>
I have not experienced this, but I understand that this is no guarantee
that it does not happen at all.
>> + 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
>>
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-07-08 19:14 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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-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-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
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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox