@@ -564,6 +564,7 @@ enum {
BRIDGE_VLANDB_GOPTS_MCAST_QUERIER,
BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS,
BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_STATE,
+ BRIDGE_VLANDB_GOPTS_MSTID,
__BRIDGE_VLANDB_GOPTS_MAX
};
#define BRIDGE_VLANDB_GOPTS_MAX (__BRIDGE_VLANDB_GOPTS_MAX - 1)
@@ -1759,6 +1759,9 @@ static inline void br_vlan_set_state(struct net_bridge_vlan *v, u8 state)
mst->state = state;
}
+u16 br_vlan_mstid_get(const struct net_bridge_vlan *v);
+int br_vlan_mstid_set(struct net_bridge_vlan *v, u16 mstid);
+
static inline u8 br_vlan_get_pvid_state(const struct net_bridge_vlan_group *vg)
{
return READ_ONCE(vg->pvid_state);
@@ -41,10 +41,8 @@ static void br_vlan_mst_rcu_free(struct rcu_head *rcu)
kfree(mst);
}
-static void br_vlan_mst_put(struct net_bridge_vlan *v)
+static void br_vlan_mst_put(struct br_vlan_mst *mst)
{
- 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);
}
@@ -153,13 +151,17 @@ static struct br_vlan_mst *br_vlan_group_mst_get(struct net_bridge_vlan_group *v
static int br_vlan_mst_migrate(struct net_bridge_vlan *v, u16 mstid)
{
+ struct br_vlan_mst *mst, *old_mst;
struct net_bridge_vlan_group *vg;
- struct br_vlan_mst *mst;
+ struct net_bridge *br;
- if (br_vlan_is_master(v))
- vg = br_vlan_group(v->br);
- else
+ if (br_vlan_is_master(v)) {
+ br = v->br;
+ vg = br_vlan_group(br);
+ } else {
+ br = v->port->br;
vg = nbp_vlan_group(v->port);
+ }
mst = br_vlan_group_mst_get(vg, mstid);
if (!mst) {
@@ -168,10 +170,37 @@ static int br_vlan_mst_migrate(struct net_bridge_vlan *v, u16 mstid)
return -ENOMEM;
}
- if (rtnl_dereference(v->mst))
- br_vlan_mst_put(v);
-
+ old_mst = rtnl_dereference(v->mst);
rcu_assign_pointer(v->mst, mst);
+
+ if (old_mst)
+ br_vlan_mst_put(old_mst);
+
+ return 0;
+}
+
+int br_vlan_mstid_set(struct net_bridge_vlan *v, u16 mstid)
+{
+ struct net_bridge *br = v->br;
+ struct net_bridge_port *p;
+ int err;
+
+ err = br_vlan_mst_migrate(v, mstid);
+ if (err)
+ return err;
+
+ list_for_each_entry(p, &br->port_list, list) {
+ struct net_bridge_vlan_group *vg = nbp_vlan_group(p);
+ struct net_bridge_vlan *portv;
+
+ portv = br_vlan_lookup(&vg->vlan_hash, v->vid);
+ if (!portv)
+ continue;
+
+ err = br_vlan_mst_migrate(portv, mstid);
+ if (err)
+ return err;
+ }
return 0;
}
@@ -501,7 +530,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
return err;
out_mst_init:
- br_vlan_mst_put(v);
+ br_vlan_mst_put(rtnl_dereference(v->mst));
out_fdb_insert:
if (br_vlan_should_use(v)) {
@@ -570,7 +599,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_mst_put(rtnl_dereference(v->mst));
br_vlan_put_master(masterv);
out:
return err;
@@ -380,6 +380,9 @@ bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
#endif
#endif
+ if (nla_put_u16(skb, BRIDGE_VLANDB_GOPTS_MSTID, br_vlan_mstid_get(v_opts)))
+ goto out_err;
+
nla_nest_end(skb, nest);
return true;
@@ -411,7 +414,9 @@ static size_t rtnl_vlan_global_opts_nlmsg_size(const struct net_bridge_vlan *v)
+ nla_total_size(0) /* BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS */
+ br_rports_size(&v->br_mcast_ctx) /* BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS */
#endif
- + nla_total_size(sizeof(u16)); /* BRIDGE_VLANDB_GOPTS_RANGE */
+ + nla_total_size(sizeof(u16)) /* BRIDGE_VLANDB_GOPTS_RANGE */
+ + nla_total_size(sizeof(u16)) /* BRIDGE_VLANDB_GOPTS_MSTID */
+ + 0;
}
static void br_vlan_global_opts_notify(const struct net_bridge *br,
@@ -560,6 +565,15 @@ static int br_vlan_process_global_one_opts(const struct net_bridge *br,
}
#endif
#endif
+ if (tb[BRIDGE_VLANDB_GOPTS_MSTID]) {
+ u16 mstid;
+
+ mstid = nla_get_u16(tb[BRIDGE_VLANDB_GOPTS_MSTID]);
+ err = br_vlan_mstid_set(v, mstid);
+ if (err)
+ return err;
+ *changed = true;
+ }
return 0;
}
@@ -579,6 +593,7 @@ static const struct nla_policy br_vlan_db_gpol[BRIDGE_VLANDB_GOPTS_MAX + 1] = {
[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL] = { .type = NLA_U64 },
[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL] = { .type = NLA_U64 },
[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL] = { .type = NLA_U64 },
+ [BRIDGE_VLANDB_GOPTS_MSTID] = NLA_POLICY_RANGE(NLA_U16, 1, 4094),
};
int br_vlan_rtm_process_global_options(struct net_device *dev,
Allow a VLAN to change its MSTID. In particular, allow multiple VLANs to use the same MSTID. This is a global VLAN setting, i.e. any VLANs bound to the same MSTID will share their per-VLAN STP states on all bridge ports. Example: By default, each VLAN is placed in a separate MSTID: root@coronet:~# ip link add dev br0 type bridge vlan_filtering 1 root@coronet:~# ip link set dev eth1 master br0 root@coronet:~# bridge vlan add dev eth1 vid 2 pvid untagged root@coronet:~# bridge vlan add dev eth1 vid 3 root@coronet:~# bridge vlan global port vlan-id br0 1 mcast_snooping 1 mca<redacted>_interval 1000 mstid 1 2 mcast_snooping 1 mca<redacted>_interval 1000 mstid 2 3 mcast_snooping 1 mca<redacted>_interval 1000 mstid 3 Once two or more VLANs are bound to the same MSTID, their states move in lockstep, independent of which VID is used to access the state: root@coronet:~# bridge vlan global set dev br0 vid 2 mstid 10 root@coronet:~# bridge vlan global set dev br0 vid 3 mstid 10 root@coronet:~# bridge -d vlan global port vlan-id br0 1 mcast_snooping 1 mca<redacted>_interval 1000 mstid 1 2-3 mcast_snooping 1 mca<redacted>_interval 1000 mstid 10 root@coronet:~# bridge vlan set dev eth1 vid 2 state blocking root@coronet:~# bridge -d vlan port vlan-id eth1 1 Egress Untagged state forwarding mcast_router 1 2 PVID Egress Untagged state blocking mcast_router 1 3 state blocking mcast_router 1 br0 1 PVID Egress Untagged state forwarding mcast_router 1 root@coronet:~# bridge vlan set dev eth1 vid 3 state forwarding root@coronet:~# bridge -d vlan port vlan-id eth1 1 Egress Untagged state forwarding mcast_router 1 2 PVID Egress Untagged state forwarding mcast_router 1 3 state forwarding mcast_router 1 br0 1 PVID Egress Untagged state forwarding mcast_router 1 Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com> --- include/uapi/linux/if_bridge.h | 1 + net/bridge/br_private.h | 3 ++ net/bridge/br_vlan.c | 53 ++++++++++++++++++++++++++-------- net/bridge/br_vlan_options.c | 17 ++++++++++- 4 files changed, 61 insertions(+), 13 deletions(-)