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