1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Bridge Multiple Spanning Tree Support |
4 | * |
5 | * Authors: |
6 | * Tobias Waldekranz <tobias@waldekranz.com> |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <net/switchdev.h> |
11 | |
12 | #include "br_private.h" |
13 | |
14 | DEFINE_STATIC_KEY_FALSE(br_mst_used); |
15 | |
16 | bool br_mst_enabled(const struct net_device *dev) |
17 | { |
18 | if (!netif_is_bridge_master(dev)) |
19 | return false; |
20 | |
21 | return br_opt_get(br: netdev_priv(dev), opt: BROPT_MST_ENABLED); |
22 | } |
23 | EXPORT_SYMBOL_GPL(br_mst_enabled); |
24 | |
25 | int br_mst_get_info(const struct net_device *dev, u16 msti, unsigned long *vids) |
26 | { |
27 | const struct net_bridge_vlan_group *vg; |
28 | const struct net_bridge_vlan *v; |
29 | const struct net_bridge *br; |
30 | |
31 | ASSERT_RTNL(); |
32 | |
33 | if (!netif_is_bridge_master(dev)) |
34 | return -EINVAL; |
35 | |
36 | br = netdev_priv(dev); |
37 | if (!br_opt_get(br, opt: BROPT_MST_ENABLED)) |
38 | return -EINVAL; |
39 | |
40 | vg = br_vlan_group(br); |
41 | |
42 | list_for_each_entry(v, &vg->vlan_list, vlist) { |
43 | if (v->msti == msti) |
44 | __set_bit(v->vid, vids); |
45 | } |
46 | |
47 | return 0; |
48 | } |
49 | EXPORT_SYMBOL_GPL(br_mst_get_info); |
50 | |
51 | int br_mst_get_state(const struct net_device *dev, u16 msti, u8 *state) |
52 | { |
53 | const struct net_bridge_port *p = NULL; |
54 | const struct net_bridge_vlan_group *vg; |
55 | const struct net_bridge_vlan *v; |
56 | |
57 | ASSERT_RTNL(); |
58 | |
59 | p = br_port_get_check_rtnl(dev); |
60 | if (!p || !br_opt_get(br: p->br, opt: BROPT_MST_ENABLED)) |
61 | return -EINVAL; |
62 | |
63 | vg = nbp_vlan_group(p); |
64 | |
65 | list_for_each_entry(v, &vg->vlan_list, vlist) { |
66 | if (v->brvlan->msti == msti) { |
67 | *state = v->state; |
68 | return 0; |
69 | } |
70 | } |
71 | |
72 | return -ENOENT; |
73 | } |
74 | EXPORT_SYMBOL_GPL(br_mst_get_state); |
75 | |
76 | static void br_mst_vlan_set_state(struct net_bridge_port *p, struct net_bridge_vlan *v, |
77 | u8 state) |
78 | { |
79 | struct net_bridge_vlan_group *vg = nbp_vlan_group(p); |
80 | |
81 | if (v->state == state) |
82 | return; |
83 | |
84 | br_vlan_set_state(v, state); |
85 | |
86 | if (v->vid == vg->pvid) |
87 | br_vlan_set_pvid_state(vg, state); |
88 | } |
89 | |
90 | int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state, |
91 | struct netlink_ext_ack *extack) |
92 | { |
93 | struct switchdev_attr attr = { |
94 | .id = SWITCHDEV_ATTR_ID_PORT_MST_STATE, |
95 | .orig_dev = p->dev, |
96 | .u.mst_state = { |
97 | .msti = msti, |
98 | .state = state, |
99 | }, |
100 | }; |
101 | struct net_bridge_vlan_group *vg; |
102 | struct net_bridge_vlan *v; |
103 | int err; |
104 | |
105 | vg = nbp_vlan_group(p); |
106 | if (!vg) |
107 | return 0; |
108 | |
109 | /* MSTI 0 (CST) state changes are notified via the regular |
110 | * SWITCHDEV_ATTR_ID_PORT_STP_STATE. |
111 | */ |
112 | if (msti) { |
113 | err = switchdev_port_attr_set(dev: p->dev, attr: &attr, extack); |
114 | if (err && err != -EOPNOTSUPP) |
115 | return err; |
116 | } |
117 | |
118 | list_for_each_entry(v, &vg->vlan_list, vlist) { |
119 | if (v->brvlan->msti != msti) |
120 | continue; |
121 | |
122 | br_mst_vlan_set_state(p, v, state); |
123 | } |
124 | |
125 | return 0; |
126 | } |
127 | |
128 | static void br_mst_vlan_sync_state(struct net_bridge_vlan *pv, u16 msti) |
129 | { |
130 | struct net_bridge_vlan_group *vg = nbp_vlan_group(p: pv->port); |
131 | struct net_bridge_vlan *v; |
132 | |
133 | list_for_each_entry(v, &vg->vlan_list, vlist) { |
134 | /* If this port already has a defined state in this |
135 | * MSTI (through some other VLAN membership), inherit |
136 | * it. |
137 | */ |
138 | if (v != pv && v->brvlan->msti == msti) { |
139 | br_mst_vlan_set_state(p: pv->port, v: pv, state: v->state); |
140 | return; |
141 | } |
142 | } |
143 | |
144 | /* Otherwise, start out in a new MSTI with all ports disabled. */ |
145 | return br_mst_vlan_set_state(p: pv->port, v: pv, BR_STATE_DISABLED); |
146 | } |
147 | |
148 | int br_mst_vlan_set_msti(struct net_bridge_vlan *mv, u16 msti) |
149 | { |
150 | struct switchdev_attr attr = { |
151 | .id = SWITCHDEV_ATTR_ID_VLAN_MSTI, |
152 | .orig_dev = mv->br->dev, |
153 | .u.vlan_msti = { |
154 | .vid = mv->vid, |
155 | .msti = msti, |
156 | }, |
157 | }; |
158 | struct net_bridge_vlan_group *vg; |
159 | struct net_bridge_vlan *pv; |
160 | struct net_bridge_port *p; |
161 | int err; |
162 | |
163 | if (mv->msti == msti) |
164 | return 0; |
165 | |
166 | err = switchdev_port_attr_set(dev: mv->br->dev, attr: &attr, NULL); |
167 | if (err && err != -EOPNOTSUPP) |
168 | return err; |
169 | |
170 | mv->msti = msti; |
171 | |
172 | list_for_each_entry(p, &mv->br->port_list, list) { |
173 | vg = nbp_vlan_group(p); |
174 | |
175 | pv = br_vlan_find(vg, vid: mv->vid); |
176 | if (pv) |
177 | br_mst_vlan_sync_state(pv, msti); |
178 | } |
179 | |
180 | return 0; |
181 | } |
182 | |
183 | void br_mst_vlan_init_state(struct net_bridge_vlan *v) |
184 | { |
185 | /* VLANs always start out in MSTI 0 (CST) */ |
186 | v->msti = 0; |
187 | |
188 | if (br_vlan_is_master(v)) |
189 | v->state = BR_STATE_FORWARDING; |
190 | else |
191 | v->state = v->port->state; |
192 | } |
193 | |
194 | int br_mst_set_enabled(struct net_bridge *br, bool on, |
195 | struct netlink_ext_ack *extack) |
196 | { |
197 | struct switchdev_attr attr = { |
198 | .id = SWITCHDEV_ATTR_ID_BRIDGE_MST, |
199 | .orig_dev = br->dev, |
200 | .u.mst = on, |
201 | }; |
202 | struct net_bridge_vlan_group *vg; |
203 | struct net_bridge_port *p; |
204 | int err; |
205 | |
206 | list_for_each_entry(p, &br->port_list, list) { |
207 | vg = nbp_vlan_group(p); |
208 | |
209 | if (!vg->num_vlans) |
210 | continue; |
211 | |
212 | NL_SET_ERR_MSG(extack, |
213 | "MST mode can't be changed while VLANs exist" ); |
214 | return -EBUSY; |
215 | } |
216 | |
217 | if (br_opt_get(br, opt: BROPT_MST_ENABLED) == on) |
218 | return 0; |
219 | |
220 | err = switchdev_port_attr_set(dev: br->dev, attr: &attr, extack); |
221 | if (err && err != -EOPNOTSUPP) |
222 | return err; |
223 | |
224 | if (on) |
225 | static_branch_enable(&br_mst_used); |
226 | else |
227 | static_branch_disable(&br_mst_used); |
228 | |
229 | br_opt_toggle(br, opt: BROPT_MST_ENABLED, on); |
230 | return 0; |
231 | } |
232 | |
233 | size_t br_mst_info_size(const struct net_bridge_vlan_group *vg) |
234 | { |
235 | DECLARE_BITMAP(seen, VLAN_N_VID) = { 0 }; |
236 | const struct net_bridge_vlan *v; |
237 | size_t sz; |
238 | |
239 | /* IFLA_BRIDGE_MST */ |
240 | sz = nla_total_size(payload: 0); |
241 | |
242 | list_for_each_entry_rcu(v, &vg->vlan_list, vlist) { |
243 | if (test_bit(v->brvlan->msti, seen)) |
244 | continue; |
245 | |
246 | /* IFLA_BRIDGE_MST_ENTRY */ |
247 | sz += nla_total_size(payload: 0) + |
248 | /* IFLA_BRIDGE_MST_ENTRY_MSTI */ |
249 | nla_total_size(payload: sizeof(u16)) + |
250 | /* IFLA_BRIDGE_MST_ENTRY_STATE */ |
251 | nla_total_size(payload: sizeof(u8)); |
252 | |
253 | __set_bit(v->brvlan->msti, seen); |
254 | } |
255 | |
256 | return sz; |
257 | } |
258 | |
259 | int br_mst_fill_info(struct sk_buff *skb, |
260 | const struct net_bridge_vlan_group *vg) |
261 | { |
262 | DECLARE_BITMAP(seen, VLAN_N_VID) = { 0 }; |
263 | const struct net_bridge_vlan *v; |
264 | struct nlattr *nest; |
265 | int err = 0; |
266 | |
267 | list_for_each_entry(v, &vg->vlan_list, vlist) { |
268 | if (test_bit(v->brvlan->msti, seen)) |
269 | continue; |
270 | |
271 | nest = nla_nest_start_noflag(skb, attrtype: IFLA_BRIDGE_MST_ENTRY); |
272 | if (!nest || |
273 | nla_put_u16(skb, attrtype: IFLA_BRIDGE_MST_ENTRY_MSTI, value: v->brvlan->msti) || |
274 | nla_put_u8(skb, attrtype: IFLA_BRIDGE_MST_ENTRY_STATE, value: v->state)) { |
275 | err = -EMSGSIZE; |
276 | break; |
277 | } |
278 | nla_nest_end(skb, start: nest); |
279 | |
280 | __set_bit(v->brvlan->msti, seen); |
281 | } |
282 | |
283 | return err; |
284 | } |
285 | |
286 | static const struct nla_policy br_mst_nl_policy[IFLA_BRIDGE_MST_ENTRY_MAX + 1] = { |
287 | [IFLA_BRIDGE_MST_ENTRY_MSTI] = NLA_POLICY_RANGE(NLA_U16, |
288 | 1, /* 0 reserved for CST */ |
289 | VLAN_N_VID - 1), |
290 | [IFLA_BRIDGE_MST_ENTRY_STATE] = NLA_POLICY_RANGE(NLA_U8, |
291 | BR_STATE_DISABLED, |
292 | BR_STATE_BLOCKING), |
293 | }; |
294 | |
295 | static int br_mst_process_one(struct net_bridge_port *p, |
296 | const struct nlattr *attr, |
297 | struct netlink_ext_ack *extack) |
298 | { |
299 | struct nlattr *tb[IFLA_BRIDGE_MST_ENTRY_MAX + 1]; |
300 | u16 msti; |
301 | u8 state; |
302 | int err; |
303 | |
304 | err = nla_parse_nested(tb, IFLA_BRIDGE_MST_ENTRY_MAX, nla: attr, |
305 | policy: br_mst_nl_policy, extack); |
306 | if (err) |
307 | return err; |
308 | |
309 | if (!tb[IFLA_BRIDGE_MST_ENTRY_MSTI]) { |
310 | NL_SET_ERR_MSG_MOD(extack, "MSTI not specified" ); |
311 | return -EINVAL; |
312 | } |
313 | |
314 | if (!tb[IFLA_BRIDGE_MST_ENTRY_STATE]) { |
315 | NL_SET_ERR_MSG_MOD(extack, "State not specified" ); |
316 | return -EINVAL; |
317 | } |
318 | |
319 | msti = nla_get_u16(nla: tb[IFLA_BRIDGE_MST_ENTRY_MSTI]); |
320 | state = nla_get_u8(nla: tb[IFLA_BRIDGE_MST_ENTRY_STATE]); |
321 | |
322 | return br_mst_set_state(p, msti, state, extack); |
323 | } |
324 | |
325 | int br_mst_process(struct net_bridge_port *p, const struct nlattr *mst_attr, |
326 | struct netlink_ext_ack *extack) |
327 | { |
328 | struct nlattr *attr; |
329 | int err, msts = 0; |
330 | int rem; |
331 | |
332 | if (!br_opt_get(br: p->br, opt: BROPT_MST_ENABLED)) { |
333 | NL_SET_ERR_MSG_MOD(extack, "Can't modify MST state when MST is disabled" ); |
334 | return -EBUSY; |
335 | } |
336 | |
337 | nla_for_each_nested(attr, mst_attr, rem) { |
338 | switch (nla_type(nla: attr)) { |
339 | case IFLA_BRIDGE_MST_ENTRY: |
340 | err = br_mst_process_one(p, attr, extack); |
341 | break; |
342 | default: |
343 | continue; |
344 | } |
345 | |
346 | msts++; |
347 | if (err) |
348 | break; |
349 | } |
350 | |
351 | if (!msts) { |
352 | NL_SET_ERR_MSG_MOD(extack, "Found no MST entries to process" ); |
353 | err = -EINVAL; |
354 | } |
355 | |
356 | return err; |
357 | } |
358 | |