From patchwork Wed Feb 16 13:29:26 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tobias Waldekranz X-Patchwork-Id: 12748616 X-Patchwork-Delegate: kuba@kernel.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C41AAC433EF for ; Wed, 16 Feb 2022 13:30:31 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234350AbiBPNak (ORCPT ); Wed, 16 Feb 2022 08:30:40 -0500 Received: from mxb-00190b01.gslb.pphosted.com ([23.128.96.19]:42720 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233590AbiBPNaj (ORCPT ); Wed, 16 Feb 2022 08:30:39 -0500 Received: from mail-lf1-x131.google.com (mail-lf1-x131.google.com [IPv6:2a00:1450:4864:20::131]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 34CC51728B2 for ; Wed, 16 Feb 2022 05:30:26 -0800 (PST) Received: by mail-lf1-x131.google.com with SMTP id e5so3818801lfr.9 for ; Wed, 16 Feb 2022 05:30:26 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=waldekranz-com.20210112.gappssmtp.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:organization:content-transfer-encoding; bh=XlQYLM8l9YP6fA05hV8ZIQiqX+CEM3X6J0L0A3uQZHU=; b=uZP+bmSmdF22/IqQAh7JhsXN7QUJilKpebrKmLzK12rBZsPq1h9TgvyMeiLzx/sixU V3lQIUHbvXyOZyadaX2UZJyu7MpzGABS16JU1eObqqrsn1MOcEi98EMvPrO4QLtOtFKx ZuzOKJEkQxnMdwE9e0GIy65Br/qjiQtU5JzoQ/hyILRdJI+y4clL4RvC8VgfRc3vIytm B9eIaEhHu/xXy7yD9ZupEQ9I+w20t8wF4ycy88nerSTcn4rVBPKLgdAn3vREbP4PHwj8 u3NyHm9aqxFOCjxeCa9842WCNgD1QrE/vGXFRd1a/TaCTAonCpQCBQh6hi16QD+Fb3kK kgvQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:organization:content-transfer-encoding; bh=XlQYLM8l9YP6fA05hV8ZIQiqX+CEM3X6J0L0A3uQZHU=; b=mkP6Bt4x0TM98WPXlRmJ8uFVxmNsk8Wq1hKrWjnnlHQaVPw8w8a8MVJnXB+3k5esWg jTdg0GWa9DmwjE49aADbjRFljLDQlAjdMmLuWDUnCrbawU7piJRkP3KNqx5s9R6sTwqN LVEYFRWUu0zt15B97uKeObHxGRnoQNMGhF5sdyVhhlaujcjRkfr0d4LcGWQE5qCVZB5H MxowAPJvgBQvdf02ULbZwhcV7sHxzf36prbY8eogjXCCVCR+pU6nh95cstMy/irBapzU zEA6VMD0z6pkObmdJtMj1Rd27U0vT11wrJW+apWj8tpgOwZDmYjVasSr7uCnNDNcl9ZG +zHw== X-Gm-Message-State: AOAM533OMxALdt7vlJZRzTaTEomxet+KMOKtn/tSe44rRT1pgMy7/Zl3 F0a5brFZto9wphGY5xerts62Q78yIlRf3Q== X-Google-Smtp-Source: ABdhPJyQA20O4VzBdbt1DxD1vQ6wknk3/9R1oWsaaINMwDaRKtMimKINpObqBOpmiAABr4M1tYtvWg== X-Received: by 2002:a05:6512:3f9:b0:443:3c86:31f1 with SMTP id n25-20020a05651203f900b004433c8631f1mr2002015lfq.532.1645018224491; Wed, 16 Feb 2022 05:30:24 -0800 (PST) Received: from veiron.westermo.com (static-193-12-47-89.cust.tele2.se. [193.12.47.89]) by smtp.gmail.com with ESMTPSA id v6sm234780ljd.86.2022.02.16.05.30.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 16 Feb 2022 05:30:23 -0800 (PST) From: Tobias Waldekranz To: davem@davemloft.net, kuba@kernel.org Cc: Andrew Lunn , Vivien Didelot , Florian Fainelli , Vladimir Oltean , Jiri Pirko , Ivan Vecera , Roopa Prabhu , Nikolay Aleksandrov , Russell King , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, bridge@lists.linux-foundation.org Subject: [RFC net-next 1/9] net: bridge: vlan: Introduce multiple spanning trees (MST) Date: Wed, 16 Feb 2022 14:29:26 +0100 Message-Id: <20220216132934.1775649-2-tobias@waldekranz.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220216132934.1775649-1-tobias@waldekranz.com> References: <20220216132934.1775649-1-tobias@waldekranz.com> MIME-Version: 1.0 Organization: Westermo Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-Delegate: kuba@kernel.org X-Patchwork-State: RFC Before this commit, the bridge was able to manage the forwarding state of a port on either a global or per-VLAN basis. I.e. either 1:N or N:N. There are two issues with this: 1. In order to support MSTP (802.1Q-2018 13.5), the controlling entity expects the bridge to be able to group multiple VLANs to operate on the same tree (MST). I.e. an M:N mapping, where M <= N. 2. Some hardware (e.g. mv88e6xxx) has a smaller pool of spanning tree groups than VLANs. I.e. the full set of 4k VLANs can be configured, but each VLAN must be mapped to one of only 64 spanning trees. While somewhat less efficient (and non-atomic), (1) can be worked around in software by iterating over all affected VLANs when changing the state of a tree to make sure that they are all in sync. Unfortunately, (2) means that offloading is not possible in this architecture. Therefore, add a level of indirection in the per-VLAN STP state. By default, each new VLAN will be assigned to a separate MST. I.e. there are no functional changes introduced by this commit. Upcoming commits will then extend the VLAN DB configuration to allow arbitrary M:N mappings. Signed-off-by: Tobias Waldekranz --- include/linux/if_bridge.h | 6 ++ net/bridge/br_private.h | 41 +++++-- net/bridge/br_vlan.c | 200 +++++++++++++++++++++++++++++++++-- net/bridge/br_vlan_options.c | 9 +- 4 files changed, 234 insertions(+), 22 deletions(-) diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h index 509e18c7e740..a3b0e95c3047 100644 --- a/include/linux/if_bridge.h +++ b/include/linux/if_bridge.h @@ -118,6 +118,7 @@ int br_vlan_get_info(const struct net_device *dev, u16 vid, struct bridge_vlan_info *p_vinfo); int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid, struct bridge_vlan_info *p_vinfo); +int br_vlan_get_mstid(const struct net_device *dev, u16 vid, u16 *mstid); #else static inline bool br_vlan_enabled(const struct net_device *dev) { @@ -150,6 +151,11 @@ static inline int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid, { return -EINVAL; } +static inline int br_vlan_get_mstid(const struct net_device *dev, u16 vid, + u16 *mstid) +{ + return -EINVAL; +} #endif #if IS_ENABLED(CONFIG_BRIDGE) diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index 2661dda1a92b..7781e7a4449b 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -153,6 +153,14 @@ struct br_tunnel_info { struct metadata_dst __rcu *tunnel_dst; }; +struct br_vlan_mst { + refcount_t refcnt; + u16 id; + u8 state; + + struct rcu_head rcu; +}; + /* private vlan flags */ enum { BR_VLFLAG_PER_PORT_STATS = BIT(0), @@ -168,7 +176,8 @@ enum { * @vid: VLAN id * @flags: bridge vlan flags * @priv_flags: private (in-kernel) bridge vlan flags - * @state: STP state (e.g. blocking, learning, forwarding) + * @mst: the port's STP state (e.g. blocking, learning, forwarding) in the MST + * associated with this VLAN * @stats: per-cpu VLAN statistics * @br: if MASTER flag set, this points to a bridge struct * @port: if MASTER flag unset, this points to a port struct @@ -192,7 +201,7 @@ struct net_bridge_vlan { u16 vid; u16 flags; u16 priv_flags; - u8 state; + struct br_vlan_mst __rcu *mst; struct pcpu_sw_netstats __percpu *stats; union { struct net_bridge *br; @@ -215,6 +224,20 @@ struct net_bridge_vlan { struct rcu_head rcu; }; +static inline u8 br_vlan_get_state_rcu(const struct net_bridge_vlan *v) +{ + const struct br_vlan_mst *mst = rcu_dereference(v->mst); + + return mst->state; +} + +static inline u8 br_vlan_get_state_rtnl(const struct net_bridge_vlan *v) +{ + const struct br_vlan_mst *mst = rtnl_dereference(v->mst); + + return mst->state; +} + /** * struct net_bridge_vlan_group * @@ -1179,7 +1202,7 @@ br_multicast_port_ctx_state_disabled(const struct net_bridge_mcast_port *pmctx) return pmctx->port->state == BR_STATE_DISABLED || (br_multicast_port_ctx_is_vlan(pmctx) && (br_multicast_port_ctx_vlan_disabled(pmctx) || - pmctx->vlan->state == BR_STATE_DISABLED)); + br_vlan_get_state_rcu(pmctx->vlan) == BR_STATE_DISABLED)); } static inline bool @@ -1188,7 +1211,7 @@ br_multicast_port_ctx_state_stopped(const struct net_bridge_mcast_port *pmctx) return br_multicast_port_ctx_state_disabled(pmctx) || pmctx->port->state == BR_STATE_BLOCKING || (br_multicast_port_ctx_is_vlan(pmctx) && - pmctx->vlan->state == BR_STATE_BLOCKING); + br_vlan_get_state_rcu(pmctx->vlan) == BR_STATE_BLOCKING); } static inline bool @@ -1729,15 +1752,11 @@ bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr, bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range, const struct net_bridge_vlan *v_opts); -/* vlan state manipulation helpers using *_ONCE to annotate lock-free access */ -static inline u8 br_vlan_get_state(const struct net_bridge_vlan *v) -{ - return READ_ONCE(v->state); -} - static inline void br_vlan_set_state(struct net_bridge_vlan *v, u8 state) { - WRITE_ONCE(v->state, state); + struct br_vlan_mst *mst = rtnl_dereference(v->mst); + + mst->state = state; } static inline u8 br_vlan_get_pvid_state(const struct net_bridge_vlan_group *vg) diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c index 6315e43a7a3e..b0383ec6cc91 100644 --- a/net/bridge/br_vlan.c +++ b/net/bridge/br_vlan.c @@ -34,6 +34,187 @@ static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid) return rhashtable_lookup_fast(tbl, &vid, br_vlan_rht_params); } +static void br_vlan_mst_rcu_free(struct rcu_head *rcu) +{ + struct br_vlan_mst *mst = container_of(rcu, struct br_vlan_mst, rcu); + + kfree(mst); +} + +static void br_vlan_mst_put(struct net_bridge_vlan *v) +{ + struct br_vlan_mst *mst = rtnl_dereference(v->mst); + + if (refcount_dec_and_test(&mst->refcnt)) + call_rcu(&mst->rcu, br_vlan_mst_rcu_free); +} + +static struct br_vlan_mst *br_vlan_mst_new(u16 id) +{ + struct br_vlan_mst *mst; + + mst = kzalloc(sizeof(*mst), GFP_KERNEL); + if (!mst) + return NULL; + + refcount_set(&mst->refcnt, 1); + mst->id = id; + mst->state = BR_STATE_FORWARDING; + return mst; +} + +static int br_vlan_mstid_get_free(struct net_bridge *br) +{ + const struct net_bridge_vlan *v; + struct rhashtable_iter iter; + struct br_vlan_mst *mst; + unsigned long *busy; + int err = 0; + u16 id; + + busy = bitmap_zalloc(VLAN_N_VID, GFP_KERNEL); + if (!busy) + return -ENOMEM; + + /* MSTID 0 is reserved for the CIST */ + set_bit(0, busy); + + rhashtable_walk_enter(&br_vlan_group(br)->vlan_hash, &iter); + rhashtable_walk_start(&iter); + + while ((v = rhashtable_walk_next(&iter))) { + if (IS_ERR(v)) { + err = PTR_ERR(v); + goto out_free; + } + + mst = rtnl_dereference(v->mst); + set_bit(mst->id, busy); + } + + rhashtable_walk_stop(&iter); + + id = find_first_zero_bit(busy, VLAN_N_VID); + if (id >= VLAN_N_VID) + err = -ENOSPC; + +out_free: + kfree(busy); + return err ? : id; +} + +u16 br_vlan_mstid_get(const struct net_bridge_vlan *v) +{ + const struct net_bridge_vlan *masterv; + const struct br_vlan_mst *mst; + const struct net_bridge *br; + + if (br_vlan_is_master(v)) + br = v->br; + else + br = v->port->br; + + masterv = br_vlan_lookup(&br_vlan_group(br)->vlan_hash, v->vid); + + mst = rtnl_dereference(masterv->mst); + + return mst->id; +} + +int br_vlan_get_mstid(const struct net_device *dev, u16 vid, u16 *mstid) +{ + struct net_bridge *br = netdev_priv(dev); + struct net_bridge_vlan *v; + + v = br_vlan_lookup(&br_vlan_group(br)->vlan_hash, vid); + if (!v) + return -ENOENT; + + *mstid = br_vlan_mstid_get(v); + return 0; +} +EXPORT_SYMBOL_GPL(br_vlan_get_mstid); + +static struct br_vlan_mst *br_vlan_group_mst_get(struct net_bridge_vlan_group *vg, u16 mstid) +{ + struct net_bridge_vlan *v; + struct br_vlan_mst *mst; + + list_for_each_entry(v, &vg->vlan_list, vlist) { + mst = rtnl_dereference(v->mst); + if (mst->id == mstid) { + refcount_inc(&mst->refcnt); + return mst; + } + } + + return NULL; +} + +static int br_vlan_mst_migrate(struct net_bridge_vlan *v, u16 mstid) +{ + struct net_bridge_vlan_group *vg; + struct br_vlan_mst *mst; + + if (br_vlan_is_master(v)) + vg = br_vlan_group(v->br); + else + vg = nbp_vlan_group(v->port); + + mst = br_vlan_group_mst_get(vg, mstid); + if (!mst) { + mst = br_vlan_mst_new(mstid); + if (!mst) + return -ENOMEM; + } + + if (rtnl_dereference(v->mst)) + br_vlan_mst_put(v); + + rcu_assign_pointer(v->mst, mst); + return 0; +} + +static int br_vlan_mst_init_master(struct net_bridge_vlan *v) +{ + struct net_bridge *br = v->br; + struct br_vlan_mst *mst; + int mstid; + + /* The bridge VLAN is always added first, either as context or + * as a proper entry. Since the bridge default is a 1:1 map + * from VID to MST, we always need to allocate a new ID in + * this case. + */ + mstid = br_vlan_mstid_get_free(br); + if (mstid < 0) + return mstid; + + mst = br_vlan_mst_new(mstid); + if (!mst) + return -ENOMEM; + + rcu_assign_pointer(v->mst, mst); + return 0; +} + +static int br_vlan_mst_init_port(struct net_bridge_vlan *v) +{ + u16 mstid; + + mstid = br_vlan_mstid_get(v); + + return br_vlan_mst_migrate(v, mstid); +} + +static int br_vlan_mst_init(struct net_bridge_vlan *v) +{ + if (br_vlan_is_master(v)) + return br_vlan_mst_init_master(v); + else + return br_vlan_mst_init_port(v); +} + static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg, const struct net_bridge_vlan *v) { @@ -41,7 +222,7 @@ static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg, return false; smp_wmb(); - br_vlan_set_pvid_state(vg, v->state); + br_vlan_set_pvid_state(vg, br_vlan_get_state_rtnl(v)); vg->pvid = v->vid; return true; @@ -301,13 +482,14 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags, vg->num_vlans++; } - /* set the state before publishing */ - v->state = BR_STATE_FORWARDING; + err = br_vlan_mst_init(v); + if (err) + goto out_fdb_insert; err = rhashtable_lookup_insert_fast(&vg->vlan_hash, &v->vnode, br_vlan_rht_params); if (err) - goto out_fdb_insert; + goto out_mst_init; __vlan_add_list(v); __vlan_add_flags(v, flags); @@ -318,6 +500,9 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags, out: return err; +out_mst_init: + br_vlan_mst_put(v); + out_fdb_insert: if (br_vlan_should_use(v)) { br_fdb_find_delete_local(br, p, dev->dev_addr, v->vid); @@ -385,6 +570,7 @@ static int __vlan_del(struct net_bridge_vlan *v) call_rcu(&v->rcu, nbp_vlan_rcu_free); } + br_vlan_mst_put(v); br_vlan_put_master(masterv); out: return err; @@ -578,7 +764,7 @@ static bool __allowed_ingress(const struct net_bridge *br, goto drop; if (*state == BR_STATE_FORWARDING) { - *state = br_vlan_get_state(v); + *state = br_vlan_get_state_rcu(v); if (!br_vlan_state_allowed(*state, true)) goto drop; } @@ -631,7 +817,7 @@ bool br_allowed_egress(struct net_bridge_vlan_group *vg, br_vlan_get_tag(skb, &vid); v = br_vlan_find(vg, vid); if (v && br_vlan_should_use(v) && - br_vlan_state_allowed(br_vlan_get_state(v), false)) + br_vlan_state_allowed(br_vlan_get_state_rcu(v), false)) return true; return false; @@ -665,7 +851,7 @@ bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid) } v = br_vlan_find(vg, *vid); - if (v && br_vlan_state_allowed(br_vlan_get_state(v), true)) + if (v && br_vlan_state_allowed(br_vlan_get_state_rcu(v), true)) return true; return false; diff --git a/net/bridge/br_vlan_options.c b/net/bridge/br_vlan_options.c index a6382973b3e7..0b1099709d4b 100644 --- a/net/bridge/br_vlan_options.c +++ b/net/bridge/br_vlan_options.c @@ -43,14 +43,14 @@ bool br_vlan_opts_eq_range(const struct net_bridge_vlan *v_curr, u8 range_mc_rtr = br_vlan_multicast_router(range_end); u8 curr_mc_rtr = br_vlan_multicast_router(v_curr); - return v_curr->state == range_end->state && + return br_vlan_get_state_rtnl(v_curr) == br_vlan_get_state_rtnl(range_end) && __vlan_tun_can_enter_range(v_curr, range_end) && curr_mc_rtr == range_mc_rtr; } bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v) { - if (nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_STATE, br_vlan_get_state(v)) || + if (nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_STATE, br_vlan_get_state_rtnl(v)) || !__vlan_tun_put(skb, v)) return false; @@ -99,7 +99,7 @@ static int br_vlan_modify_state(struct net_bridge_vlan_group *vg, return -EBUSY; } - if (v->state == state) + if (br_vlan_get_state_rtnl(v) == state) return 0; if (v->vid == br_get_pvid(vg)) @@ -294,7 +294,8 @@ bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr, ((v_curr->priv_flags ^ r_end->priv_flags) & BR_VLFLAG_GLOBAL_MCAST_ENABLED) == 0 && br_multicast_ctx_options_equal(&v_curr->br_mcast_ctx, - &r_end->br_mcast_ctx); + &r_end->br_mcast_ctx) && + br_vlan_mstid_get(v_curr) == br_vlan_mstid_get(r_end); } bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,