net: bridge: vlan: use an RCU array for large flood sets

Walking the master VLAN's port-VLAN list avoids considering ports outside
the VLAN but its pointer chasing becomes more expensive than array when
many ports participate. Add an rcu array of port-VLAN pointers for larger
flood sets. Rebuild and publish the array under RTNL when VLAN membership
changes. Continue using the list for small flood sets and as a fallback
if the array allocation fails.

Signed-off-by: Nikolay Aleksandrov <razor@blackwall.org>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Link: https://patch.msgid.link/20260918152950.1938259-7-razor@blackwall.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Nikolay Aleksandrov 2026-09-18 18:29:47 +03:00 committed by Jakub Kicinski
parent d5219589e4
commit 9c30ccaa73
3 changed files with 79 additions and 10 deletions

View File

@ -261,6 +261,35 @@ static void br_flood_port(struct net_bridge_port **prev,
*prev = maybe_deliver(*prev, p, skb, local_orig);
}
static void br_flood_vlan(struct net_bridge_port **prev,
struct net_bridge_vlan *v, struct sk_buff *skb,
enum br_pkt_type pkt_type, bool local_orig)
{
struct net_bridge_vlan_port_array *array;
struct net_bridge_vlan *masterv, *pv;
masterv = br_vlan_is_master(v) ? v : v->brvlan;
array = rcu_dereference(masterv->port_array);
if (array) {
unsigned int i;
for (i = 0; i < array->count; i++) {
pv = array->vlans[i];
br_flood_port(prev, pv->port, skb, pkt_type,
local_orig, v->vid);
if (IS_ERR(*prev))
break;
}
} else {
list_for_each_entry_rcu(pv, &masterv->port_vlist, port_vlist) {
br_flood_port(prev, pv->port, skb, pkt_type,
local_orig, v->vid);
if (IS_ERR(*prev))
break;
}
}
}
/* called under rcu_read_lock */
void br_flood(struct net_bridge *br, struct net_bridge_vlan *v,
struct sk_buff *skb, enum br_pkt_type pkt_type,
@ -271,15 +300,7 @@ void br_flood(struct net_bridge *br, struct net_bridge_vlan *v,
br_tc_skb_miss_set(skb, pkt_type != BR_PKT_BROADCAST);
if (v) {
struct net_bridge_vlan *masterv, *pv;
masterv = br_vlan_is_master(v) ? v : v->brvlan;
list_for_each_entry_rcu(pv, &masterv->port_vlist, port_vlist) {
br_flood_port(&prev, pv->port, skb, pkt_type,
local_orig, v->vid);
if (IS_ERR(prev))
break;
}
br_flood_vlan(&prev, v, skb, pkt_type, local_orig);
} else {
struct net_bridge_port *p;

View File

@ -190,6 +190,17 @@ enum {
BR_VLFLAG_NEIGH_FORWARD_GRAT_ENABLED = BIT(6),
};
/* start publishing arrays when there're > BR_VLAN_PORT_ARRAY_THRESHOLD
* port-VLANs
*/
#define BR_VLAN_PORT_ARRAY_THRESHOLD 8
struct net_bridge_vlan_port_array {
struct rcu_head rcu;
unsigned int count;
struct net_bridge_vlan *vlans[];
};
/**
* struct net_bridge_vlan - per-vlan entry
*
@ -210,6 +221,7 @@ enum {
* @port_mcast_ctx: if MASTER flag unset, this is the per-port/vlan multicast
* context
* @msti: if MASTER flag set, this holds the VLANs MST instance
* @port_array: if MASTER flag set, this is the port-VLAN array
* @port_vlist: if MASTER flag set, this is the port-VLAN list
* @vlist: sorted list of VLAN entries
* @rcu: used for entry destruction
@ -245,6 +257,7 @@ struct net_bridge_vlan {
u16 msti;
struct net_bridge_vlan_port_array __rcu *port_array;
struct list_head port_vlist;
struct list_head vlist;

View File

@ -258,6 +258,36 @@ static void br_vlan_init_state(struct net_bridge_vlan *v)
v->msti = 0;
}
static unsigned int br_vlan_num_ports(const struct net_bridge_vlan *masterv)
{
return refcount_read(&masterv->refcnt) - br_vlan_is_brentry(masterv);
}
static void br_vlan_rebuild_port_array(struct net_bridge_vlan *masterv,
unsigned int count)
{
struct net_bridge_vlan_port_array *array = NULL, *old;
unsigned int i = 0;
WARN_ON(!br_vlan_is_master(masterv));
if (count > BR_VLAN_PORT_ARRAY_THRESHOLD)
array = kvmalloc(struct_size(array, vlans, count), GFP_KERNEL);
if (array) {
struct net_bridge_vlan *pv;
array->count = count;
list_for_each_entry(pv, &masterv->port_vlist, port_vlist)
array->vlans[i++] = pv;
}
old = rtnl_dereference(masterv->port_array);
rcu_assign_pointer(masterv->port_array, array);
if (old)
kvfree_rcu(old, rcu);
}
/* This is the shared VLAN add function which works for both ports and bridge
* devices. There are four possible calls to this function in terms of the
* vlan entry type:
@ -368,8 +398,10 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
__vlan_flags_commit(v, flags);
br_multicast_toggle_one_vlan(v, true);
if (p)
if (p) {
br_vlan_rebuild_port_array(masterv, br_vlan_num_ports(masterv));
nbp_vlan_set_vlan_dev_state(p, v->vid);
}
out:
return err;
@ -438,6 +470,9 @@ static void __vlan_del(struct net_bridge_vlan *v)
rhashtable_remove_fast(&vg->vlan_hash, &v->vnode,
br_vlan_rht_params);
__vlan_del_list(v);
/* -1 because br_vlan_put_master() is called later */
br_vlan_rebuild_port_array(masterv,
br_vlan_num_ports(masterv) - 1);
nbp_vlan_set_vlan_dev_state(p, v->vid);
br_multicast_toggle_one_vlan(v, false);
br_multicast_port_ctx_deinit(&v->port_mcast_ctx);