123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * Bridge Multiple Spanning Tree Support
- *
- * Authors:
- * Tobias Waldekranz <[email protected]>
- */
- #include <linux/kernel.h>
- #include <net/switchdev.h>
- #include "br_private.h"
- DEFINE_STATIC_KEY_FALSE(br_mst_used);
- bool br_mst_enabled(const struct net_device *dev)
- {
- if (!netif_is_bridge_master(dev))
- return false;
- return br_opt_get(netdev_priv(dev), BROPT_MST_ENABLED);
- }
- EXPORT_SYMBOL_GPL(br_mst_enabled);
- int br_mst_get_info(const struct net_device *dev, u16 msti, unsigned long *vids)
- {
- const struct net_bridge_vlan_group *vg;
- const struct net_bridge_vlan *v;
- const struct net_bridge *br;
- ASSERT_RTNL();
- if (!netif_is_bridge_master(dev))
- return -EINVAL;
- br = netdev_priv(dev);
- if (!br_opt_get(br, BROPT_MST_ENABLED))
- return -EINVAL;
- vg = br_vlan_group(br);
- list_for_each_entry(v, &vg->vlan_list, vlist) {
- if (v->msti == msti)
- __set_bit(v->vid, vids);
- }
- return 0;
- }
- EXPORT_SYMBOL_GPL(br_mst_get_info);
- int br_mst_get_state(const struct net_device *dev, u16 msti, u8 *state)
- {
- const struct net_bridge_port *p = NULL;
- const struct net_bridge_vlan_group *vg;
- const struct net_bridge_vlan *v;
- ASSERT_RTNL();
- p = br_port_get_check_rtnl(dev);
- if (!p || !br_opt_get(p->br, BROPT_MST_ENABLED))
- return -EINVAL;
- vg = nbp_vlan_group(p);
- list_for_each_entry(v, &vg->vlan_list, vlist) {
- if (v->brvlan->msti == msti) {
- *state = v->state;
- return 0;
- }
- }
- return -ENOENT;
- }
- EXPORT_SYMBOL_GPL(br_mst_get_state);
- static void br_mst_vlan_set_state(struct net_bridge_port *p, struct net_bridge_vlan *v,
- u8 state)
- {
- struct net_bridge_vlan_group *vg = nbp_vlan_group(p);
- if (v->state == state)
- return;
- br_vlan_set_state(v, state);
- if (v->vid == vg->pvid)
- br_vlan_set_pvid_state(vg, state);
- }
- int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state,
- struct netlink_ext_ack *extack)
- {
- struct switchdev_attr attr = {
- .id = SWITCHDEV_ATTR_ID_PORT_MST_STATE,
- .orig_dev = p->dev,
- .u.mst_state = {
- .msti = msti,
- .state = state,
- },
- };
- struct net_bridge_vlan_group *vg;
- struct net_bridge_vlan *v;
- int err;
- vg = nbp_vlan_group(p);
- if (!vg)
- return 0;
- /* MSTI 0 (CST) state changes are notified via the regular
- * SWITCHDEV_ATTR_ID_PORT_STP_STATE.
- */
- if (msti) {
- err = switchdev_port_attr_set(p->dev, &attr, extack);
- if (err && err != -EOPNOTSUPP)
- return err;
- }
- list_for_each_entry(v, &vg->vlan_list, vlist) {
- if (v->brvlan->msti != msti)
- continue;
- br_mst_vlan_set_state(p, v, state);
- }
- return 0;
- }
- static void br_mst_vlan_sync_state(struct net_bridge_vlan *pv, u16 msti)
- {
- struct net_bridge_vlan_group *vg = nbp_vlan_group(pv->port);
- struct net_bridge_vlan *v;
- list_for_each_entry(v, &vg->vlan_list, vlist) {
- /* If this port already has a defined state in this
- * MSTI (through some other VLAN membership), inherit
- * it.
- */
- if (v != pv && v->brvlan->msti == msti) {
- br_mst_vlan_set_state(pv->port, pv, v->state);
- return;
- }
- }
- /* Otherwise, start out in a new MSTI with all ports disabled. */
- return br_mst_vlan_set_state(pv->port, pv, BR_STATE_DISABLED);
- }
- int br_mst_vlan_set_msti(struct net_bridge_vlan *mv, u16 msti)
- {
- struct switchdev_attr attr = {
- .id = SWITCHDEV_ATTR_ID_VLAN_MSTI,
- .orig_dev = mv->br->dev,
- .u.vlan_msti = {
- .vid = mv->vid,
- .msti = msti,
- },
- };
- struct net_bridge_vlan_group *vg;
- struct net_bridge_vlan *pv;
- struct net_bridge_port *p;
- int err;
- if (mv->msti == msti)
- return 0;
- err = switchdev_port_attr_set(mv->br->dev, &attr, NULL);
- if (err && err != -EOPNOTSUPP)
- return err;
- mv->msti = msti;
- list_for_each_entry(p, &mv->br->port_list, list) {
- vg = nbp_vlan_group(p);
- pv = br_vlan_find(vg, mv->vid);
- if (pv)
- br_mst_vlan_sync_state(pv, msti);
- }
- return 0;
- }
- void br_mst_vlan_init_state(struct net_bridge_vlan *v)
- {
- /* VLANs always start out in MSTI 0 (CST) */
- v->msti = 0;
- if (br_vlan_is_master(v))
- v->state = BR_STATE_FORWARDING;
- else
- v->state = v->port->state;
- }
- int br_mst_set_enabled(struct net_bridge *br, bool on,
- struct netlink_ext_ack *extack)
- {
- struct switchdev_attr attr = {
- .id = SWITCHDEV_ATTR_ID_BRIDGE_MST,
- .orig_dev = br->dev,
- .u.mst = on,
- };
- struct net_bridge_vlan_group *vg;
- struct net_bridge_port *p;
- int err;
- list_for_each_entry(p, &br->port_list, list) {
- vg = nbp_vlan_group(p);
- if (!vg->num_vlans)
- continue;
- NL_SET_ERR_MSG(extack,
- "MST mode can't be changed while VLANs exist");
- return -EBUSY;
- }
- if (br_opt_get(br, BROPT_MST_ENABLED) == on)
- return 0;
- err = switchdev_port_attr_set(br->dev, &attr, extack);
- if (err && err != -EOPNOTSUPP)
- return err;
- if (on)
- static_branch_enable(&br_mst_used);
- else
- static_branch_disable(&br_mst_used);
- br_opt_toggle(br, BROPT_MST_ENABLED, on);
- return 0;
- }
- size_t br_mst_info_size(const struct net_bridge_vlan_group *vg)
- {
- DECLARE_BITMAP(seen, VLAN_N_VID) = { 0 };
- const struct net_bridge_vlan *v;
- size_t sz;
- /* IFLA_BRIDGE_MST */
- sz = nla_total_size(0);
- list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
- if (test_bit(v->brvlan->msti, seen))
- continue;
- /* IFLA_BRIDGE_MST_ENTRY */
- sz += nla_total_size(0) +
- /* IFLA_BRIDGE_MST_ENTRY_MSTI */
- nla_total_size(sizeof(u16)) +
- /* IFLA_BRIDGE_MST_ENTRY_STATE */
- nla_total_size(sizeof(u8));
- __set_bit(v->brvlan->msti, seen);
- }
- return sz;
- }
- int br_mst_fill_info(struct sk_buff *skb,
- const struct net_bridge_vlan_group *vg)
- {
- DECLARE_BITMAP(seen, VLAN_N_VID) = { 0 };
- const struct net_bridge_vlan *v;
- struct nlattr *nest;
- int err = 0;
- list_for_each_entry(v, &vg->vlan_list, vlist) {
- if (test_bit(v->brvlan->msti, seen))
- continue;
- nest = nla_nest_start_noflag(skb, IFLA_BRIDGE_MST_ENTRY);
- if (!nest ||
- nla_put_u16(skb, IFLA_BRIDGE_MST_ENTRY_MSTI, v->brvlan->msti) ||
- nla_put_u8(skb, IFLA_BRIDGE_MST_ENTRY_STATE, v->state)) {
- err = -EMSGSIZE;
- break;
- }
- nla_nest_end(skb, nest);
- __set_bit(v->brvlan->msti, seen);
- }
- return err;
- }
- static const struct nla_policy br_mst_nl_policy[IFLA_BRIDGE_MST_ENTRY_MAX + 1] = {
- [IFLA_BRIDGE_MST_ENTRY_MSTI] = NLA_POLICY_RANGE(NLA_U16,
- 1, /* 0 reserved for CST */
- VLAN_N_VID - 1),
- [IFLA_BRIDGE_MST_ENTRY_STATE] = NLA_POLICY_RANGE(NLA_U8,
- BR_STATE_DISABLED,
- BR_STATE_BLOCKING),
- };
- static int br_mst_process_one(struct net_bridge_port *p,
- const struct nlattr *attr,
- struct netlink_ext_ack *extack)
- {
- struct nlattr *tb[IFLA_BRIDGE_MST_ENTRY_MAX + 1];
- u16 msti;
- u8 state;
- int err;
- err = nla_parse_nested(tb, IFLA_BRIDGE_MST_ENTRY_MAX, attr,
- br_mst_nl_policy, extack);
- if (err)
- return err;
- if (!tb[IFLA_BRIDGE_MST_ENTRY_MSTI]) {
- NL_SET_ERR_MSG_MOD(extack, "MSTI not specified");
- return -EINVAL;
- }
- if (!tb[IFLA_BRIDGE_MST_ENTRY_STATE]) {
- NL_SET_ERR_MSG_MOD(extack, "State not specified");
- return -EINVAL;
- }
- msti = nla_get_u16(tb[IFLA_BRIDGE_MST_ENTRY_MSTI]);
- state = nla_get_u8(tb[IFLA_BRIDGE_MST_ENTRY_STATE]);
- return br_mst_set_state(p, msti, state, extack);
- }
- int br_mst_process(struct net_bridge_port *p, const struct nlattr *mst_attr,
- struct netlink_ext_ack *extack)
- {
- struct nlattr *attr;
- int err, msts = 0;
- int rem;
- if (!br_opt_get(p->br, BROPT_MST_ENABLED)) {
- NL_SET_ERR_MSG_MOD(extack, "Can't modify MST state when MST is disabled");
- return -EBUSY;
- }
- nla_for_each_nested(attr, mst_attr, rem) {
- switch (nla_type(attr)) {
- case IFLA_BRIDGE_MST_ENTRY:
- err = br_mst_process_one(p, attr, extack);
- break;
- default:
- continue;
- }
- msts++;
- if (err)
- break;
- }
- if (!msts) {
- NL_SET_ERR_MSG_MOD(extack, "Found no MST entries to process");
- err = -EINVAL;
- }
- return err;
- }
|