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 <tobias@xxxxxxxxxxxxxx> --- 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, -- 2.25.1