From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wr1-f49.google.com (mail-wr1-f49.google.com [209.85.221.49]) (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 C944739DBEF for ; Tue, 30 Jun 2026 06:58:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.49 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782802684; cv=none; b=H2FTMCVubywY/aD8TK5owv++mwfOIeqcCJtpDrw6IYGJtGub2bZGDwVUAloi2tTf76+gYdcD70fQtgzI7u7yRkqw0v6vfqFWaFdBMd2SU/kaiiTU+dF7JFHbu7Li47q8w3DhNZZU8V0dOZJ8S47sgMf7g0cbcxUvS+OBHoXpNt4= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782802684; c=relaxed/simple; bh=uFlsWrhJwtQf9yt8auqKp+aP7EpEMnGUWhzv1INgKXg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Vvw4NaO5Nb+B/Ndl66W3QwnvGTuEPMObKVg9Jl7FsE6cs9GCLNiQThWGHKHm1IXb29eHoq+D2droYj1xcgKcl9Q7WRlrAdXtFXaRKI5kdnzRguVhpm8DthwCDXkr8aho3lJ33YugaWQgtDuWQcl1WiysAbvOW1MKsKPRDXRYoGk= 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=kxD3iO+O; arc=none smtp.client-ip=209.85.221.49 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="kxD3iO+O" Received: by mail-wr1-f49.google.com with SMTP id ffacd0b85a97d-46ed4f66256so3282949f8f.3 for ; Mon, 29 Jun 2026 23:58:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782802681; x=1783407481; darn=lists.linux.dev; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=0ZkMfYGbmmBFNVQavhfnwkj1f6U3WSLYvJuEXnZXuTc=; b=kxD3iO+O4MK5fLLBs45J4IA6s+9QzzYFHc7Ce0FBDLOrfKbkxWtdGyu3m7VcMHgZae 5mVx4uMnbwU3g9yBp9ogvgrb04EJ1YgN5jh8sMJC3R/lFbq/iUUDONu0EideWt8PFYye rhtRcIQb55vvSmhu11UUkqTmzh/M5kZ0Zppx/hwslFOECVAm1pVaScr5ZcbRCF2NCOBY r+UuPx/vuL2fLPGAifv0KQOAUXIsWM1EUvKBHhbODZQ96nm3pKrIovYORXEUdTzJsj/K RIkViRCzRI3P9qaDlUZAkTdln4jia0jk7rsrho3It/bDpPJG+lnk+ghvDMSZfs4ZzpaS oSLQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782802681; x=1783407481; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=0ZkMfYGbmmBFNVQavhfnwkj1f6U3WSLYvJuEXnZXuTc=; b=F4Kdb1f8unp28sdRht+tKlsnTv4y7Wk/wXDDidCHgLKiU1rV1ARzoMYlkVzYLAxSgk HQr+7iYFo3jRMEX7z63JC4LrkQXuESc+ZvXqvrSFIiW9EkXV6/XfcaS3sxWilIYztJvL iOfYhQSeLh5ZFV/Ugpc0ZMAAnwh+c2kADFG2QYC40CbJV8mi7mQ63Ffbrx7wQxkUKgup MvTHLZSyyJMWZmR5ZvniFIIL6ptlIJ8ok9NWoFrgEWy/sVzO9xDPQ7/gcTqear3XGFR7 ICsaESBEpZn1fJSuOMcYSrtOzUxC/w8yw5WBevpWlvMXYFzYVZStdM8meKt20BsKI5a+ LzaA== X-Forwarded-Encrypted: i=1; AHgh+RoJ9bJnSh5bQt+uLWJNo/QLPeiHChfTD+WyBMB18aNy0xzIg1XczD0+QOeO/Pr+q3Kertbo4Ac=@lists.linux.dev X-Gm-Message-State: AOJu0YzZdfLLaF6NAu6kEKwcpFWyIhVvCyxfzN9HQ3YOgAkBaMRb+8r6 9n6NXp7YCorXBv2VzvpPL5yeLzbpN95Oxu/E/8pnsri0ChGLKdhB5Yay X-Gm-Gg: AfdE7ckBL1pQ5y0Tmul6dNF6DznnUjQvT1Sp2XGhF/S86tS9+lp04GJ0jDLiSDZmO7I QMZ0108OlfmqpY9PcK7LbR/wpsmIclQdg0kUya7nlklyyuhtZxNcuQyQFqTbljxuQTRnFgOcbgn cDZAJHbcYblsDSzP/muyolDF9koJDgo2dWjuqi+XptBmsW33WUzjIma6TcJej903+15TPRpr6+r X30GNQA4kEKQvoNCAiDo7jSwcWJAcGlFoARkN250M4q2T8/FE41w+ep3z66y7jIwv4BOnhiaZWf kjafLmn4T4VtMGmNOHESnfCUMrNB8rqst/2gkBQPaamAEIqPn0r/CqGAyGE5AvUvxzyCHXI0TtZ Zz7QUXr7y+/cZeCfITIBc6gt5zH7wgOzKdhoHkfpmYq/by8fSWJi7pqU+980h77nxjJhPgfj8By pMU9eHhSFgSoddXy1vfg== X-Received: by 2002:a05:6000:2dc7:b0:475:f0d1:eb6f with SMTP id ffacd0b85a97d-475f0d1ed54mr649075f8f.60.1782802681185; Mon, 29 Jun 2026 23:58:01 -0700 (PDT) Received: from fedora ([46.205.218.111]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-4756636cf26sm4570949f8f.19.2026.06.29.23.57.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 29 Jun 2026 23:58:00 -0700 (PDT) From: Daniel Pawlik To: netfilter-devel@vger.kernel.org, netdev@vger.kernel.org Cc: pablo@netfilter.org, fw@strlen.de, phil@nwl.cc, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, horms@kernel.org, andrew+netdev@lunn.ch, razor@blackwall.org, idosch@nvidia.com, matthias.bgg@gmail.com, angelogioacchino.delregno@collabora.com, bridge@lists.linux.dev, coreteam@netfilter.org, linux-mediatek@lists.infradead.org, linux-arm-kernel@lists.infradead.org, rchen14b@gmail.com, lorenzo@kernel.org, Daniel Pawlik Subject: [PATCH 3/5] netfilter: nf_flow_table_path: add L2 bridge offload Date: Tue, 30 Jun 2026 08:57:33 +0200 Message-ID: <20260630065735.3341614-4-pawlik.dan@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260630065735.3341614-1-pawlik.dan@gmail.com> References: <20260630065735.3341614-1-pawlik.dan@gmail.com> Precedence: bulk X-Mailing-List: bridge@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Ryan Chen Allow nft_flow_offload to accelerate traffic forwarded at layer 2 through Linux bridge ports. Detection: nft_flow_offload_is_bridging() identifies bridged flows by checking that the ingress device is a bridge port and that the destination MAC appears in the bridge FDB with a forwarding destination port (non-local entry). VLAN resolution and FDB lookup are combined in a single br_port_get_rcu() call via br_fdb_has_forwarding_entry_rcu(). Routing: nft_flow_route_bridging() allocates minimal dst entries anchored to the bridge master device via rt_dst_alloc()/ip6_dst_alloc(). A full routing table lookup via nf_route() is intentionally avoided: it fails for prefixes that are only bridged, not routed, through the bridge interface (e.g. when the bridge has no IP address or the bridged subnet is not in the routing table). MAC addresses: for bridged flows, nft_dev_forward_path() copies Ethernet addresses directly from the packet header instead of going through the neighbour table. Direction (original vs reply) is resolved against the conntrack direction so both flow directions receive the correct MAC pair. VLAN context: nft_br_vlan_dev_fill_forward_path() pre-populates the net_device_path_ctx with the port VLAN id and protocol before the forward path walk, enabling VLAN-aware hardware offload entries. Also: - info->indev is updated for every path type in nft_dev_path_info() so the bridge ingress device is correctly tracked regardless of path type. - nft_flow_route() is now a thin dispatcher that delegates to nft_flow_route_routing() (routed traffic) or nft_flow_route_bridging() (bridged traffic); the exported API is unchanged. Path discovery infrastructure was moved to nf_flow_table_path.c in commit 93d7a7ed0734 ("netfilter: flowtable: move path discovery infrastructure to its own file"), so all changes land in that file. Based on a MediaTek SDK patch by Bo-Cun Chen . Co-developed-by: Daniel Pawlik Signed-off-by: Daniel Pawlik Signed-off-by: Ryan Chen --- net/netfilter/nf_flow_table_path.c | 167 +++++++++++++++++++++++++++-- 1 file changed, 157 insertions(+), 10 deletions(-) diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 98c03b487f52..6c470854127f 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -15,6 +15,10 @@ #include #include #include +#include +#include +#include +#include static enum flow_offload_xmit_type nft_xmit_type(struct dst_entry *dst) { @@ -42,7 +46,25 @@ static bool nft_is_valid_ether_device(const struct net_device *dev) return true; } -static int nft_dev_fill_forward_path(const struct nf_flow_route *route, +static bool nft_flow_offload_is_bridging(struct sk_buff *skb) +{ + bool ret; + + if (!netif_is_bridge_port(skb->dev)) + return false; + if (!skb_mac_header_was_set(skb)) + return false; + + rcu_read_lock(); + ret = br_fdb_has_forwarding_entry_rcu(skb->dev, skb, + eth_hdr(skb)->h_dest); + rcu_read_unlock(); + + return ret; +} + +static int nft_dev_fill_forward_path(struct net_device_path_ctx *ctx, + const struct nf_flow_route *route, const struct dst_entry *dst_cache, const struct nf_conn *ct, enum ip_conntrack_dir dir, u8 *ha, @@ -58,6 +80,12 @@ static int nft_dev_fill_forward_path(const struct nf_flow_route *route, goto out; } + /* Bridging fastpath copies Ethernet addresses into ha; do not replace + * them via neighbour lookup on the routed destination device. + */ + if (!is_zero_ether_addr(ha)) + goto out; + n = dst_neigh_lookup(dst_cache, daddr); if (!n) return -1; @@ -72,7 +100,23 @@ static int nft_dev_fill_forward_path(const struct nf_flow_route *route, return -1; out: - return dev_fill_forward_path(dev, ha, stack); + return __dev_fill_forward_path(ctx, ha, stack); +} + +static void nft_br_vlan_dev_fill_forward_path(const struct nft_pktinfo *pkt, + struct net_device_path_ctx *ctx) +{ + __be16 proto = 0; + u16 vlan_id; + + rcu_read_lock(); + vlan_id = br_vlan_get_offload_info_rcu(pkt->skb->dev, pkt->skb, &proto); + if (vlan_id) { + ctx->num_vlans = 1; + ctx->vlan[0].id = vlan_id; + ctx->vlan[0].proto = proto; + } + rcu_read_unlock(); } struct nft_forward_info { @@ -103,13 +147,13 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, for (i = 0; i < stack->num_paths; i++) { path = &stack->path[i]; + info->indev = path->dev; switch (path->type) { case DEV_PATH_ETHERNET: case DEV_PATH_DSA: case DEV_PATH_VLAN: case DEV_PATH_PPPOE: case DEV_PATH_TUN: - info->indev = path->dev; if (is_zero_ether_addr(info->h_source)) memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN); @@ -244,6 +288,7 @@ static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt, } static int nft_dev_forward_path(const struct nft_pktinfo *pkt, + bool is_bridging, struct nf_flow_route *route, const struct nf_conn *ct, enum ip_conntrack_dir dir, @@ -251,11 +296,33 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt, { const struct dst_entry *dst = route->tuple[dir].dst; struct net_device_path_stack stack; + struct net_device_path_ctx ctx = { + .dev = dst->dev, + }; struct nft_forward_info info = {}; + enum ip_conntrack_info pkt_ctinfo; + enum ip_conntrack_dir skb_dir; + struct ethhdr *eth; unsigned char ha[ETH_ALEN]; int i; - if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) < 0 || + memset(ha, 0, sizeof(ha)); + + if (is_bridging) { + nf_ct_get(pkt->skb, &pkt_ctinfo); + eth = eth_hdr(pkt->skb); + skb_dir = CTINFO2DIR(pkt_ctinfo); + if (skb_dir != dir) { + memcpy(ha, eth->h_source, ETH_ALEN); + memcpy(info.h_source, eth->h_dest, ETH_ALEN); + } else { + memcpy(ha, eth->h_dest, ETH_ALEN); + memcpy(info.h_source, eth->h_source, ETH_ALEN); + } + nft_br_vlan_dev_fill_forward_path(pkt, &ctx); + } + + if (nft_dev_fill_forward_path(&ctx, route, dst, ct, dir, ha, &stack) < 0 || nft_dev_path_info(&stack, &info, ha, &ft->data) < 0) return -ENOENT; @@ -292,9 +359,11 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt, return 0; } -int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct, - struct nf_flow_route *route, enum ip_conntrack_dir dir, - struct nft_flowtable *ft) +static int nft_flow_route_routing(const struct nft_pktinfo *pkt, + const struct nf_conn *ct, + struct nf_flow_route *route, + enum ip_conntrack_dir dir, + struct nft_flowtable *ft) { struct dst_entry *this_dst = skb_dst(pkt->skb); struct dst_entry *other_dst = NULL; @@ -334,12 +403,12 @@ int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct, nft_default_forward_path(route, this_dst, dir); nft_default_forward_path(route, other_dst, !dir); - if (route->tuple[dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH && - nft_dev_forward_path(pkt, route, ct, dir, ft) < 0) + if (route->tuple[dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH && + nft_dev_forward_path(pkt, false, route, ct, dir, ft) < 0) goto err_dst_release; if (route->tuple[!dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH && - nft_dev_forward_path(pkt, route, ct, !dir, ft) < 0) + nft_dev_forward_path(pkt, false, route, ct, !dir, ft) < 0) goto err_dst_release; return 0; @@ -349,4 +418,82 @@ int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct, dst_release(route->tuple[!dir].dst); return -ENOENT; } + +static int nft_flow_route_bridging(const struct nft_pktinfo *pkt, + const struct nf_conn *ct, + struct nf_flow_route *route, + enum ip_conntrack_dir dir, + struct nft_flowtable *ft) +{ + struct dst_entry *dsts[IP_CT_DIR_MAX] = {}; + struct net_device *br_dev; + int i; + + /* Allocate minimal dsts anchored to the bridge master device to supply + * xmit_type and MTU. A full routing lookup via nf_route() is avoided + * because it fails for prefixes that are bridged but not routed. + */ + rcu_read_lock(); + br_dev = netdev_master_upper_dev_get_rcu(pkt->skb->dev); + if (!br_dev || !netif_is_bridge_master(br_dev)) { + rcu_read_unlock(); + return -ENOENT; + } + + for (i = 0; i < IP_CT_DIR_MAX; i++) { + switch (nft_pf(pkt)) { + case NFPROTO_IPV4: { + struct rtable *rt; + + rt = rt_dst_alloc(br_dev, 0, RTN_UNICAST, true); + if (rt) + dsts[i] = &rt->dst; + break; + } + case NFPROTO_IPV6: { + struct rt6_info *rt; + + rt = ip6_dst_alloc(nft_net(pkt), br_dev, 0); + if (rt) + dsts[i] = &rt->dst; + break; + } + } + } + rcu_read_unlock(); + + if (!dsts[dir] || !dsts[!dir]) { + dst_release(dsts[dir]); + dst_release(dsts[!dir]); + return -ENOENT; + } + + nft_default_forward_path(route, dsts[dir], dir); + nft_default_forward_path(route, dsts[!dir], !dir); + /* Drop allocation references; route->tuple[*].dst holds the clones. */ + dst_release(dsts[dir]); + dst_release(dsts[!dir]); + + if (route->tuple[dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH && + route->tuple[!dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH) { + if (nft_dev_forward_path(pkt, true, route, ct, dir, ft) || + nft_dev_forward_path(pkt, true, route, ct, !dir, ft)) { + dst_release(route->tuple[dir].dst); + dst_release(route->tuple[!dir].dst); + return -ENOENT; + } + } + + return 0; +} + +int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct, + struct nf_flow_route *route, enum ip_conntrack_dir dir, + struct nft_flowtable *ft) +{ + if (nft_flow_offload_is_bridging(pkt->skb)) + return nft_flow_route_bridging(pkt, ct, route, dir, ft); + + return nft_flow_route_routing(pkt, ct, route, dir, ft); +} EXPORT_SYMBOL_GPL(nft_flow_route); -- 2.54.0