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 <tobias@waldekranz.com> | |
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 | ||
ec7328b5 TW |
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 | { | |
7ae9147f TW |
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 | }; | |
ec7328b5 TW |
101 | struct net_bridge_vlan_group *vg; |
102 | struct net_bridge_vlan *v; | |
7ae9147f | 103 | int err; |
ec7328b5 TW |
104 | |
105 | vg = nbp_vlan_group(p); | |
106 | if (!vg) | |
107 | return 0; | |
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) | |
115 | return err; | |
116 | } | |
117 | ||
ec7328b5 TW |
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 | ||
8c678d60 TW |
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(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(pv->port, pv, 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(pv->port, pv, BR_STATE_DISABLED); | |
146 | } | |
147 | ||
148 | int br_mst_vlan_set_msti(struct net_bridge_vlan *mv, u16 msti) | |
149 | { | |
6284c723 TW |
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 | }; | |
8c678d60 TW |
158 | struct net_bridge_vlan_group *vg; |
159 | struct net_bridge_vlan *pv; | |
160 | struct net_bridge_port *p; | |
6284c723 | 161 | int err; |
8c678d60 TW |
162 | |
163 | if (mv->msti == msti) | |
164 | return 0; | |
165 | ||
6284c723 TW |
166 | err = switchdev_port_attr_set(mv->br->dev, &attr, NULL); |
167 | if (err && err != -EOPNOTSUPP) | |
168 | return err; | |
169 | ||
8c678d60 TW |
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, mv->vid); | |
176 | if (pv) | |
177 | br_mst_vlan_sync_state(pv, msti); | |
178 | } | |
179 | ||
180 | return 0; | |
181 | } | |
182 | ||
ec7328b5 TW |
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 | { | |
87c167bb TW |
197 | struct switchdev_attr attr = { |
198 | .id = SWITCHDEV_ATTR_ID_BRIDGE_MST, | |
199 | .orig_dev = br->dev, | |
200 | .u.mst = on, | |
201 | }; | |
ec7328b5 TW |
202 | struct net_bridge_vlan_group *vg; |
203 | struct net_bridge_port *p; | |
87c167bb | 204 | int err; |
ec7328b5 TW |
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, BROPT_MST_ENABLED) == on) | |
218 | return 0; | |
219 | ||
87c167bb TW |
220 | err = switchdev_port_attr_set(br->dev, &attr, extack); |
221 | if (err && err != -EOPNOTSUPP) | |
222 | return err; | |
223 | ||
ec7328b5 TW |
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, BROPT_MST_ENABLED, on); | |
230 | return 0; | |
231 | } | |
122c2948 TW |
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(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(0) + | |
248 | /* IFLA_BRIDGE_MST_ENTRY_MSTI */ | |
249 | nla_total_size(sizeof(u16)) + | |
250 | /* IFLA_BRIDGE_MST_ENTRY_STATE */ | |
251 | nla_total_size(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, IFLA_BRIDGE_MST_ENTRY); | |
272 | if (!nest || | |
273 | nla_put_u16(skb, IFLA_BRIDGE_MST_ENTRY_MSTI, v->brvlan->msti) || | |
274 | nla_put_u8(skb, IFLA_BRIDGE_MST_ENTRY_STATE, v->state)) { | |
275 | err = -EMSGSIZE; | |
276 | break; | |
277 | } | |
278 | nla_nest_end(skb, 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, attr, | |
305 | 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(tb[IFLA_BRIDGE_MST_ENTRY_MSTI]); | |
320 | state = nla_get_u8(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(p->br, 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(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 | } |