1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Bridge per vlan tunnel port dst_metadata netlink control interface |
4 | * |
5 | * Authors: |
6 | * Roopa Prabhu <roopa@cumulusnetworks.com> |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/etherdevice.h> |
12 | #include <net/rtnetlink.h> |
13 | #include <net/net_namespace.h> |
14 | #include <net/sock.h> |
15 | #include <uapi/linux/if_bridge.h> |
16 | #include <net/dst_metadata.h> |
17 | |
18 | #include "br_private.h" |
19 | #include "br_private_tunnel.h" |
20 | |
21 | static size_t __get_vlan_tinfo_size(void) |
22 | { |
23 | return nla_total_size(payload: 0) + /* nest IFLA_BRIDGE_VLAN_TUNNEL_INFO */ |
24 | nla_total_size(payload: sizeof(u32)) + /* IFLA_BRIDGE_VLAN_TUNNEL_ID */ |
25 | nla_total_size(payload: sizeof(u16)) + /* IFLA_BRIDGE_VLAN_TUNNEL_VID */ |
26 | nla_total_size(payload: sizeof(u16)); /* IFLA_BRIDGE_VLAN_TUNNEL_FLAGS */ |
27 | } |
28 | |
29 | bool vlan_tunid_inrange(const struct net_bridge_vlan *v_curr, |
30 | const struct net_bridge_vlan *v_last) |
31 | { |
32 | __be32 tunid_curr = tunnel_id_to_key32(tun_id: v_curr->tinfo.tunnel_id); |
33 | __be32 tunid_last = tunnel_id_to_key32(tun_id: v_last->tinfo.tunnel_id); |
34 | |
35 | return (be32_to_cpu(tunid_curr) - be32_to_cpu(tunid_last)) == 1; |
36 | } |
37 | |
38 | static int __get_num_vlan_tunnel_infos(struct net_bridge_vlan_group *vg) |
39 | { |
40 | struct net_bridge_vlan *v, *vtbegin = NULL, *vtend = NULL; |
41 | int num_tinfos = 0; |
42 | |
43 | /* Count number of vlan infos */ |
44 | list_for_each_entry_rcu(v, &vg->vlan_list, vlist) { |
45 | /* only a context, bridge vlan not activated */ |
46 | if (!br_vlan_should_use(v) || !v->tinfo.tunnel_id) |
47 | continue; |
48 | |
49 | if (!vtbegin) { |
50 | goto initvars; |
51 | } else if ((v->vid - vtend->vid) == 1 && |
52 | vlan_tunid_inrange(v_curr: v, v_last: vtend)) { |
53 | vtend = v; |
54 | continue; |
55 | } else { |
56 | if ((vtend->vid - vtbegin->vid) > 0) |
57 | num_tinfos += 2; |
58 | else |
59 | num_tinfos += 1; |
60 | } |
61 | initvars: |
62 | vtbegin = v; |
63 | vtend = v; |
64 | } |
65 | |
66 | if (vtbegin && vtend) { |
67 | if ((vtend->vid - vtbegin->vid) > 0) |
68 | num_tinfos += 2; |
69 | else |
70 | num_tinfos += 1; |
71 | } |
72 | |
73 | return num_tinfos; |
74 | } |
75 | |
76 | int br_get_vlan_tunnel_info_size(struct net_bridge_vlan_group *vg) |
77 | { |
78 | int num_tinfos; |
79 | |
80 | if (!vg) |
81 | return 0; |
82 | |
83 | rcu_read_lock(); |
84 | num_tinfos = __get_num_vlan_tunnel_infos(vg); |
85 | rcu_read_unlock(); |
86 | |
87 | return num_tinfos * __get_vlan_tinfo_size(); |
88 | } |
89 | |
90 | static int br_fill_vlan_tinfo(struct sk_buff *skb, u16 vid, |
91 | __be64 tunnel_id, u16 flags) |
92 | { |
93 | __be32 tid = tunnel_id_to_key32(tun_id: tunnel_id); |
94 | struct nlattr *tmap; |
95 | |
96 | tmap = nla_nest_start_noflag(skb, attrtype: IFLA_BRIDGE_VLAN_TUNNEL_INFO); |
97 | if (!tmap) |
98 | return -EMSGSIZE; |
99 | if (nla_put_u32(skb, attrtype: IFLA_BRIDGE_VLAN_TUNNEL_ID, |
100 | be32_to_cpu(tid))) |
101 | goto nla_put_failure; |
102 | if (nla_put_u16(skb, attrtype: IFLA_BRIDGE_VLAN_TUNNEL_VID, |
103 | value: vid)) |
104 | goto nla_put_failure; |
105 | if (nla_put_u16(skb, attrtype: IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, |
106 | value: flags)) |
107 | goto nla_put_failure; |
108 | nla_nest_end(skb, start: tmap); |
109 | |
110 | return 0; |
111 | |
112 | nla_put_failure: |
113 | nla_nest_cancel(skb, start: tmap); |
114 | |
115 | return -EMSGSIZE; |
116 | } |
117 | |
118 | static int br_fill_vlan_tinfo_range(struct sk_buff *skb, |
119 | struct net_bridge_vlan *vtbegin, |
120 | struct net_bridge_vlan *vtend) |
121 | { |
122 | int err; |
123 | |
124 | if (vtend && (vtend->vid - vtbegin->vid) > 0) { |
125 | /* add range to skb */ |
126 | err = br_fill_vlan_tinfo(skb, vid: vtbegin->vid, |
127 | tunnel_id: vtbegin->tinfo.tunnel_id, |
128 | BRIDGE_VLAN_INFO_RANGE_BEGIN); |
129 | if (err) |
130 | return err; |
131 | |
132 | err = br_fill_vlan_tinfo(skb, vid: vtend->vid, |
133 | tunnel_id: vtend->tinfo.tunnel_id, |
134 | BRIDGE_VLAN_INFO_RANGE_END); |
135 | if (err) |
136 | return err; |
137 | } else { |
138 | err = br_fill_vlan_tinfo(skb, vid: vtbegin->vid, |
139 | tunnel_id: vtbegin->tinfo.tunnel_id, |
140 | flags: 0); |
141 | if (err) |
142 | return err; |
143 | } |
144 | |
145 | return 0; |
146 | } |
147 | |
148 | int br_fill_vlan_tunnel_info(struct sk_buff *skb, |
149 | struct net_bridge_vlan_group *vg) |
150 | { |
151 | struct net_bridge_vlan *vtbegin = NULL; |
152 | struct net_bridge_vlan *vtend = NULL; |
153 | struct net_bridge_vlan *v; |
154 | int err; |
155 | |
156 | /* Count number of vlan infos */ |
157 | list_for_each_entry_rcu(v, &vg->vlan_list, vlist) { |
158 | /* only a context, bridge vlan not activated */ |
159 | if (!br_vlan_should_use(v)) |
160 | continue; |
161 | |
162 | if (!v->tinfo.tunnel_dst) |
163 | continue; |
164 | |
165 | if (!vtbegin) { |
166 | goto initvars; |
167 | } else if ((v->vid - vtend->vid) == 1 && |
168 | vlan_tunid_inrange(v_curr: v, v_last: vtend)) { |
169 | vtend = v; |
170 | continue; |
171 | } else { |
172 | err = br_fill_vlan_tinfo_range(skb, vtbegin, vtend); |
173 | if (err) |
174 | return err; |
175 | } |
176 | initvars: |
177 | vtbegin = v; |
178 | vtend = v; |
179 | } |
180 | |
181 | if (vtbegin) { |
182 | err = br_fill_vlan_tinfo_range(skb, vtbegin, vtend); |
183 | if (err) |
184 | return err; |
185 | } |
186 | |
187 | return 0; |
188 | } |
189 | |
190 | static const struct nla_policy vlan_tunnel_policy[IFLA_BRIDGE_VLAN_TUNNEL_MAX + 1] = { |
191 | [IFLA_BRIDGE_VLAN_TUNNEL_UNSPEC] = { |
192 | .strict_start_type = IFLA_BRIDGE_VLAN_TUNNEL_FLAGS + 1 |
193 | }, |
194 | [IFLA_BRIDGE_VLAN_TUNNEL_ID] = { .type = NLA_U32 }, |
195 | [IFLA_BRIDGE_VLAN_TUNNEL_VID] = { .type = NLA_U16 }, |
196 | [IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] = { .type = NLA_U16 }, |
197 | }; |
198 | |
199 | int br_vlan_tunnel_info(const struct net_bridge_port *p, int cmd, |
200 | u16 vid, u32 tun_id, bool *changed) |
201 | { |
202 | int err = 0; |
203 | |
204 | if (!p) |
205 | return -EINVAL; |
206 | |
207 | switch (cmd) { |
208 | case RTM_SETLINK: |
209 | err = nbp_vlan_tunnel_info_add(port: p, vid, tun_id); |
210 | if (!err) |
211 | *changed = true; |
212 | break; |
213 | case RTM_DELLINK: |
214 | if (!nbp_vlan_tunnel_info_delete(port: p, vid)) |
215 | *changed = true; |
216 | break; |
217 | } |
218 | |
219 | return err; |
220 | } |
221 | |
222 | int br_parse_vlan_tunnel_info(struct nlattr *attr, |
223 | struct vtunnel_info *tinfo) |
224 | { |
225 | struct nlattr *tb[IFLA_BRIDGE_VLAN_TUNNEL_MAX + 1]; |
226 | u32 tun_id; |
227 | u16 vid, flags = 0; |
228 | int err; |
229 | |
230 | memset(tinfo, 0, sizeof(*tinfo)); |
231 | |
232 | err = nla_parse_nested_deprecated(tb, IFLA_BRIDGE_VLAN_TUNNEL_MAX, |
233 | nla: attr, policy: vlan_tunnel_policy, NULL); |
234 | if (err < 0) |
235 | return err; |
236 | |
237 | if (!tb[IFLA_BRIDGE_VLAN_TUNNEL_ID] || |
238 | !tb[IFLA_BRIDGE_VLAN_TUNNEL_VID]) |
239 | return -EINVAL; |
240 | |
241 | tun_id = nla_get_u32(nla: tb[IFLA_BRIDGE_VLAN_TUNNEL_ID]); |
242 | vid = nla_get_u16(nla: tb[IFLA_BRIDGE_VLAN_TUNNEL_VID]); |
243 | if (vid >= VLAN_VID_MASK) |
244 | return -ERANGE; |
245 | |
246 | if (tb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]) |
247 | flags = nla_get_u16(nla: tb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]); |
248 | |
249 | tinfo->tunid = tun_id; |
250 | tinfo->vid = vid; |
251 | tinfo->flags = flags; |
252 | |
253 | return 0; |
254 | } |
255 | |
256 | /* send a notification if v_curr can't enter the range and start a new one */ |
257 | static void __vlan_tunnel_handle_range(const struct net_bridge_port *p, |
258 | struct net_bridge_vlan **v_start, |
259 | struct net_bridge_vlan **v_end, |
260 | int v_curr, bool curr_change) |
261 | { |
262 | struct net_bridge_vlan_group *vg; |
263 | struct net_bridge_vlan *v; |
264 | |
265 | vg = nbp_vlan_group(p); |
266 | if (!vg) |
267 | return; |
268 | |
269 | v = br_vlan_find(vg, vid: v_curr); |
270 | |
271 | if (!*v_start) |
272 | goto out_init; |
273 | |
274 | if (v && curr_change && br_vlan_can_enter_range(v_curr: v, range_end: *v_end)) { |
275 | *v_end = v; |
276 | return; |
277 | } |
278 | |
279 | br_vlan_notify(br: p->br, p, vid: (*v_start)->vid, vid_range: (*v_end)->vid, cmd: RTM_NEWVLAN); |
280 | out_init: |
281 | /* we start a range only if there are any changes to notify about */ |
282 | *v_start = curr_change ? v : NULL; |
283 | *v_end = *v_start; |
284 | } |
285 | |
286 | int br_process_vlan_tunnel_info(const struct net_bridge *br, |
287 | const struct net_bridge_port *p, int cmd, |
288 | struct vtunnel_info *tinfo_curr, |
289 | struct vtunnel_info *tinfo_last, |
290 | bool *changed) |
291 | { |
292 | int err; |
293 | |
294 | if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) { |
295 | if (tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) |
296 | return -EINVAL; |
297 | memcpy(tinfo_last, tinfo_curr, sizeof(struct vtunnel_info)); |
298 | } else if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_END) { |
299 | struct net_bridge_vlan *v_start = NULL, *v_end = NULL; |
300 | int t, v; |
301 | |
302 | if (!(tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN)) |
303 | return -EINVAL; |
304 | if ((tinfo_curr->vid - tinfo_last->vid) != |
305 | (tinfo_curr->tunid - tinfo_last->tunid)) |
306 | return -EINVAL; |
307 | t = tinfo_last->tunid; |
308 | for (v = tinfo_last->vid; v <= tinfo_curr->vid; v++) { |
309 | bool curr_change = false; |
310 | |
311 | err = br_vlan_tunnel_info(p, cmd, vid: v, tun_id: t, changed: &curr_change); |
312 | if (err) |
313 | break; |
314 | t++; |
315 | |
316 | if (curr_change) |
317 | *changed = curr_change; |
318 | __vlan_tunnel_handle_range(p, v_start: &v_start, v_end: &v_end, v_curr: v, |
319 | curr_change); |
320 | } |
321 | if (v_start && v_end) |
322 | br_vlan_notify(br, p, vid: v_start->vid, vid_range: v_end->vid, |
323 | cmd: RTM_NEWVLAN); |
324 | if (err) |
325 | return err; |
326 | |
327 | memset(tinfo_last, 0, sizeof(struct vtunnel_info)); |
328 | memset(tinfo_curr, 0, sizeof(struct vtunnel_info)); |
329 | } else { |
330 | if (tinfo_last->flags) |
331 | return -EINVAL; |
332 | err = br_vlan_tunnel_info(p, cmd, vid: tinfo_curr->vid, |
333 | tun_id: tinfo_curr->tunid, changed); |
334 | if (err) |
335 | return err; |
336 | br_vlan_notify(br, p, vid: tinfo_curr->vid, vid_range: 0, cmd: RTM_NEWVLAN); |
337 | memset(tinfo_last, 0, sizeof(struct vtunnel_info)); |
338 | memset(tinfo_curr, 0, sizeof(struct vtunnel_info)); |
339 | } |
340 | |
341 | return 0; |
342 | } |
343 | |