1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // Copyright (c) 2020, Nikolay Aleksandrov <nikolay@cumulusnetworks.com> |
3 | #include <linux/kernel.h> |
4 | #include <linux/netdevice.h> |
5 | #include <linux/rtnetlink.h> |
6 | #include <linux/slab.h> |
7 | #include <net/ip_tunnels.h> |
8 | |
9 | #include "br_private.h" |
10 | #include "br_private_tunnel.h" |
11 | |
12 | static bool __vlan_tun_put(struct sk_buff *skb, const struct net_bridge_vlan *v) |
13 | { |
14 | __be32 tid = tunnel_id_to_key32(tun_id: v->tinfo.tunnel_id); |
15 | struct nlattr *nest; |
16 | |
17 | if (!v->tinfo.tunnel_dst) |
18 | return true; |
19 | |
20 | nest = nla_nest_start(skb, attrtype: BRIDGE_VLANDB_ENTRY_TUNNEL_INFO); |
21 | if (!nest) |
22 | return false; |
23 | if (nla_put_u32(skb, attrtype: BRIDGE_VLANDB_TINFO_ID, be32_to_cpu(tid))) { |
24 | nla_nest_cancel(skb, start: nest); |
25 | return false; |
26 | } |
27 | nla_nest_end(skb, start: nest); |
28 | |
29 | return true; |
30 | } |
31 | |
32 | static bool __vlan_tun_can_enter_range(const struct net_bridge_vlan *v_curr, |
33 | const struct net_bridge_vlan *range_end) |
34 | { |
35 | return (!v_curr->tinfo.tunnel_dst && !range_end->tinfo.tunnel_dst) || |
36 | vlan_tunid_inrange(v_curr, v_last: range_end); |
37 | } |
38 | |
39 | /* check if the options' state of v_curr allow it to enter the range */ |
40 | bool br_vlan_opts_eq_range(const struct net_bridge_vlan *v_curr, |
41 | const struct net_bridge_vlan *range_end) |
42 | { |
43 | u8 range_mc_rtr = br_vlan_multicast_router(v: range_end); |
44 | u8 curr_mc_rtr = br_vlan_multicast_router(v: v_curr); |
45 | |
46 | return v_curr->state == range_end->state && |
47 | __vlan_tun_can_enter_range(v_curr, range_end) && |
48 | curr_mc_rtr == range_mc_rtr; |
49 | } |
50 | |
51 | bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v, |
52 | const struct net_bridge_port *p) |
53 | { |
54 | if (nla_put_u8(skb, attrtype: BRIDGE_VLANDB_ENTRY_STATE, value: br_vlan_get_state(v)) || |
55 | !__vlan_tun_put(skb, v) || |
56 | nla_put_u8(skb, attrtype: BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS, |
57 | value: !!(v->priv_flags & BR_VLFLAG_NEIGH_SUPPRESS_ENABLED))) |
58 | return false; |
59 | |
60 | #ifdef CONFIG_BRIDGE_IGMP_SNOOPING |
61 | if (nla_put_u8(skb, attrtype: BRIDGE_VLANDB_ENTRY_MCAST_ROUTER, |
62 | value: br_vlan_multicast_router(v))) |
63 | return false; |
64 | if (p && !br_multicast_port_ctx_vlan_disabled(pmctx: &v->port_mcast_ctx) && |
65 | (nla_put_u32(skb, attrtype: BRIDGE_VLANDB_ENTRY_MCAST_N_GROUPS, |
66 | value: br_multicast_ngroups_get(pmctx: &v->port_mcast_ctx)) || |
67 | nla_put_u32(skb, attrtype: BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS, |
68 | value: br_multicast_ngroups_get_max(pmctx: &v->port_mcast_ctx)))) |
69 | return false; |
70 | #endif |
71 | |
72 | return true; |
73 | } |
74 | |
75 | size_t br_vlan_opts_nl_size(void) |
76 | { |
77 | return nla_total_size(payload: sizeof(u8)) /* BRIDGE_VLANDB_ENTRY_STATE */ |
78 | + nla_total_size(payload: 0) /* BRIDGE_VLANDB_ENTRY_TUNNEL_INFO */ |
79 | + nla_total_size(payload: sizeof(u32)) /* BRIDGE_VLANDB_TINFO_ID */ |
80 | #ifdef CONFIG_BRIDGE_IGMP_SNOOPING |
81 | + nla_total_size(payload: sizeof(u8)) /* BRIDGE_VLANDB_ENTRY_MCAST_ROUTER */ |
82 | + nla_total_size(payload: sizeof(u32)) /* BRIDGE_VLANDB_ENTRY_MCAST_N_GROUPS */ |
83 | + nla_total_size(payload: sizeof(u32)) /* BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS */ |
84 | #endif |
85 | + nla_total_size(payload: sizeof(u8)) /* BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS */ |
86 | + 0; |
87 | } |
88 | |
89 | static int br_vlan_modify_state(struct net_bridge_vlan_group *vg, |
90 | struct net_bridge_vlan *v, |
91 | u8 state, |
92 | bool *changed, |
93 | struct netlink_ext_ack *extack) |
94 | { |
95 | struct net_bridge *br; |
96 | |
97 | ASSERT_RTNL(); |
98 | |
99 | if (state > BR_STATE_BLOCKING) { |
100 | NL_SET_ERR_MSG_MOD(extack, "Invalid vlan state" ); |
101 | return -EINVAL; |
102 | } |
103 | |
104 | if (br_vlan_is_brentry(v)) |
105 | br = v->br; |
106 | else |
107 | br = v->port->br; |
108 | |
109 | if (br->stp_enabled == BR_KERNEL_STP) { |
110 | NL_SET_ERR_MSG_MOD(extack, "Can't modify vlan state when using kernel STP" ); |
111 | return -EBUSY; |
112 | } |
113 | |
114 | if (br_opt_get(br, opt: BROPT_MST_ENABLED)) { |
115 | NL_SET_ERR_MSG_MOD(extack, "Can't modify vlan state directly when MST is enabled" ); |
116 | return -EBUSY; |
117 | } |
118 | |
119 | if (v->state == state) |
120 | return 0; |
121 | |
122 | if (v->vid == br_get_pvid(vg)) |
123 | br_vlan_set_pvid_state(vg, state); |
124 | |
125 | br_vlan_set_state(v, state); |
126 | *changed = true; |
127 | |
128 | return 0; |
129 | } |
130 | |
131 | static const struct nla_policy br_vlandb_tinfo_pol[BRIDGE_VLANDB_TINFO_MAX + 1] = { |
132 | [BRIDGE_VLANDB_TINFO_ID] = { .type = NLA_U32 }, |
133 | [BRIDGE_VLANDB_TINFO_CMD] = { .type = NLA_U32 }, |
134 | }; |
135 | |
136 | static int br_vlan_modify_tunnel(const struct net_bridge_port *p, |
137 | struct net_bridge_vlan *v, |
138 | struct nlattr **tb, |
139 | bool *changed, |
140 | struct netlink_ext_ack *extack) |
141 | { |
142 | struct nlattr *tun_tb[BRIDGE_VLANDB_TINFO_MAX + 1], *attr; |
143 | struct bridge_vlan_info *vinfo; |
144 | u32 tun_id = 0; |
145 | int cmd, err; |
146 | |
147 | if (!p) { |
148 | NL_SET_ERR_MSG_MOD(extack, "Can't modify tunnel mapping of non-port vlans" ); |
149 | return -EINVAL; |
150 | } |
151 | if (!(p->flags & BR_VLAN_TUNNEL)) { |
152 | NL_SET_ERR_MSG_MOD(extack, "Port doesn't have tunnel flag set" ); |
153 | return -EINVAL; |
154 | } |
155 | |
156 | attr = tb[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO]; |
157 | err = nla_parse_nested(tb: tun_tb, BRIDGE_VLANDB_TINFO_MAX, nla: attr, |
158 | policy: br_vlandb_tinfo_pol, extack); |
159 | if (err) |
160 | return err; |
161 | |
162 | if (!tun_tb[BRIDGE_VLANDB_TINFO_CMD]) { |
163 | NL_SET_ERR_MSG_MOD(extack, "Missing tunnel command attribute" ); |
164 | return -ENOENT; |
165 | } |
166 | cmd = nla_get_u32(nla: tun_tb[BRIDGE_VLANDB_TINFO_CMD]); |
167 | switch (cmd) { |
168 | case RTM_SETLINK: |
169 | if (!tun_tb[BRIDGE_VLANDB_TINFO_ID]) { |
170 | NL_SET_ERR_MSG_MOD(extack, "Missing tunnel id attribute" ); |
171 | return -ENOENT; |
172 | } |
173 | /* when working on vlan ranges this is the starting tunnel id */ |
174 | tun_id = nla_get_u32(nla: tun_tb[BRIDGE_VLANDB_TINFO_ID]); |
175 | /* vlan info attr is guaranteed by br_vlan_rtm_process_one */ |
176 | vinfo = nla_data(nla: tb[BRIDGE_VLANDB_ENTRY_INFO]); |
177 | /* tunnel ids are mapped to each vlan in increasing order, |
178 | * the starting vlan is in BRIDGE_VLANDB_ENTRY_INFO and v is the |
179 | * current vlan, so we compute: tun_id + v - vinfo->vid |
180 | */ |
181 | tun_id += v->vid - vinfo->vid; |
182 | break; |
183 | case RTM_DELLINK: |
184 | break; |
185 | default: |
186 | NL_SET_ERR_MSG_MOD(extack, "Unsupported tunnel command" ); |
187 | return -EINVAL; |
188 | } |
189 | |
190 | return br_vlan_tunnel_info(p, cmd, vid: v->vid, tun_id, changed); |
191 | } |
192 | |
193 | static int br_vlan_process_one_opts(const struct net_bridge *br, |
194 | const struct net_bridge_port *p, |
195 | struct net_bridge_vlan_group *vg, |
196 | struct net_bridge_vlan *v, |
197 | struct nlattr **tb, |
198 | bool *changed, |
199 | struct netlink_ext_ack *extack) |
200 | { |
201 | int err; |
202 | |
203 | *changed = false; |
204 | if (tb[BRIDGE_VLANDB_ENTRY_STATE]) { |
205 | u8 state = nla_get_u8(nla: tb[BRIDGE_VLANDB_ENTRY_STATE]); |
206 | |
207 | err = br_vlan_modify_state(vg, v, state, changed, extack); |
208 | if (err) |
209 | return err; |
210 | } |
211 | if (tb[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO]) { |
212 | err = br_vlan_modify_tunnel(p, v, tb, changed, extack); |
213 | if (err) |
214 | return err; |
215 | } |
216 | |
217 | #ifdef CONFIG_BRIDGE_IGMP_SNOOPING |
218 | if (tb[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER]) { |
219 | u8 val; |
220 | |
221 | val = nla_get_u8(nla: tb[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER]); |
222 | err = br_multicast_set_vlan_router(v, mcast_router: val); |
223 | if (err) |
224 | return err; |
225 | *changed = true; |
226 | } |
227 | if (tb[BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS]) { |
228 | u32 val; |
229 | |
230 | if (!p) { |
231 | NL_SET_ERR_MSG_MOD(extack, "Can't set mcast_max_groups for non-port vlans" ); |
232 | return -EINVAL; |
233 | } |
234 | if (br_multicast_port_ctx_vlan_disabled(pmctx: &v->port_mcast_ctx)) { |
235 | NL_SET_ERR_MSG_MOD(extack, "Multicast snooping disabled on this VLAN" ); |
236 | return -EINVAL; |
237 | } |
238 | |
239 | val = nla_get_u32(nla: tb[BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS]); |
240 | br_multicast_ngroups_set_max(pmctx: &v->port_mcast_ctx, max: val); |
241 | *changed = true; |
242 | } |
243 | #endif |
244 | |
245 | if (tb[BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS]) { |
246 | bool enabled = v->priv_flags & BR_VLFLAG_NEIGH_SUPPRESS_ENABLED; |
247 | bool val = nla_get_u8(nla: tb[BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS]); |
248 | |
249 | if (!p) { |
250 | NL_SET_ERR_MSG_MOD(extack, "Can't set neigh_suppress for non-port vlans" ); |
251 | return -EINVAL; |
252 | } |
253 | |
254 | if (val != enabled) { |
255 | v->priv_flags ^= BR_VLFLAG_NEIGH_SUPPRESS_ENABLED; |
256 | *changed = true; |
257 | } |
258 | } |
259 | |
260 | return 0; |
261 | } |
262 | |
263 | int br_vlan_process_options(const struct net_bridge *br, |
264 | const struct net_bridge_port *p, |
265 | struct net_bridge_vlan *range_start, |
266 | struct net_bridge_vlan *range_end, |
267 | struct nlattr **tb, |
268 | struct netlink_ext_ack *extack) |
269 | { |
270 | struct net_bridge_vlan *v, *curr_start = NULL, *curr_end = NULL; |
271 | struct net_bridge_vlan_group *vg; |
272 | int vid, err = 0; |
273 | u16 pvid; |
274 | |
275 | if (p) |
276 | vg = nbp_vlan_group(p); |
277 | else |
278 | vg = br_vlan_group(br); |
279 | |
280 | if (!range_start || !br_vlan_should_use(v: range_start)) { |
281 | NL_SET_ERR_MSG_MOD(extack, "Vlan range start doesn't exist, can't process options" ); |
282 | return -ENOENT; |
283 | } |
284 | if (!range_end || !br_vlan_should_use(v: range_end)) { |
285 | NL_SET_ERR_MSG_MOD(extack, "Vlan range end doesn't exist, can't process options" ); |
286 | return -ENOENT; |
287 | } |
288 | |
289 | pvid = br_get_pvid(vg); |
290 | for (vid = range_start->vid; vid <= range_end->vid; vid++) { |
291 | bool changed = false; |
292 | |
293 | v = br_vlan_find(vg, vid); |
294 | if (!v || !br_vlan_should_use(v)) { |
295 | NL_SET_ERR_MSG_MOD(extack, "Vlan in range doesn't exist, can't process options" ); |
296 | err = -ENOENT; |
297 | break; |
298 | } |
299 | |
300 | err = br_vlan_process_one_opts(br, p, vg, v, tb, changed: &changed, |
301 | extack); |
302 | if (err) |
303 | break; |
304 | |
305 | if (changed) { |
306 | /* vlan options changed, check for range */ |
307 | if (!curr_start) { |
308 | curr_start = v; |
309 | curr_end = v; |
310 | continue; |
311 | } |
312 | |
313 | if (v->vid == pvid || |
314 | !br_vlan_can_enter_range(v_curr: v, range_end: curr_end)) { |
315 | br_vlan_notify(br, p, vid: curr_start->vid, |
316 | vid_range: curr_end->vid, cmd: RTM_NEWVLAN); |
317 | curr_start = v; |
318 | } |
319 | curr_end = v; |
320 | } else { |
321 | /* nothing changed and nothing to notify yet */ |
322 | if (!curr_start) |
323 | continue; |
324 | |
325 | br_vlan_notify(br, p, vid: curr_start->vid, vid_range: curr_end->vid, |
326 | cmd: RTM_NEWVLAN); |
327 | curr_start = NULL; |
328 | curr_end = NULL; |
329 | } |
330 | } |
331 | if (curr_start) |
332 | br_vlan_notify(br, p, vid: curr_start->vid, vid_range: curr_end->vid, |
333 | cmd: RTM_NEWVLAN); |
334 | |
335 | return err; |
336 | } |
337 | |
338 | bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr, |
339 | const struct net_bridge_vlan *r_end) |
340 | { |
341 | return v_curr->vid - r_end->vid == 1 && |
342 | v_curr->msti == r_end->msti && |
343 | ((v_curr->priv_flags ^ r_end->priv_flags) & |
344 | BR_VLFLAG_GLOBAL_MCAST_ENABLED) == 0 && |
345 | br_multicast_ctx_options_equal(brmctx1: &v_curr->br_mcast_ctx, |
346 | brmctx2: &r_end->br_mcast_ctx); |
347 | } |
348 | |
349 | bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range, |
350 | const struct net_bridge_vlan *v_opts) |
351 | { |
352 | struct nlattr *nest2 __maybe_unused; |
353 | u64 clockval __maybe_unused; |
354 | struct nlattr *nest; |
355 | |
356 | nest = nla_nest_start(skb, attrtype: BRIDGE_VLANDB_GLOBAL_OPTIONS); |
357 | if (!nest) |
358 | return false; |
359 | |
360 | if (nla_put_u16(skb, attrtype: BRIDGE_VLANDB_GOPTS_ID, value: vid)) |
361 | goto out_err; |
362 | |
363 | if (vid_range && vid < vid_range && |
364 | nla_put_u16(skb, attrtype: BRIDGE_VLANDB_GOPTS_RANGE, value: vid_range)) |
365 | goto out_err; |
366 | |
367 | #ifdef CONFIG_BRIDGE_IGMP_SNOOPING |
368 | if (nla_put_u8(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING, |
369 | value: !!(v_opts->priv_flags & BR_VLFLAG_GLOBAL_MCAST_ENABLED)) || |
370 | nla_put_u8(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION, |
371 | value: v_opts->br_mcast_ctx.multicast_igmp_version) || |
372 | nla_put_u32(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT, |
373 | value: v_opts->br_mcast_ctx.multicast_last_member_count) || |
374 | nla_put_u32(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT, |
375 | value: v_opts->br_mcast_ctx.multicast_startup_query_count) || |
376 | nla_put_u8(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_QUERIER, |
377 | value: v_opts->br_mcast_ctx.multicast_querier) || |
378 | br_multicast_dump_querier_state(skb, brmctx: &v_opts->br_mcast_ctx, |
379 | nest_attr: BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_STATE)) |
380 | goto out_err; |
381 | |
382 | clockval = jiffies_to_clock_t(x: v_opts->br_mcast_ctx.multicast_last_member_interval); |
383 | if (nla_put_u64_64bit(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL, |
384 | value: clockval, padattr: BRIDGE_VLANDB_GOPTS_PAD)) |
385 | goto out_err; |
386 | clockval = jiffies_to_clock_t(x: v_opts->br_mcast_ctx.multicast_membership_interval); |
387 | if (nla_put_u64_64bit(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL, |
388 | value: clockval, padattr: BRIDGE_VLANDB_GOPTS_PAD)) |
389 | goto out_err; |
390 | clockval = jiffies_to_clock_t(x: v_opts->br_mcast_ctx.multicast_querier_interval); |
391 | if (nla_put_u64_64bit(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL, |
392 | value: clockval, padattr: BRIDGE_VLANDB_GOPTS_PAD)) |
393 | goto out_err; |
394 | clockval = jiffies_to_clock_t(x: v_opts->br_mcast_ctx.multicast_query_interval); |
395 | if (nla_put_u64_64bit(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL, |
396 | value: clockval, padattr: BRIDGE_VLANDB_GOPTS_PAD)) |
397 | goto out_err; |
398 | clockval = jiffies_to_clock_t(x: v_opts->br_mcast_ctx.multicast_query_response_interval); |
399 | if (nla_put_u64_64bit(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL, |
400 | value: clockval, padattr: BRIDGE_VLANDB_GOPTS_PAD)) |
401 | goto out_err; |
402 | clockval = jiffies_to_clock_t(x: v_opts->br_mcast_ctx.multicast_startup_query_interval); |
403 | if (nla_put_u64_64bit(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL, |
404 | value: clockval, padattr: BRIDGE_VLANDB_GOPTS_PAD)) |
405 | goto out_err; |
406 | |
407 | if (br_rports_have_mc_router(brmctx: &v_opts->br_mcast_ctx)) { |
408 | nest2 = nla_nest_start(skb, |
409 | attrtype: BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS); |
410 | if (!nest2) |
411 | goto out_err; |
412 | |
413 | rcu_read_lock(); |
414 | if (br_rports_fill_info(skb, brmctx: &v_opts->br_mcast_ctx)) { |
415 | rcu_read_unlock(); |
416 | nla_nest_cancel(skb, start: nest2); |
417 | goto out_err; |
418 | } |
419 | rcu_read_unlock(); |
420 | |
421 | nla_nest_end(skb, start: nest2); |
422 | } |
423 | |
424 | #if IS_ENABLED(CONFIG_IPV6) |
425 | if (nla_put_u8(skb, attrtype: BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION, |
426 | value: v_opts->br_mcast_ctx.multicast_mld_version)) |
427 | goto out_err; |
428 | #endif |
429 | #endif |
430 | |
431 | if (nla_put_u16(skb, attrtype: BRIDGE_VLANDB_GOPTS_MSTI, value: v_opts->msti)) |
432 | goto out_err; |
433 | |
434 | nla_nest_end(skb, start: nest); |
435 | |
436 | return true; |
437 | |
438 | out_err: |
439 | nla_nest_cancel(skb, start: nest); |
440 | return false; |
441 | } |
442 | |
443 | static size_t rtnl_vlan_global_opts_nlmsg_size(const struct net_bridge_vlan *v) |
444 | { |
445 | return NLMSG_ALIGN(sizeof(struct br_vlan_msg)) |
446 | + nla_total_size(payload: 0) /* BRIDGE_VLANDB_GLOBAL_OPTIONS */ |
447 | + nla_total_size(payload: sizeof(u16)) /* BRIDGE_VLANDB_GOPTS_ID */ |
448 | #ifdef CONFIG_BRIDGE_IGMP_SNOOPING |
449 | + nla_total_size(payload: sizeof(u8)) /* BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING */ |
450 | + nla_total_size(payload: sizeof(u8)) /* BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION */ |
451 | + nla_total_size(payload: sizeof(u8)) /* BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION */ |
452 | + nla_total_size(payload: sizeof(u32)) /* BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT */ |
453 | + nla_total_size(payload: sizeof(u32)) /* BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT */ |
454 | + nla_total_size(payload: sizeof(u64)) /* BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL */ |
455 | + nla_total_size(payload: sizeof(u64)) /* BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL */ |
456 | + nla_total_size(payload: sizeof(u64)) /* BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL */ |
457 | + nla_total_size(payload: sizeof(u64)) /* BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL */ |
458 | + nla_total_size(payload: sizeof(u64)) /* BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL */ |
459 | + nla_total_size(payload: sizeof(u64)) /* BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL */ |
460 | + nla_total_size(payload: sizeof(u8)) /* BRIDGE_VLANDB_GOPTS_MCAST_QUERIER */ |
461 | + br_multicast_querier_state_size() /* BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_STATE */ |
462 | + nla_total_size(payload: 0) /* BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS */ |
463 | + br_rports_size(brmctx: &v->br_mcast_ctx) /* BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS */ |
464 | #endif |
465 | + nla_total_size(payload: sizeof(u16)) /* BRIDGE_VLANDB_GOPTS_MSTI */ |
466 | + nla_total_size(payload: sizeof(u16)); /* BRIDGE_VLANDB_GOPTS_RANGE */ |
467 | } |
468 | |
469 | static void br_vlan_global_opts_notify(const struct net_bridge *br, |
470 | u16 vid, u16 vid_range) |
471 | { |
472 | struct net_bridge_vlan *v; |
473 | struct br_vlan_msg *bvm; |
474 | struct nlmsghdr *nlh; |
475 | struct sk_buff *skb; |
476 | int err = -ENOBUFS; |
477 | |
478 | /* right now notifications are done only with rtnl held */ |
479 | ASSERT_RTNL(); |
480 | |
481 | /* need to find the vlan due to flags/options */ |
482 | v = br_vlan_find(vg: br_vlan_group(br), vid); |
483 | if (!v) |
484 | return; |
485 | |
486 | skb = nlmsg_new(payload: rtnl_vlan_global_opts_nlmsg_size(v), GFP_KERNEL); |
487 | if (!skb) |
488 | goto out_err; |
489 | |
490 | err = -EMSGSIZE; |
491 | nlh = nlmsg_put(skb, portid: 0, seq: 0, type: RTM_NEWVLAN, payload: sizeof(*bvm), flags: 0); |
492 | if (!nlh) |
493 | goto out_err; |
494 | bvm = nlmsg_data(nlh); |
495 | memset(bvm, 0, sizeof(*bvm)); |
496 | bvm->family = AF_BRIDGE; |
497 | bvm->ifindex = br->dev->ifindex; |
498 | |
499 | if (!br_vlan_global_opts_fill(skb, vid, vid_range, v_opts: v)) |
500 | goto out_err; |
501 | |
502 | nlmsg_end(skb, nlh); |
503 | rtnl_notify(skb, net: dev_net(dev: br->dev), pid: 0, RTNLGRP_BRVLAN, NULL, GFP_KERNEL); |
504 | return; |
505 | |
506 | out_err: |
507 | rtnl_set_sk_err(net: dev_net(dev: br->dev), RTNLGRP_BRVLAN, error: err); |
508 | kfree_skb(skb); |
509 | } |
510 | |
511 | static int br_vlan_process_global_one_opts(const struct net_bridge *br, |
512 | struct net_bridge_vlan_group *vg, |
513 | struct net_bridge_vlan *v, |
514 | struct nlattr **tb, |
515 | bool *changed, |
516 | struct netlink_ext_ack *extack) |
517 | { |
518 | int err __maybe_unused; |
519 | |
520 | *changed = false; |
521 | #ifdef CONFIG_BRIDGE_IGMP_SNOOPING |
522 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING]) { |
523 | u8 mc_snooping; |
524 | |
525 | mc_snooping = nla_get_u8(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING]); |
526 | if (br_multicast_toggle_global_vlan(vlan: v, on: !!mc_snooping)) |
527 | *changed = true; |
528 | } |
529 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION]) { |
530 | u8 ver; |
531 | |
532 | ver = nla_get_u8(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION]); |
533 | err = br_multicast_set_igmp_version(brmctx: &v->br_mcast_ctx, val: ver); |
534 | if (err) |
535 | return err; |
536 | *changed = true; |
537 | } |
538 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT]) { |
539 | u32 cnt; |
540 | |
541 | cnt = nla_get_u32(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT]); |
542 | v->br_mcast_ctx.multicast_last_member_count = cnt; |
543 | *changed = true; |
544 | } |
545 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT]) { |
546 | u32 cnt; |
547 | |
548 | cnt = nla_get_u32(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT]); |
549 | v->br_mcast_ctx.multicast_startup_query_count = cnt; |
550 | *changed = true; |
551 | } |
552 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL]) { |
553 | u64 val; |
554 | |
555 | val = nla_get_u64(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL]); |
556 | v->br_mcast_ctx.multicast_last_member_interval = clock_t_to_jiffies(x: val); |
557 | *changed = true; |
558 | } |
559 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL]) { |
560 | u64 val; |
561 | |
562 | val = nla_get_u64(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL]); |
563 | v->br_mcast_ctx.multicast_membership_interval = clock_t_to_jiffies(x: val); |
564 | *changed = true; |
565 | } |
566 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL]) { |
567 | u64 val; |
568 | |
569 | val = nla_get_u64(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL]); |
570 | v->br_mcast_ctx.multicast_querier_interval = clock_t_to_jiffies(x: val); |
571 | *changed = true; |
572 | } |
573 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL]) { |
574 | u64 val; |
575 | |
576 | val = nla_get_u64(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL]); |
577 | br_multicast_set_query_intvl(brmctx: &v->br_mcast_ctx, val); |
578 | *changed = true; |
579 | } |
580 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL]) { |
581 | u64 val; |
582 | |
583 | val = nla_get_u64(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL]); |
584 | v->br_mcast_ctx.multicast_query_response_interval = clock_t_to_jiffies(x: val); |
585 | *changed = true; |
586 | } |
587 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL]) { |
588 | u64 val; |
589 | |
590 | val = nla_get_u64(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL]); |
591 | br_multicast_set_startup_query_intvl(brmctx: &v->br_mcast_ctx, val); |
592 | *changed = true; |
593 | } |
594 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER]) { |
595 | u8 val; |
596 | |
597 | val = nla_get_u8(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER]); |
598 | err = br_multicast_set_querier(brmctx: &v->br_mcast_ctx, val); |
599 | if (err) |
600 | return err; |
601 | *changed = true; |
602 | } |
603 | #if IS_ENABLED(CONFIG_IPV6) |
604 | if (tb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]) { |
605 | u8 ver; |
606 | |
607 | ver = nla_get_u8(nla: tb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]); |
608 | err = br_multicast_set_mld_version(brmctx: &v->br_mcast_ctx, val: ver); |
609 | if (err) |
610 | return err; |
611 | *changed = true; |
612 | } |
613 | #endif |
614 | #endif |
615 | if (tb[BRIDGE_VLANDB_GOPTS_MSTI]) { |
616 | u16 msti; |
617 | |
618 | msti = nla_get_u16(nla: tb[BRIDGE_VLANDB_GOPTS_MSTI]); |
619 | err = br_mst_vlan_set_msti(v, msti); |
620 | if (err) |
621 | return err; |
622 | *changed = true; |
623 | } |
624 | |
625 | return 0; |
626 | } |
627 | |
628 | static const struct nla_policy br_vlan_db_gpol[BRIDGE_VLANDB_GOPTS_MAX + 1] = { |
629 | [BRIDGE_VLANDB_GOPTS_ID] = { .type = NLA_U16 }, |
630 | [BRIDGE_VLANDB_GOPTS_RANGE] = { .type = NLA_U16 }, |
631 | [BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING] = { .type = NLA_U8 }, |
632 | [BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION] = { .type = NLA_U8 }, |
633 | [BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL] = { .type = NLA_U64 }, |
634 | [BRIDGE_VLANDB_GOPTS_MCAST_QUERIER] = { .type = NLA_U8 }, |
635 | [BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION] = { .type = NLA_U8 }, |
636 | [BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT] = { .type = NLA_U32 }, |
637 | [BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT] = { .type = NLA_U32 }, |
638 | [BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL] = { .type = NLA_U64 }, |
639 | [BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL] = { .type = NLA_U64 }, |
640 | [BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL] = { .type = NLA_U64 }, |
641 | [BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL] = { .type = NLA_U64 }, |
642 | [BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL] = { .type = NLA_U64 }, |
643 | [BRIDGE_VLANDB_GOPTS_MSTI] = NLA_POLICY_MAX(NLA_U16, VLAN_N_VID - 1), |
644 | }; |
645 | |
646 | int br_vlan_rtm_process_global_options(struct net_device *dev, |
647 | const struct nlattr *attr, |
648 | int cmd, |
649 | struct netlink_ext_ack *extack) |
650 | { |
651 | struct net_bridge_vlan *v, *curr_start = NULL, *curr_end = NULL; |
652 | struct nlattr *tb[BRIDGE_VLANDB_GOPTS_MAX + 1]; |
653 | struct net_bridge_vlan_group *vg; |
654 | u16 vid, vid_range = 0; |
655 | struct net_bridge *br; |
656 | int err = 0; |
657 | |
658 | if (cmd != RTM_NEWVLAN) { |
659 | NL_SET_ERR_MSG_MOD(extack, "Global vlan options support only set operation" ); |
660 | return -EINVAL; |
661 | } |
662 | if (!netif_is_bridge_master(dev)) { |
663 | NL_SET_ERR_MSG_MOD(extack, "Global vlan options can only be set on bridge device" ); |
664 | return -EINVAL; |
665 | } |
666 | br = netdev_priv(dev); |
667 | vg = br_vlan_group(br); |
668 | if (WARN_ON(!vg)) |
669 | return -ENODEV; |
670 | |
671 | err = nla_parse_nested(tb, BRIDGE_VLANDB_GOPTS_MAX, nla: attr, |
672 | policy: br_vlan_db_gpol, extack); |
673 | if (err) |
674 | return err; |
675 | |
676 | if (!tb[BRIDGE_VLANDB_GOPTS_ID]) { |
677 | NL_SET_ERR_MSG_MOD(extack, "Missing vlan entry id" ); |
678 | return -EINVAL; |
679 | } |
680 | vid = nla_get_u16(nla: tb[BRIDGE_VLANDB_GOPTS_ID]); |
681 | if (!br_vlan_valid_id(vid, extack)) |
682 | return -EINVAL; |
683 | |
684 | if (tb[BRIDGE_VLANDB_GOPTS_RANGE]) { |
685 | vid_range = nla_get_u16(nla: tb[BRIDGE_VLANDB_GOPTS_RANGE]); |
686 | if (!br_vlan_valid_id(vid: vid_range, extack)) |
687 | return -EINVAL; |
688 | if (vid >= vid_range) { |
689 | NL_SET_ERR_MSG_MOD(extack, "End vlan id is less than or equal to start vlan id" ); |
690 | return -EINVAL; |
691 | } |
692 | } else { |
693 | vid_range = vid; |
694 | } |
695 | |
696 | for (; vid <= vid_range; vid++) { |
697 | bool changed = false; |
698 | |
699 | v = br_vlan_find(vg, vid); |
700 | if (!v) { |
701 | NL_SET_ERR_MSG_MOD(extack, "Vlan in range doesn't exist, can't process global options" ); |
702 | err = -ENOENT; |
703 | break; |
704 | } |
705 | |
706 | err = br_vlan_process_global_one_opts(br, vg, v, tb, changed: &changed, |
707 | extack); |
708 | if (err) |
709 | break; |
710 | |
711 | if (changed) { |
712 | /* vlan options changed, check for range */ |
713 | if (!curr_start) { |
714 | curr_start = v; |
715 | curr_end = v; |
716 | continue; |
717 | } |
718 | |
719 | if (!br_vlan_global_opts_can_enter_range(v_curr: v, r_end: curr_end)) { |
720 | br_vlan_global_opts_notify(br, vid: curr_start->vid, |
721 | vid_range: curr_end->vid); |
722 | curr_start = v; |
723 | } |
724 | curr_end = v; |
725 | } else { |
726 | /* nothing changed and nothing to notify yet */ |
727 | if (!curr_start) |
728 | continue; |
729 | |
730 | br_vlan_global_opts_notify(br, vid: curr_start->vid, |
731 | vid_range: curr_end->vid); |
732 | curr_start = NULL; |
733 | curr_end = NULL; |
734 | } |
735 | } |
736 | if (curr_start) |
737 | br_vlan_global_opts_notify(br, vid: curr_start->vid, vid_range: curr_end->vid); |
738 | |
739 | return err; |
740 | } |
741 | |