From nobody Mon Jun 8 22:52:42 2026 Received: from mail-pj1-f43.google.com (mail-pj1-f43.google.com [209.85.216.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2977C330662 for ; Mon, 25 May 2026 16:24:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779726274; cv=none; b=adcMzIokQdUrmJaCkBTFsSMI3Vm/0wnJmg4IXZOX3FsBsH2W2nrf3lzBYTVPfSiQ/omyGGHoxp+EcVrHo9khXIk2XfNo83c08NV+rwzJmNHtdCb0DeNx1s8yJWs/cssaghfCgp3IqeEBzim2God/d/qfl9WBManavgUlmC1aYcg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779726274; c=relaxed/simple; bh=XkMKBuU5V+3BeDo+GJTT4o5X9ZU0iua6PXBxHBn8K9I=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=MTDw23JJJRSCUorNDfshvoIzw5/NqxEaPqwbJXGqz7DAJ8bQZA4Oz6+yJZDRUWceCLl4MlBG2AsRNe5fDOR66gFhlKCS8//UQXcr8aMHPhW30xdghtfmSW6+XNm5jkGbC9JEyZF2roMPY+arAduH+bxlRvFuHGjGozCh2S+mI3E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=UH/z8CzJ; arc=none smtp.client-ip=209.85.216.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="UH/z8CzJ" Received: by mail-pj1-f43.google.com with SMTP id 98e67ed59e1d1-36af4b7840aso473884a91.3 for ; Mon, 25 May 2026 09:24:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779726271; x=1780331071; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=ZlpsTDeAuSNftCXZpxo4u072eQxRXT55x77vQlFxgZI=; b=UH/z8CzJhqjyChSFwscjLIJbp1tty3KBnArLhWIN+/aOZntCRBNWYlSo20Wdg72sG2 1wkhT2kT8eBDD/OOFDvKSMaQFCARRTx2SMI8CuuF6qNKAPf1uik6dxNZr67jfPUh6nz5 boQDqgtlSt7xyCIrKZg2NGSj38OIuIIz1lmxoW61sCB52RH1A8fKzwaE9vqbbuqNfSC3 LTlsJ8HeAQxpwQdWjFMy34l/7PU/RC6HV0oYd2UjkPWNdDfl/diNipM4QHUW8gcXI4Ru YAS+xW1eZTA5fC2A76l50R5gpAVUzcogRYIgngjycInrXvC6dei3ETx+3Ou6ZKiBawr1 MCbQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779726271; x=1780331071; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=ZlpsTDeAuSNftCXZpxo4u072eQxRXT55x77vQlFxgZI=; b=MDmTkbmEiBddUc5WtqzJmwm/ZpNR3Wwck9DHgVbQT8bumXmr0/I6ZTo2JgynrOfxi2 5uoGyc4WfK0qc/m8h3lehdEwUTCKQH5Eijl18k7pcu5wP+VK/flr5hSVsnl31d9y4jwH ha/Xi2eHTtu7yduN8KwGcK29m+3TcdJ2/km2xtNH50f1W2xcg5xG89ks9m0b3nivsATe YLrAYJdhi19x7DyPZzoagmNofB2Dh4r2uZ5e0ywC83IBRUVDYhUbV5AdJONl4TuuLZq/ h6muYQE+0We8Vb28uG7BibCwh3lV9cjlv9eJMSyV3pbLL3eV/SS8he8suCABbgOBmBqy LB0A== X-Forwarded-Encrypted: i=1; AFNElJ/EF2vaXeDpj0uVQ1ElmewROGAGKrwZwI1Zsi/TQr6m7fpL/OU32Ihld/bTvcF+iUDpHkebnNzAVFWmaeE=@vger.kernel.org X-Gm-Message-State: AOJu0YxCFEkF7zHpeK93xOjDEicKpp9XyDMVuKM9KImhzuCzkrC3ggYH ZkB5nm4oOLwtpG8Qa1MbJqMb4mRIQq7hYpoVGyN4q9MAseQIdei8jMVe X-Gm-Gg: Acq92OEMZnSvc0OR6AuffFonRDOkHc5t7pu4pChFG1EGJWhBRAJMzRlLOC8BN9+dElx b6MngKQJcjIrZqOVYH98Nolpsw+9epFoR2bpC5H4fKp3re/P0R0expH2BIZrH2NkEI8oGqwYMDt TAtfpjfnTSMdkhNfEZNG9vR8X2Hr33MBfFndLYPtDhAt2FDYC88MOyLg2IlrWmEKsZTcbhy1sP3 LiNeXOlkd2ilVzR9QAa7mGa0pZ/Xh748e+imHCWOkevqHWicVIh+16V6snHTVN+bkIvV/sncICB NwBiE949lzb08Y3i3IC38Fu4c784d+Yg7BxVeGh6kxJ1kBL3yRiKQ7PFTHywYh5bMRck9w9aPPO Q8ftxVbfoK2EuR9Ga2sGBRa/clrAq13c/FCWmRRRdJ78Haj7p+cXqQ8Xkx1E5+lkvk/xYj/iwzt qHXyxqrNF86FwFIT26BrhD X-Received: by 2002:a17:90b:2d50:b0:364:e97f:64e7 with SMTP id 98e67ed59e1d1-36a676372f2mr15441005a91.27.1779726271299; Mon, 25 May 2026 09:24:31 -0700 (PDT) Received: from mincom1 ([119.214.48.8]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-36a6f0baca7sm5744357a91.2.2026.05.25.09.24.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 25 May 2026 09:24:30 -0700 (PDT) From: Jihong Min To: Pablo Neira Ayuso , Florian Westphal Cc: Phil Sutter , coreteam@netfilter.org, netfilter-devel@vger.kernel.org, linux-kernel@vger.kernel.org, Jihong Min Subject: [PATCH] netfilter: flowtable: resolve LAG slave for direct HW offload Date: Tue, 26 May 2026 01:24:17 +0900 Message-ID: <20260525162417.366556-1-hurryman2212@gmail.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- 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_tab= le_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 =3D route->tuple[dir].out.ifindex; + flow_tuple->out.hw_ifidx =3D 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 =3D &flow->tuplehash[dir].tuple; - ifindex =3D this_tuple->out.ifidx; + ifindex =3D this_tuple->out.hw_ifidx; break; case FLOW_OFFLOAD_XMIT_NEIGH: other_tuple =3D &flow->tuplehash[!dir].tuple; diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_tab= le_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 #include #include +#include #include #include #include @@ -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_p= ath_stack *stack, } } info->outdev =3D info->indev; + info->hw_outdev =3D info->indev; =20 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_pktin= fo *pkt, struct net_device_path_stack stack; struct nft_forward_info info =3D {}; unsigned char ha[ETH_ALEN]; + struct net_device *lag_slave =3D NULL; int i; =20 if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >=3D 0) @@ -258,9 +262,34 @@ static void nft_dev_forward_path(const struct nft_pkti= nfo *pkt, if (info.outdev) route->tuple[dir].out.ifindex =3D info.outdev->ifindex; =20 - if (!info.indev || !nft_flowtable_find_dev(info.indev, ft)) + if (!info.indev) return; =20 + if (info.xmit_type =3D=3D FLOW_OFFLOAD_XMIT_DIRECT && + netif_is_lag_master(info.hw_outdev)) { + rcu_read_lock(); + lag_slave =3D 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 =3D lag_slave; + } + + if (!nft_flowtable_find_dev(info.hw_outdev, ft)) { + dev_put(lag_slave); + return; + } + route->tuple[!dir].in.ifindex =3D info.indev->ifindex; for (i =3D 0; i < info.num_encaps; i++) { route->tuple[!dir].in.encap[i].id =3D info.encap[i].id; @@ -281,9 +310,12 @@ static void nft_dev_forward_path(const struct nft_pkti= nfo *pkt, if (info.xmit_type =3D=3D 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 =3D info.hw_outdev->ifindex; route->tuple[dir].xmit_type =3D info.xmit_type; } route->tuple[dir].out.needs_gso_segment =3D info.needs_gso_segment; + + dev_put(lag_slave); } =20 int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct,