From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wr1-f48.google.com (mail-wr1-f48.google.com [209.85.221.48]) (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 CF9313C1418 for ; Tue, 30 Jun 2026 06:58:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.48 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782802685; cv=none; b=QLI7jWQt7HyCpWC55DNhUiPww62zoaqghwyZn9E74BwZJf7PQuiQ2JT1u9kOkCL/ND64/RIqAhWzo5co3XwCkNNE/cPAAmWJtNQEU35ZBXPBAApPQpbOCoGngJIdWQXE8G25NWXR+necWujOA4P1uzFPdMiJPuDC7gviihLfj6s= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782802685; c=relaxed/simple; bh=uFlsWrhJwtQf9yt8auqKp+aP7EpEMnGUWhzv1INgKXg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=QSHdp6c5w5fwFO3MkP5oCVntDQ5LkkLhRVKYyQH3MKyOckhPboNIubb1bfuFZWlq5xGv2hUYhQwGhOj7eqWE5EeXMZmDQsFvW3ESL8VjTumZgHFO9u0ZUPXVxaCo1KhqIFu0ukhNyavEx+aBDpLvcHd2EN2DKabVtOh5elmg96s= 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=RS6F+dMN; arc=none smtp.client-ip=209.85.221.48 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="RS6F+dMN" Received: by mail-wr1-f48.google.com with SMTP id ffacd0b85a97d-46ed4f66256so3282946f8f.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=vger.kernel.org; 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=RS6F+dMNBRSUSIwjLUNhssTvIeJ/wL1i/kprYCPi1cCcOikBDC4QzknjGQaeZxZsCx VDVHLrtE/90+geahpgU+U1r5B13s0glYJ/SKDPx4Cp7kN+EJPdlr0rICbzklvSbROW22 ePk4dfLhbzj27pPj83eRSwfnLuh1u8AMRcKp+0069wcn89Ldw6+e1IQnv1Rbl0BCIgM0 NlkZIE/p50XvPrtK/kYNFs6c4qmoAQzvaAshoSBC+Jhopa9itE5qvxQXGzXEzZ7v3g8R Gk+K+HXAE0xpXKXdWaH7sBpNeITY0vIWor98XwG1LenrIzuHDjy3jMsMj7hiCHnkSbnm k8WQ== 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=RIBfZp9psxzLNIh6z1KJlKI6yg382Rdf7+TT8+WAMwnTtpxdnq2a4OcKEOZblbW/Rm 61np+1IY6Pfm/zWk9qtYiALr+YbWB5C8Q4bySa8OaYcjZXtBH611/krJAM/joM7uwX8b cjC299BZiCqDcZsqBxVnl3qdKY8FRn84bE05yVUP6D1QbhxDET8p3P5aVhCTvIH78h9M b9DnIwwEwUlWyE/GywyFXqu1m4AQK7FMj7tKHc4zI2XNow1XKQrl5Qczq3s6Pgt1R7Sa 7EsUw+J+MU8ZOnYsXU+arTubCv0tEF8QF5z2AlIzOwF1G7RYU28PJPX24b8icaItZLYP /JrA== X-Forwarded-Encrypted: i=1; AHgh+RoHwiVl6C1twYfmzGy1vlW8Oyt9kzNA1KDfdNFVetHa4dV8vLQbCIDjMbx6JS0HG9XoMipk/0g=@vger.kernel.org X-Gm-Message-State: AOJu0YyJcADQfAN25R3y/nh3K1XSXcXivIn+eFb6+1BU7WSb27BV7xTu xBEPbREN9Y82DBgO+QR5i4tKhm2QD/kf5lIe8WCvkyN4qqW44C4u21/W X-Gm-Gg: AfdE7cn0n1pWeZ0foVzr3SfQnclOb4w4oB4o7d1REPI8SrN/TPjrEA5y2zB1V2RW2CZ V1eAlTm8ymOIXYlL0g4KaB0eqG94P3jgNX4+To4LPNimKBqF+AqTMGuw4VI6dacks29RTECqJNh felKoUn0zLdKpSbGKfx4V4WEJH/nGacTHpZ4rtBApFPK8QZ4oC9sBQH02QCA5G0VC5VCDSqXowP nRa/W/sLDxKk21Aep1gOdNm88Za3Ho2EtMx/4i3lITBn6mdKgv/NaamifCQsiiJ8lCAzPkYKSg1 6Z5v2rfkjwZCyT8eCn3dTSuHmKKUhchkBYZCW6sPQiOCGWGPpW0w91VcRUEFqO2f5n+cwe3OCnM 33lTlEQGY1la2vooh1fVp9Cccr03k5KYWAXtUUP5y+q2IncgrmUXwEEG5AF6ffJCh+vUpVQUO6T GHM0ld9E5xdDsAT2BR2w== 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: netdev@vger.kernel.org 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