Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
efa5356b RP |
2 | /* |
3 | * Bridge per vlan tunnel port dst_metadata netlink control interface | |
4 | * | |
5 | * Authors: | |
6 | * Roopa Prabhu <roopa@cumulusnetworks.com> | |
efa5356b RP |
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(0) + /* nest IFLA_BRIDGE_VLAN_TUNNEL_INFO */ | |
24 | nla_total_size(sizeof(u32)) + /* IFLA_BRIDGE_VLAN_TUNNEL_ID */ | |
25 | nla_total_size(sizeof(u16)) + /* IFLA_BRIDGE_VLAN_TUNNEL_VID */ | |
26 | nla_total_size(sizeof(u16)); /* IFLA_BRIDGE_VLAN_TUNNEL_FLAGS */ | |
27 | } | |
28 | ||
188c67dd NA |
29 | bool vlan_tunid_inrange(const struct net_bridge_vlan *v_curr, |
30 | const struct net_bridge_vlan *v_last) | |
efa5356b | 31 | { |
8ef95947 RP |
32 | __be32 tunid_curr = tunnel_id_to_key32(v_curr->tinfo.tunnel_id); |
33 | __be32 tunid_last = tunnel_id_to_key32(v_last->tinfo.tunnel_id); | |
efa5356b | 34 | |
8ef95947 | 35 | return (be32_to_cpu(tunid_curr) - be32_to_cpu(tunid_last)) == 1; |
efa5356b RP |
36 | } |
37 | ||
38 | static int __get_num_vlan_tunnel_infos(struct net_bridge_vlan_group *vg) | |
39 | { | |
8ef95947 | 40 | struct net_bridge_vlan *v, *vtbegin = NULL, *vtend = NULL; |
efa5356b RP |
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 | ||
8ef95947 | 49 | if (!vtbegin) { |
efa5356b | 50 | goto initvars; |
8ef95947 RP |
51 | } else if ((v->vid - vtend->vid) == 1 && |
52 | vlan_tunid_inrange(v, vtend)) { | |
53 | vtend = v; | |
efa5356b RP |
54 | continue; |
55 | } else { | |
8ef95947 | 56 | if ((vtend->vid - vtbegin->vid) > 0) |
efa5356b RP |
57 | num_tinfos += 2; |
58 | else | |
59 | num_tinfos += 1; | |
60 | } | |
61 | initvars: | |
8ef95947 RP |
62 | vtbegin = v; |
63 | vtend = v; | |
efa5356b RP |
64 | } |
65 | ||
8ef95947 RP |
66 | if (vtbegin && vtend) { |
67 | if ((vtend->vid - vtbegin->vid) > 0) | |
efa5356b RP |
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(tunnel_id); | |
94 | struct nlattr *tmap; | |
95 | ||
ae0be8de | 96 | tmap = nla_nest_start_noflag(skb, IFLA_BRIDGE_VLAN_TUNNEL_INFO); |
efa5356b RP |
97 | if (!tmap) |
98 | return -EMSGSIZE; | |
99 | if (nla_put_u32(skb, IFLA_BRIDGE_VLAN_TUNNEL_ID, | |
100 | be32_to_cpu(tid))) | |
101 | goto nla_put_failure; | |
102 | if (nla_put_u16(skb, IFLA_BRIDGE_VLAN_TUNNEL_VID, | |
103 | vid)) | |
104 | goto nla_put_failure; | |
105 | if (nla_put_u16(skb, IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, | |
106 | flags)) | |
107 | goto nla_put_failure; | |
108 | nla_nest_end(skb, tmap); | |
109 | ||
110 | return 0; | |
111 | ||
112 | nla_put_failure: | |
113 | nla_nest_cancel(skb, 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 | ||
a8cab863 | 124 | if (vtend && (vtend->vid - vtbegin->vid) > 0) { |
efa5356b RP |
125 | /* add range to skb */ |
126 | err = br_fill_vlan_tinfo(skb, vtbegin->vid, | |
127 | vtbegin->tinfo.tunnel_id, | |
128 | BRIDGE_VLAN_INFO_RANGE_BEGIN); | |
129 | if (err) | |
130 | return err; | |
131 | ||
132 | err = br_fill_vlan_tinfo(skb, vtend->vid, | |
133 | 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, vtbegin->vid, | |
139 | vtbegin->tinfo.tunnel_id, | |
140 | 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 && | |
8ef95947 | 168 | vlan_tunid_inrange(v, vtend)) { |
efa5356b RP |
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] = { | |
c00041cf PM |
191 | [IFLA_BRIDGE_VLAN_TUNNEL_UNSPEC] = { |
192 | .strict_start_type = IFLA_BRIDGE_VLAN_TUNNEL_FLAGS + 1 | |
193 | }, | |
efa5356b RP |
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 | ||
569da082 NA |
199 | int br_vlan_tunnel_info(const struct net_bridge_port *p, int cmd, |
200 | u16 vid, u32 tun_id, bool *changed) | |
efa5356b RP |
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(p, vid, tun_id); | |
e19b42a1 NA |
210 | if (!err) |
211 | *changed = true; | |
efa5356b RP |
212 | break; |
213 | case RTM_DELLINK: | |
e19b42a1 NA |
214 | if (!nbp_vlan_tunnel_info_delete(p, vid)) |
215 | *changed = true; | |
efa5356b RP |
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 | ||
8cb08174 JB |
232 | err = nla_parse_nested_deprecated(tb, IFLA_BRIDGE_VLAN_TUNNEL_MAX, |
233 | attr, vlan_tunnel_policy, NULL); | |
efa5356b RP |
234 | if (err < 0) |
235 | return err; | |
236 | ||
bb580ad6 NA |
237 | if (!tb[IFLA_BRIDGE_VLAN_TUNNEL_ID] || |
238 | !tb[IFLA_BRIDGE_VLAN_TUNNEL_VID]) | |
239 | return -EINVAL; | |
240 | ||
efa5356b RP |
241 | tun_id = nla_get_u32(tb[IFLA_BRIDGE_VLAN_TUNNEL_ID]); |
242 | vid = nla_get_u16(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(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 | ||
94339443 NA |
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, v_curr); | |
270 | ||
271 | if (!*v_start) | |
272 | goto out_init; | |
273 | ||
274 | if (v && curr_change && br_vlan_can_enter_range(v, *v_end)) { | |
275 | *v_end = v; | |
276 | return; | |
277 | } | |
278 | ||
279 | br_vlan_notify(p->br, p, (*v_start)->vid, (*v_end)->vid, 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 | ||
53e96632 NA |
286 | int br_process_vlan_tunnel_info(const struct net_bridge *br, |
287 | const struct net_bridge_port *p, int cmd, | |
efa5356b | 288 | struct vtunnel_info *tinfo_curr, |
e19b42a1 NA |
289 | struct vtunnel_info *tinfo_last, |
290 | bool *changed) | |
efa5356b RP |
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) { | |
94339443 | 299 | struct net_bridge_vlan *v_start = NULL, *v_end = NULL; |
efa5356b RP |
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++) { | |
94339443 NA |
309 | bool curr_change = false; |
310 | ||
311 | err = br_vlan_tunnel_info(p, cmd, v, t, &curr_change); | |
efa5356b | 312 | if (err) |
94339443 | 313 | break; |
efa5356b | 314 | t++; |
94339443 NA |
315 | |
316 | if (curr_change) | |
317 | *changed = curr_change; | |
cdbdb3c6 CH |
318 | __vlan_tunnel_handle_range(p, &v_start, &v_end, v, |
319 | curr_change); | |
efa5356b | 320 | } |
94339443 NA |
321 | if (v_start && v_end) |
322 | br_vlan_notify(br, p, v_start->vid, v_end->vid, | |
323 | RTM_NEWVLAN); | |
324 | if (err) | |
325 | return err; | |
326 | ||
efa5356b RP |
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, tinfo_curr->vid, | |
e19b42a1 | 333 | tinfo_curr->tunid, changed); |
efa5356b RP |
334 | if (err) |
335 | return err; | |
94339443 | 336 | br_vlan_notify(br, p, tinfo_curr->vid, 0, RTM_NEWVLAN); |
efa5356b RP |
337 | memset(tinfo_last, 0, sizeof(struct vtunnel_info)); |
338 | memset(tinfo_curr, 0, sizeof(struct vtunnel_info)); | |
339 | } | |
340 | ||
341 | return 0; | |
342 | } |