]>
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] = { | |
191 | [IFLA_BRIDGE_VLAN_TUNNEL_ID] = { .type = NLA_U32 }, | |
192 | [IFLA_BRIDGE_VLAN_TUNNEL_VID] = { .type = NLA_U16 }, | |
193 | [IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] = { .type = NLA_U16 }, | |
194 | }; | |
195 | ||
569da082 NA |
196 | int br_vlan_tunnel_info(const struct net_bridge_port *p, int cmd, |
197 | u16 vid, u32 tun_id, bool *changed) | |
efa5356b RP |
198 | { |
199 | int err = 0; | |
200 | ||
201 | if (!p) | |
202 | return -EINVAL; | |
203 | ||
204 | switch (cmd) { | |
205 | case RTM_SETLINK: | |
206 | err = nbp_vlan_tunnel_info_add(p, vid, tun_id); | |
e19b42a1 NA |
207 | if (!err) |
208 | *changed = true; | |
efa5356b RP |
209 | break; |
210 | case RTM_DELLINK: | |
e19b42a1 NA |
211 | if (!nbp_vlan_tunnel_info_delete(p, vid)) |
212 | *changed = true; | |
efa5356b RP |
213 | break; |
214 | } | |
215 | ||
216 | return err; | |
217 | } | |
218 | ||
219 | int br_parse_vlan_tunnel_info(struct nlattr *attr, | |
220 | struct vtunnel_info *tinfo) | |
221 | { | |
222 | struct nlattr *tb[IFLA_BRIDGE_VLAN_TUNNEL_MAX + 1]; | |
223 | u32 tun_id; | |
224 | u16 vid, flags = 0; | |
225 | int err; | |
226 | ||
227 | memset(tinfo, 0, sizeof(*tinfo)); | |
228 | ||
8cb08174 JB |
229 | err = nla_parse_nested_deprecated(tb, IFLA_BRIDGE_VLAN_TUNNEL_MAX, |
230 | attr, vlan_tunnel_policy, NULL); | |
efa5356b RP |
231 | if (err < 0) |
232 | return err; | |
233 | ||
bb580ad6 NA |
234 | if (!tb[IFLA_BRIDGE_VLAN_TUNNEL_ID] || |
235 | !tb[IFLA_BRIDGE_VLAN_TUNNEL_VID]) | |
236 | return -EINVAL; | |
237 | ||
efa5356b RP |
238 | tun_id = nla_get_u32(tb[IFLA_BRIDGE_VLAN_TUNNEL_ID]); |
239 | vid = nla_get_u16(tb[IFLA_BRIDGE_VLAN_TUNNEL_VID]); | |
240 | if (vid >= VLAN_VID_MASK) | |
241 | return -ERANGE; | |
242 | ||
243 | if (tb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]) | |
244 | flags = nla_get_u16(tb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]); | |
245 | ||
246 | tinfo->tunid = tun_id; | |
247 | tinfo->vid = vid; | |
248 | tinfo->flags = flags; | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
53e96632 NA |
253 | int br_process_vlan_tunnel_info(const struct net_bridge *br, |
254 | const struct net_bridge_port *p, int cmd, | |
efa5356b | 255 | struct vtunnel_info *tinfo_curr, |
e19b42a1 NA |
256 | struct vtunnel_info *tinfo_last, |
257 | bool *changed) | |
efa5356b RP |
258 | { |
259 | int err; | |
260 | ||
261 | if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) { | |
262 | if (tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) | |
263 | return -EINVAL; | |
264 | memcpy(tinfo_last, tinfo_curr, sizeof(struct vtunnel_info)); | |
265 | } else if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_END) { | |
266 | int t, v; | |
267 | ||
268 | if (!(tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN)) | |
269 | return -EINVAL; | |
270 | if ((tinfo_curr->vid - tinfo_last->vid) != | |
271 | (tinfo_curr->tunid - tinfo_last->tunid)) | |
272 | return -EINVAL; | |
273 | t = tinfo_last->tunid; | |
274 | for (v = tinfo_last->vid; v <= tinfo_curr->vid; v++) { | |
e19b42a1 | 275 | err = br_vlan_tunnel_info(p, cmd, v, t, changed); |
efa5356b RP |
276 | if (err) |
277 | return err; | |
278 | t++; | |
279 | } | |
280 | memset(tinfo_last, 0, sizeof(struct vtunnel_info)); | |
281 | memset(tinfo_curr, 0, sizeof(struct vtunnel_info)); | |
282 | } else { | |
283 | if (tinfo_last->flags) | |
284 | return -EINVAL; | |
285 | err = br_vlan_tunnel_info(p, cmd, tinfo_curr->vid, | |
e19b42a1 | 286 | tinfo_curr->tunid, changed); |
efa5356b RP |
287 | if (err) |
288 | return err; | |
289 | memset(tinfo_last, 0, sizeof(struct vtunnel_info)); | |
290 | memset(tinfo_curr, 0, sizeof(struct vtunnel_info)); | |
291 | } | |
292 | ||
293 | return 0; | |
294 | } |