* [PATCH] netfilter: flowtable: resolve LAG slave for direct HW offload @ 2026-05-25 16:24 Jihong Min 2026-05-25 16:33 ` Jihong Min 0 siblings, 1 reply; 4+ messages in thread From: Jihong Min @ 2026-05-25 16:24 UTC (permalink / raw) To: Pablo Neira Ayuso, Florian Westphal Cc: Phil Sutter, coreteam, netfilter-devel, linux-kernel, Jihong Min FLOW_OFFLOAD_XMIT_DIRECT path discovery can stop at a LAG master because the real egress port is selected later through ndo_get_xmit_slave(). Hardware flow offload drivers that program per-port redirects need the selected lower device, while software forwarding must still transmit through the LAG master. Keep the route tuple software egress ifindex on the LAG master and carry a separate hardware redirect ifindex. When the direct egress device is a LAG master, resolve the selected slave with netdev_get_xmit_slave(), verify that it belongs to the flowtable, and store it as the hardware redirect device. Signed-off-by: Jihong Min <hurryman2212@gmail.com> --- include/net/netfilter/nf_flow_table.h | 1 + net/netfilter/nf_flow_table_core.c | 1 + net/netfilter/nf_flow_table_offload.c | 2 +- net/netfilter/nf_flow_table_path.c | 34 ++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h index 7b23b245a5a8..ada9db7e5c38 100644 --- a/include/net/netfilter/nf_flow_table.h +++ b/include/net/netfilter/nf_flow_table.h @@ -163,6 +163,7 @@ struct flow_offload_tuple { }; struct { u32 ifidx; + u32 hw_ifidx; u8 h_source[ETH_ALEN]; u8 h_dest[ETH_ALEN]; } out; diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c index 785d8c244a77..bc329420f882 100644 --- a/net/netfilter/nf_flow_table_core.c +++ b/net/netfilter/nf_flow_table_core.c @@ -132,6 +132,7 @@ static int flow_offload_fill_route(struct flow_offload *flow, memcpy(flow_tuple->out.h_source, route->tuple[dir].out.h_source, ETH_ALEN); flow_tuple->out.ifidx = route->tuple[dir].out.ifindex; + flow_tuple->out.hw_ifidx = route->tuple[dir].out.hw_ifindex; dst_release(dst); break; case FLOW_OFFLOAD_XMIT_XFRM: diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c index 002ec15d988b..7c46baa1546d 100644 --- a/net/netfilter/nf_flow_table_offload.c +++ b/net/netfilter/nf_flow_table_offload.c @@ -596,7 +596,7 @@ static int flow_offload_redirect(struct net *net, switch (this_tuple->xmit_type) { case FLOW_OFFLOAD_XMIT_DIRECT: this_tuple = &flow->tuplehash[dir].tuple; - ifindex = this_tuple->out.ifidx; + ifindex = this_tuple->out.hw_ifidx; break; case FLOW_OFFLOAD_XMIT_NEIGH: other_tuple = &flow->tuplehash[!dir].tuple; diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 9e88ea6a2eef..10f38ca27a6f 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -5,6 +5,7 @@ #include <linux/etherdevice.h> #include <linux/netlink.h> #include <linux/netfilter.h> +#include <linux/netdevice.h> #include <linux/spinlock.h> #include <linux/netfilter/nf_conntrack_common.h> #include <linux/netfilter/nf_tables.h> @@ -76,6 +77,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 *hw_outdev; struct id { __u16 id; __be16 proto; @@ -179,6 +181,7 @@ static void nft_dev_path_info(const struct net_device_path_stack *stack, } } info->outdev = info->indev; + info->hw_outdev = info->indev; if (nf_flowtable_hw_offload(flowtable) && nft_is_valid_ether_device(info->indev)) @@ -250,6 +253,7 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, struct net_device_path_stack stack; struct nft_forward_info info = {}; unsigned char ha[ETH_ALEN]; + struct net_device *lag_slave = NULL; int i; if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0) @@ -258,9 +262,34 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, if (info.outdev) route->tuple[dir].out.ifindex = info.outdev->ifindex; - if (!info.indev || !nft_flowtable_find_dev(info.indev, ft)) + if (!info.indev) return; + if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT && + netif_is_lag_master(info.hw_outdev)) { + rcu_read_lock(); + lag_slave = netdev_get_xmit_slave((struct net_device *)info.hw_outdev, + pkt->skb, false); + if (lag_slave) + dev_hold(lag_slave); + rcu_read_unlock(); + + if (!lag_slave) + return; + + if (!nft_is_valid_ether_device(lag_slave)) { + dev_put(lag_slave); + return; + } + + info.hw_outdev = lag_slave; + } + + if (!nft_flowtable_find_dev(info.hw_outdev, ft)) { + dev_put(lag_slave); + return; + } + 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; @@ -281,9 +310,12 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT) { memcpy(route->tuple[dir].out.h_source, info.h_source, ETH_ALEN); memcpy(route->tuple[dir].out.h_dest, info.h_dest, ETH_ALEN); + route->tuple[dir].out.hw_ifindex = info.hw_outdev->ifindex; route->tuple[dir].xmit_type = info.xmit_type; } route->tuple[dir].out.needs_gso_segment = info.needs_gso_segment; + + dev_put(lag_slave); } int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct, ^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] netfilter: flowtable: resolve LAG slave for direct HW offload 2026-05-25 16:24 [PATCH] netfilter: flowtable: resolve LAG slave for direct HW offload Jihong Min @ 2026-05-25 16:33 ` Jihong Min 2026-05-25 17:25 ` Pablo Neira Ayuso 0 siblings, 1 reply; 4+ messages in thread From: Jihong Min @ 2026-05-25 16:33 UTC (permalink / raw) To: Pablo Neira Ayuso, Florian Westphal Cc: Phil Sutter, coreteam, netfilter-devel, linux-kernel Sorry for the noise. While preparing the git-send-email command, I noticed that the subject prefix was not set correctly. This should have been sent with the nf-next prefix. I also noticed that the Assisted-by trailer was missing. Most of the patch was written by me, but I did get help from GPT-5.5 for some of the RCU and lifetime details, so the patch should have included: Assisted-by: Codex:gpt-5.5 Also, this change was tested on a Lumen W1700K2 with a Linux 6.18 OpenWrt-based image, where it enabled flow offload in a bonding setup. I have also applied the same diff on top of nf-next and completed a compile test there. I checked that the relevant infrastructure for bonding flow offload support is identical between the tested tree and nf-next. I will be more careful in the next submission and will correct this there. Best regards, Jihong On 5/26/26 01:24, Jihong Min wrote: > FLOW_OFFLOAD_XMIT_DIRECT path discovery can stop at a LAG master because > the real egress port is selected later through ndo_get_xmit_slave(). > Hardware flow offload drivers that program per-port redirects need the > selected lower device, while software forwarding must still transmit > through the LAG master. > > Keep the route tuple software egress ifindex on the LAG master and carry > a separate hardware redirect ifindex. When the direct egress device is a > LAG master, resolve the selected slave with netdev_get_xmit_slave(), > verify that it belongs to the flowtable, and store it as the hardware > redirect device. > > Signed-off-by: Jihong Min <hurryman2212@gmail.com> > --- > include/net/netfilter/nf_flow_table.h | 1 + > net/netfilter/nf_flow_table_core.c | 1 + > net/netfilter/nf_flow_table_offload.c | 2 +- > net/netfilter/nf_flow_table_path.c | 34 ++++++++++++++++++++++++++- > 4 files changed, 36 insertions(+), 2 deletions(-) > > diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h > index 7b23b245a5a8..ada9db7e5c38 100644 > --- a/include/net/netfilter/nf_flow_table.h > +++ b/include/net/netfilter/nf_flow_table.h > @@ -163,6 +163,7 @@ struct flow_offload_tuple { > }; > struct { > u32 ifidx; > + u32 hw_ifidx; > u8 h_source[ETH_ALEN]; > u8 h_dest[ETH_ALEN]; > } out; > diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c > index 785d8c244a77..bc329420f882 100644 > --- a/net/netfilter/nf_flow_table_core.c > +++ b/net/netfilter/nf_flow_table_core.c > @@ -132,6 +132,7 @@ static int flow_offload_fill_route(struct flow_offload *flow, > memcpy(flow_tuple->out.h_source, route->tuple[dir].out.h_source, > ETH_ALEN); > flow_tuple->out.ifidx = route->tuple[dir].out.ifindex; > + flow_tuple->out.hw_ifidx = route->tuple[dir].out.hw_ifindex; > dst_release(dst); > break; > case FLOW_OFFLOAD_XMIT_XFRM: > diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c > index 002ec15d988b..7c46baa1546d 100644 > --- a/net/netfilter/nf_flow_table_offload.c > +++ b/net/netfilter/nf_flow_table_offload.c > @@ -596,7 +596,7 @@ static int flow_offload_redirect(struct net *net, > switch (this_tuple->xmit_type) { > case FLOW_OFFLOAD_XMIT_DIRECT: > this_tuple = &flow->tuplehash[dir].tuple; > - ifindex = this_tuple->out.ifidx; > + ifindex = this_tuple->out.hw_ifidx; > break; > case FLOW_OFFLOAD_XMIT_NEIGH: > other_tuple = &flow->tuplehash[!dir].tuple; > diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c > index 9e88ea6a2eef..10f38ca27a6f 100644 > --- a/net/netfilter/nf_flow_table_path.c > +++ b/net/netfilter/nf_flow_table_path.c > @@ -5,6 +5,7 @@ > #include <linux/etherdevice.h> > #include <linux/netlink.h> > #include <linux/netfilter.h> > +#include <linux/netdevice.h> > #include <linux/spinlock.h> > #include <linux/netfilter/nf_conntrack_common.h> > #include <linux/netfilter/nf_tables.h> > @@ -76,6 +77,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 *hw_outdev; > struct id { > __u16 id; > __be16 proto; > @@ -179,6 +181,7 @@ static void nft_dev_path_info(const struct net_device_path_stack *stack, > } > } > info->outdev = info->indev; > + info->hw_outdev = info->indev; > > if (nf_flowtable_hw_offload(flowtable) && > nft_is_valid_ether_device(info->indev)) > @@ -250,6 +253,7 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, > struct net_device_path_stack stack; > struct nft_forward_info info = {}; > unsigned char ha[ETH_ALEN]; > + struct net_device *lag_slave = NULL; > int i; > > if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0) > @@ -258,9 +262,34 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, > if (info.outdev) > route->tuple[dir].out.ifindex = info.outdev->ifindex; > > - if (!info.indev || !nft_flowtable_find_dev(info.indev, ft)) > + if (!info.indev) > return; > > + if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT && > + netif_is_lag_master(info.hw_outdev)) { > + rcu_read_lock(); > + lag_slave = netdev_get_xmit_slave((struct net_device *)info.hw_outdev, > + pkt->skb, false); > + if (lag_slave) > + dev_hold(lag_slave); > + rcu_read_unlock(); > + > + if (!lag_slave) > + return; > + > + if (!nft_is_valid_ether_device(lag_slave)) { > + dev_put(lag_slave); > + return; > + } > + > + info.hw_outdev = lag_slave; > + } > + > + if (!nft_flowtable_find_dev(info.hw_outdev, ft)) { > + dev_put(lag_slave); > + return; > + } > + > 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; > @@ -281,9 +310,12 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, > if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT) { > memcpy(route->tuple[dir].out.h_source, info.h_source, ETH_ALEN); > memcpy(route->tuple[dir].out.h_dest, info.h_dest, ETH_ALEN); > + route->tuple[dir].out.hw_ifindex = info.hw_outdev->ifindex; > route->tuple[dir].xmit_type = info.xmit_type; > } > route->tuple[dir].out.needs_gso_segment = info.needs_gso_segment; > + > + dev_put(lag_slave); > } > > int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct, ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] netfilter: flowtable: resolve LAG slave for direct HW offload 2026-05-25 16:33 ` Jihong Min @ 2026-05-25 17:25 ` Pablo Neira Ayuso 2026-05-25 23:51 ` Jihong Min 0 siblings, 1 reply; 4+ messages in thread From: Pablo Neira Ayuso @ 2026-05-25 17:25 UTC (permalink / raw) To: Jihong Min Cc: Florian Westphal, Phil Sutter, coreteam, netfilter-devel, linux-kernel On Tue, May 26, 2026 at 01:33:09AM +0900, Jihong Min wrote: > Sorry for the noise. > > While preparing the git-send-email command, I noticed that the subject > prefix was not set correctly. This should have been sent with the > nf-next prefix. > > I also noticed that the Assisted-by trailer was missing. Most of the > patch was written by me, but I did get help from GPT-5.5 for some of > the RCU and lifetime details, so the patch should have included: > > Assisted-by: Codex:gpt-5.5 > > Also, this change was tested on a Lumen W1700K2 with a Linux 6.18 > OpenWrt-based image, where it enabled flow offload in a bonding setup. > I have also applied the same diff on top of nf-next and completed a > compile test there. I checked that the relevant infrastructure for > bonding flow offload support is identical between the tested tree and > nf-next. Can you make this work with fill_forward_path in the bonding device? > I will be more careful in the next submission and will correct this > there. > > Best regards, > Jihong > > On 5/26/26 01:24, Jihong Min wrote: > > FLOW_OFFLOAD_XMIT_DIRECT path discovery can stop at a LAG master because > > the real egress port is selected later through ndo_get_xmit_slave(). > > Hardware flow offload drivers that program per-port redirects need the > > selected lower device, while software forwarding must still transmit > > through the LAG master. > > > > Keep the route tuple software egress ifindex on the LAG master and carry > > a separate hardware redirect ifindex. When the direct egress device is a > > LAG master, resolve the selected slave with netdev_get_xmit_slave(), > > verify that it belongs to the flowtable, and store it as the hardware > > redirect device. > > > > Signed-off-by: Jihong Min <hurryman2212@gmail.com> > > --- > > include/net/netfilter/nf_flow_table.h | 1 + > > net/netfilter/nf_flow_table_core.c | 1 + > > net/netfilter/nf_flow_table_offload.c | 2 +- > > net/netfilter/nf_flow_table_path.c | 34 ++++++++++++++++++++++++++- > > 4 files changed, 36 insertions(+), 2 deletions(-) > > > > diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h > > index 7b23b245a5a8..ada9db7e5c38 100644 > > --- a/include/net/netfilter/nf_flow_table.h > > +++ b/include/net/netfilter/nf_flow_table.h > > @@ -163,6 +163,7 @@ struct flow_offload_tuple { > > }; > > struct { > > u32 ifidx; > > + u32 hw_ifidx; > > u8 h_source[ETH_ALEN]; > > u8 h_dest[ETH_ALEN]; > > } out; > > diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c > > index 785d8c244a77..bc329420f882 100644 > > --- a/net/netfilter/nf_flow_table_core.c > > +++ b/net/netfilter/nf_flow_table_core.c > > @@ -132,6 +132,7 @@ static int flow_offload_fill_route(struct flow_offload *flow, > > memcpy(flow_tuple->out.h_source, route->tuple[dir].out.h_source, > > ETH_ALEN); > > flow_tuple->out.ifidx = route->tuple[dir].out.ifindex; > > + flow_tuple->out.hw_ifidx = route->tuple[dir].out.hw_ifindex; > > dst_release(dst); > > break; > > case FLOW_OFFLOAD_XMIT_XFRM: > > diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c > > index 002ec15d988b..7c46baa1546d 100644 > > --- a/net/netfilter/nf_flow_table_offload.c > > +++ b/net/netfilter/nf_flow_table_offload.c > > @@ -596,7 +596,7 @@ static int flow_offload_redirect(struct net *net, > > switch (this_tuple->xmit_type) { > > case FLOW_OFFLOAD_XMIT_DIRECT: > > this_tuple = &flow->tuplehash[dir].tuple; > > - ifindex = this_tuple->out.ifidx; > > + ifindex = this_tuple->out.hw_ifidx; > > break; > > case FLOW_OFFLOAD_XMIT_NEIGH: > > other_tuple = &flow->tuplehash[!dir].tuple; > > diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c > > index 9e88ea6a2eef..10f38ca27a6f 100644 > > --- a/net/netfilter/nf_flow_table_path.c > > +++ b/net/netfilter/nf_flow_table_path.c > > @@ -5,6 +5,7 @@ > > #include <linux/etherdevice.h> > > #include <linux/netlink.h> > > #include <linux/netfilter.h> > > +#include <linux/netdevice.h> > > #include <linux/spinlock.h> > > #include <linux/netfilter/nf_conntrack_common.h> > > #include <linux/netfilter/nf_tables.h> > > @@ -76,6 +77,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 *hw_outdev; > > struct id { > > __u16 id; > > __be16 proto; > > @@ -179,6 +181,7 @@ static void nft_dev_path_info(const struct net_device_path_stack *stack, > > } > > } > > info->outdev = info->indev; > > + info->hw_outdev = info->indev; > > > > if (nf_flowtable_hw_offload(flowtable) && > > nft_is_valid_ether_device(info->indev)) > > @@ -250,6 +253,7 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, > > struct net_device_path_stack stack; > > struct nft_forward_info info = {}; > > unsigned char ha[ETH_ALEN]; > > + struct net_device *lag_slave = NULL; > > int i; > > > > if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0) > > @@ -258,9 +262,34 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, > > if (info.outdev) > > route->tuple[dir].out.ifindex = info.outdev->ifindex; > > > > - if (!info.indev || !nft_flowtable_find_dev(info.indev, ft)) > > + if (!info.indev) > > return; > > > > + if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT && > > + netif_is_lag_master(info.hw_outdev)) { > > + rcu_read_lock(); > > + lag_slave = netdev_get_xmit_slave((struct net_device *)info.hw_outdev, > > + pkt->skb, false); > > + if (lag_slave) > > + dev_hold(lag_slave); > > + rcu_read_unlock(); > > + > > + if (!lag_slave) > > + return; > > + > > + if (!nft_is_valid_ether_device(lag_slave)) { > > + dev_put(lag_slave); > > + return; > > + } > > + > > + info.hw_outdev = lag_slave; > > + } > > + > > + if (!nft_flowtable_find_dev(info.hw_outdev, ft)) { > > + dev_put(lag_slave); > > + return; > > + } > > + > > 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; > > @@ -281,9 +310,12 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, > > if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT) { > > memcpy(route->tuple[dir].out.h_source, info.h_source, ETH_ALEN); > > memcpy(route->tuple[dir].out.h_dest, info.h_dest, ETH_ALEN); > > + route->tuple[dir].out.hw_ifindex = info.hw_outdev->ifindex; > > route->tuple[dir].xmit_type = info.xmit_type; > > } > > route->tuple[dir].out.needs_gso_segment = info.needs_gso_segment; > > + > > + dev_put(lag_slave); > > } > > > > int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct, > ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] netfilter: flowtable: resolve LAG slave for direct HW offload 2026-05-25 17:25 ` Pablo Neira Ayuso @ 2026-05-25 23:51 ` Jihong Min 0 siblings, 0 replies; 4+ messages in thread From: Jihong Min @ 2026-05-25 23:51 UTC (permalink / raw) To: Pablo Neira Ayuso Cc: Florian Westphal, Phil Sutter, coreteam, netfilter-devel, linux-kernel On 5/26/26 02:25, Pablo Neira Ayuso wrote: > On Tue, May 26, 2026 at 01:33:09AM +0900, Jihong Min wrote: >> Sorry for the noise. >> >> While preparing the git-send-email command, I noticed that the subject >> prefix was not set correctly. This should have been sent with the >> nf-next prefix. >> >> I also noticed that the Assisted-by trailer was missing. Most of the >> patch was written by me, but I did get help from GPT-5.5 for some of >> the RCU and lifetime details, so the patch should have included: >> >> Assisted-by: Codex:gpt-5.5 >> >> Also, this change was tested on a Lumen W1700K2 with a Linux 6.18 >> OpenWrt-based image, where it enabled flow offload in a bonding setup. >> I have also applied the same diff on top of nf-next and completed a >> compile test there. I checked that the relevant infrastructure for >> bonding flow offload support is identical between the tested tree and >> nf-next. > > Can you make this work with fill_forward_path in the bonding device? > Hi Pablo, Sure, I will do that. The scope became a bit wider than I initially expected, since this now needs the generic forward path extension, bonding support, and the netfilter flowtable change to fit together cleanly. I will send the next submission against net-next, with netdev as the main target list. Sincerely, Jihong Min >> I will be more careful in the next submission and will correct this >> there. >> >> Best regards, >> Jihong >> >> On 5/26/26 01:24, Jihong Min wrote: >>> FLOW_OFFLOAD_XMIT_DIRECT path discovery can stop at a LAG master because >>> the real egress port is selected later through ndo_get_xmit_slave(). >>> Hardware flow offload drivers that program per-port redirects need the >>> selected lower device, while software forwarding must still transmit >>> through the LAG master. >>> >>> Keep the route tuple software egress ifindex on the LAG master and carry >>> a separate hardware redirect ifindex. When the direct egress device is a >>> LAG master, resolve the selected slave with netdev_get_xmit_slave(), >>> verify that it belongs to the flowtable, and store it as the hardware >>> redirect device. >>> >>> Signed-off-by: Jihong Min <hurryman2212@gmail.com> >>> --- >>> include/net/netfilter/nf_flow_table.h | 1 + >>> net/netfilter/nf_flow_table_core.c | 1 + >>> net/netfilter/nf_flow_table_offload.c | 2 +- >>> net/netfilter/nf_flow_table_path.c | 34 ++++++++++++++++++++++++++- >>> 4 files changed, 36 insertions(+), 2 deletions(-) >>> >>> diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h >>> index 7b23b245a5a8..ada9db7e5c38 100644 >>> --- a/include/net/netfilter/nf_flow_table.h >>> +++ b/include/net/netfilter/nf_flow_table.h >>> @@ -163,6 +163,7 @@ struct flow_offload_tuple { >>> }; >>> struct { >>> u32 ifidx; >>> + u32 hw_ifidx; >>> u8 h_source[ETH_ALEN]; >>> u8 h_dest[ETH_ALEN]; >>> } out; >>> diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c >>> index 785d8c244a77..bc329420f882 100644 >>> --- a/net/netfilter/nf_flow_table_core.c >>> +++ b/net/netfilter/nf_flow_table_core.c >>> @@ -132,6 +132,7 @@ static int flow_offload_fill_route(struct flow_offload *flow, >>> memcpy(flow_tuple->out.h_source, route->tuple[dir].out.h_source, >>> ETH_ALEN); >>> flow_tuple->out.ifidx = route->tuple[dir].out.ifindex; >>> + flow_tuple->out.hw_ifidx = route->tuple[dir].out.hw_ifindex; >>> dst_release(dst); >>> break; >>> case FLOW_OFFLOAD_XMIT_XFRM: >>> diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c >>> index 002ec15d988b..7c46baa1546d 100644 >>> --- a/net/netfilter/nf_flow_table_offload.c >>> +++ b/net/netfilter/nf_flow_table_offload.c >>> @@ -596,7 +596,7 @@ static int flow_offload_redirect(struct net *net, >>> switch (this_tuple->xmit_type) { >>> case FLOW_OFFLOAD_XMIT_DIRECT: >>> this_tuple = &flow->tuplehash[dir].tuple; >>> - ifindex = this_tuple->out.ifidx; >>> + ifindex = this_tuple->out.hw_ifidx; >>> break; >>> case FLOW_OFFLOAD_XMIT_NEIGH: >>> other_tuple = &flow->tuplehash[!dir].tuple; >>> diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c >>> index 9e88ea6a2eef..10f38ca27a6f 100644 >>> --- a/net/netfilter/nf_flow_table_path.c >>> +++ b/net/netfilter/nf_flow_table_path.c >>> @@ -5,6 +5,7 @@ >>> #include <linux/etherdevice.h> >>> #include <linux/netlink.h> >>> #include <linux/netfilter.h> >>> +#include <linux/netdevice.h> >>> #include <linux/spinlock.h> >>> #include <linux/netfilter/nf_conntrack_common.h> >>> #include <linux/netfilter/nf_tables.h> >>> @@ -76,6 +77,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 *hw_outdev; >>> struct id { >>> __u16 id; >>> __be16 proto; >>> @@ -179,6 +181,7 @@ static void nft_dev_path_info(const struct net_device_path_stack *stack, >>> } >>> } >>> info->outdev = info->indev; >>> + info->hw_outdev = info->indev; >>> >>> if (nf_flowtable_hw_offload(flowtable) && >>> nft_is_valid_ether_device(info->indev)) >>> @@ -250,6 +253,7 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, >>> struct net_device_path_stack stack; >>> struct nft_forward_info info = {}; >>> unsigned char ha[ETH_ALEN]; >>> + struct net_device *lag_slave = NULL; >>> int i; >>> >>> if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0) >>> @@ -258,9 +262,34 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, >>> if (info.outdev) >>> route->tuple[dir].out.ifindex = info.outdev->ifindex; >>> >>> - if (!info.indev || !nft_flowtable_find_dev(info.indev, ft)) >>> + if (!info.indev) >>> return; >>> >>> + if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT && >>> + netif_is_lag_master(info.hw_outdev)) { >>> + rcu_read_lock(); >>> + lag_slave = netdev_get_xmit_slave((struct net_device *)info.hw_outdev, >>> + pkt->skb, false); >>> + if (lag_slave) >>> + dev_hold(lag_slave); >>> + rcu_read_unlock(); >>> + >>> + if (!lag_slave) >>> + return; >>> + >>> + if (!nft_is_valid_ether_device(lag_slave)) { >>> + dev_put(lag_slave); >>> + return; >>> + } >>> + >>> + info.hw_outdev = lag_slave; >>> + } >>> + >>> + if (!nft_flowtable_find_dev(info.hw_outdev, ft)) { >>> + dev_put(lag_slave); >>> + return; >>> + } >>> + >>> 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; >>> @@ -281,9 +310,12 @@ static void nft_dev_forward_path(const struct nft_pktinfo *pkt, >>> if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT) { >>> memcpy(route->tuple[dir].out.h_source, info.h_source, ETH_ALEN); >>> memcpy(route->tuple[dir].out.h_dest, info.h_dest, ETH_ALEN); >>> + route->tuple[dir].out.hw_ifindex = info.hw_outdev->ifindex; >>> route->tuple[dir].xmit_type = info.xmit_type; >>> } >>> route->tuple[dir].out.needs_gso_segment = info.needs_gso_segment; >>> + >>> + dev_put(lag_slave); >>> } >>> >>> int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct, >> ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-05-25 23:51 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-05-25 16:24 [PATCH] netfilter: flowtable: resolve LAG slave for direct HW offload Jihong Min 2026-05-25 16:33 ` Jihong Min 2026-05-25 17:25 ` Pablo Neira Ayuso 2026-05-25 23:51 ` Jihong Min
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox