Commit | Line | Data |
---|---|---|
efa5356b RP |
1 | /* |
2 | * Bridge per vlan tunnel port dst_metadata netlink control interface | |
3 | * | |
4 | * Authors: | |
5 | * Roopa Prabhu <roopa@cumulusnetworks.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License | |
9 | * as published by the Free Software Foundation; either version | |
10 | * 2 of the License, or (at your option) any later version. | |
11 | */ | |
12 | ||
13 | #include <linux/kernel.h> | |
14 | #include <linux/slab.h> | |
15 | #include <linux/etherdevice.h> | |
16 | #include <net/rtnetlink.h> | |
17 | #include <net/net_namespace.h> | |
18 | #include <net/sock.h> | |
19 | #include <uapi/linux/if_bridge.h> | |
20 | #include <net/dst_metadata.h> | |
21 | ||
22 | #include "br_private.h" | |
23 | #include "br_private_tunnel.h" | |
24 | ||
25 | static size_t __get_vlan_tinfo_size(void) | |
26 | { | |
27 | return nla_total_size(0) + /* nest IFLA_BRIDGE_VLAN_TUNNEL_INFO */ | |
28 | nla_total_size(sizeof(u32)) + /* IFLA_BRIDGE_VLAN_TUNNEL_ID */ | |
29 | nla_total_size(sizeof(u16)) + /* IFLA_BRIDGE_VLAN_TUNNEL_VID */ | |
30 | nla_total_size(sizeof(u16)); /* IFLA_BRIDGE_VLAN_TUNNEL_FLAGS */ | |
31 | } | |
32 | ||
8ef95947 RP |
33 | static bool vlan_tunid_inrange(struct net_bridge_vlan *v_curr, |
34 | struct net_bridge_vlan *v_last) | |
efa5356b | 35 | { |
8ef95947 RP |
36 | __be32 tunid_curr = tunnel_id_to_key32(v_curr->tinfo.tunnel_id); |
37 | __be32 tunid_last = tunnel_id_to_key32(v_last->tinfo.tunnel_id); | |
efa5356b | 38 | |
8ef95947 | 39 | return (be32_to_cpu(tunid_curr) - be32_to_cpu(tunid_last)) == 1; |
efa5356b RP |
40 | } |
41 | ||
42 | static int __get_num_vlan_tunnel_infos(struct net_bridge_vlan_group *vg) | |
43 | { | |
8ef95947 | 44 | struct net_bridge_vlan *v, *vtbegin = NULL, *vtend = NULL; |
efa5356b RP |
45 | int num_tinfos = 0; |
46 | ||
47 | /* Count number of vlan infos */ | |
48 | list_for_each_entry_rcu(v, &vg->vlan_list, vlist) { | |
49 | /* only a context, bridge vlan not activated */ | |
50 | if (!br_vlan_should_use(v) || !v->tinfo.tunnel_id) | |
51 | continue; | |
52 | ||
8ef95947 | 53 | if (!vtbegin) { |
efa5356b | 54 | goto initvars; |
8ef95947 RP |
55 | } else if ((v->vid - vtend->vid) == 1 && |
56 | vlan_tunid_inrange(v, vtend)) { | |
57 | vtend = v; | |
efa5356b RP |
58 | continue; |
59 | } else { | |
8ef95947 | 60 | if ((vtend->vid - vtbegin->vid) > 0) |
efa5356b RP |
61 | num_tinfos += 2; |
62 | else | |
63 | num_tinfos += 1; | |
64 | } | |
65 | initvars: | |
8ef95947 RP |
66 | vtbegin = v; |
67 | vtend = v; | |
efa5356b RP |
68 | } |
69 | ||
8ef95947 RP |
70 | if (vtbegin && vtend) { |
71 | if ((vtend->vid - vtbegin->vid) > 0) | |
efa5356b RP |
72 | num_tinfos += 2; |
73 | else | |
74 | num_tinfos += 1; | |
75 | } | |
76 | ||
77 | return num_tinfos; | |
78 | } | |
79 | ||
80 | int br_get_vlan_tunnel_info_size(struct net_bridge_vlan_group *vg) | |
81 | { | |
82 | int num_tinfos; | |
83 | ||
84 | if (!vg) | |
85 | return 0; | |
86 | ||
87 | rcu_read_lock(); | |
88 | num_tinfos = __get_num_vlan_tunnel_infos(vg); | |
89 | rcu_read_unlock(); | |
90 | ||
91 | return num_tinfos * __get_vlan_tinfo_size(); | |
92 | } | |
93 | ||
94 | static int br_fill_vlan_tinfo(struct sk_buff *skb, u16 vid, | |
95 | __be64 tunnel_id, u16 flags) | |
96 | { | |
97 | __be32 tid = tunnel_id_to_key32(tunnel_id); | |
98 | struct nlattr *tmap; | |
99 | ||
ae0be8de | 100 | tmap = nla_nest_start_noflag(skb, IFLA_BRIDGE_VLAN_TUNNEL_INFO); |
efa5356b RP |
101 | if (!tmap) |
102 | return -EMSGSIZE; | |
103 | if (nla_put_u32(skb, IFLA_BRIDGE_VLAN_TUNNEL_ID, | |
104 | be32_to_cpu(tid))) | |
105 | goto nla_put_failure; | |
106 | if (nla_put_u16(skb, IFLA_BRIDGE_VLAN_TUNNEL_VID, | |
107 | vid)) | |
108 | goto nla_put_failure; | |
109 | if (nla_put_u16(skb, IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, | |
110 | flags)) | |
111 | goto nla_put_failure; | |
112 | nla_nest_end(skb, tmap); | |
113 | ||
114 | return 0; | |
115 | ||
116 | nla_put_failure: | |
117 | nla_nest_cancel(skb, tmap); | |
118 | ||
119 | return -EMSGSIZE; | |
120 | } | |
121 | ||
122 | static int br_fill_vlan_tinfo_range(struct sk_buff *skb, | |
123 | struct net_bridge_vlan *vtbegin, | |
124 | struct net_bridge_vlan *vtend) | |
125 | { | |
126 | int err; | |
127 | ||
a8cab863 | 128 | if (vtend && (vtend->vid - vtbegin->vid) > 0) { |
efa5356b RP |
129 | /* add range to skb */ |
130 | err = br_fill_vlan_tinfo(skb, vtbegin->vid, | |
131 | vtbegin->tinfo.tunnel_id, | |
132 | BRIDGE_VLAN_INFO_RANGE_BEGIN); | |
133 | if (err) | |
134 | return err; | |
135 | ||
136 | err = br_fill_vlan_tinfo(skb, vtend->vid, | |
137 | vtend->tinfo.tunnel_id, | |
138 | BRIDGE_VLAN_INFO_RANGE_END); | |
139 | if (err) | |
140 | return err; | |
141 | } else { | |
142 | err = br_fill_vlan_tinfo(skb, vtbegin->vid, | |
143 | vtbegin->tinfo.tunnel_id, | |
144 | 0); | |
145 | if (err) | |
146 | return err; | |
147 | } | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
152 | int br_fill_vlan_tunnel_info(struct sk_buff *skb, | |
153 | struct net_bridge_vlan_group *vg) | |
154 | { | |
155 | struct net_bridge_vlan *vtbegin = NULL; | |
156 | struct net_bridge_vlan *vtend = NULL; | |
157 | struct net_bridge_vlan *v; | |
158 | int err; | |
159 | ||
160 | /* Count number of vlan infos */ | |
161 | list_for_each_entry_rcu(v, &vg->vlan_list, vlist) { | |
162 | /* only a context, bridge vlan not activated */ | |
163 | if (!br_vlan_should_use(v)) | |
164 | continue; | |
165 | ||
166 | if (!v->tinfo.tunnel_dst) | |
167 | continue; | |
168 | ||
169 | if (!vtbegin) { | |
170 | goto initvars; | |
171 | } else if ((v->vid - vtend->vid) == 1 && | |
8ef95947 | 172 | vlan_tunid_inrange(v, vtend)) { |
efa5356b RP |
173 | vtend = v; |
174 | continue; | |
175 | } else { | |
176 | err = br_fill_vlan_tinfo_range(skb, vtbegin, vtend); | |
177 | if (err) | |
178 | return err; | |
179 | } | |
180 | initvars: | |
181 | vtbegin = v; | |
182 | vtend = v; | |
183 | } | |
184 | ||
185 | if (vtbegin) { | |
186 | err = br_fill_vlan_tinfo_range(skb, vtbegin, vtend); | |
187 | if (err) | |
188 | return err; | |
189 | } | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
194 | static const struct nla_policy vlan_tunnel_policy[IFLA_BRIDGE_VLAN_TUNNEL_MAX + 1] = { | |
195 | [IFLA_BRIDGE_VLAN_TUNNEL_ID] = { .type = NLA_U32 }, | |
196 | [IFLA_BRIDGE_VLAN_TUNNEL_VID] = { .type = NLA_U16 }, | |
197 | [IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] = { .type = NLA_U16 }, | |
198 | }; | |
199 | ||
200 | static int br_vlan_tunnel_info(struct net_bridge_port *p, int cmd, | |
e19b42a1 | 201 | u16 vid, u32 tun_id, bool *changed) |
efa5356b RP |
202 | { |
203 | int err = 0; | |
204 | ||
205 | if (!p) | |
206 | return -EINVAL; | |
207 | ||
208 | switch (cmd) { | |
209 | case RTM_SETLINK: | |
210 | err = nbp_vlan_tunnel_info_add(p, vid, tun_id); | |
e19b42a1 NA |
211 | if (!err) |
212 | *changed = true; | |
efa5356b RP |
213 | break; |
214 | case RTM_DELLINK: | |
e19b42a1 NA |
215 | if (!nbp_vlan_tunnel_info_delete(p, vid)) |
216 | *changed = true; | |
efa5356b RP |
217 | break; |
218 | } | |
219 | ||
220 | return err; | |
221 | } | |
222 | ||
223 | int br_parse_vlan_tunnel_info(struct nlattr *attr, | |
224 | struct vtunnel_info *tinfo) | |
225 | { | |
226 | struct nlattr *tb[IFLA_BRIDGE_VLAN_TUNNEL_MAX + 1]; | |
227 | u32 tun_id; | |
228 | u16 vid, flags = 0; | |
229 | int err; | |
230 | ||
231 | memset(tinfo, 0, sizeof(*tinfo)); | |
232 | ||
fceb6435 JB |
233 | err = nla_parse_nested(tb, IFLA_BRIDGE_VLAN_TUNNEL_MAX, attr, |
234 | vlan_tunnel_policy, NULL); | |
efa5356b RP |
235 | if (err < 0) |
236 | return err; | |
237 | ||
bb580ad6 NA |
238 | if (!tb[IFLA_BRIDGE_VLAN_TUNNEL_ID] || |
239 | !tb[IFLA_BRIDGE_VLAN_TUNNEL_VID]) | |
240 | return -EINVAL; | |
241 | ||
efa5356b RP |
242 | tun_id = nla_get_u32(tb[IFLA_BRIDGE_VLAN_TUNNEL_ID]); |
243 | vid = nla_get_u16(tb[IFLA_BRIDGE_VLAN_TUNNEL_VID]); | |
244 | if (vid >= VLAN_VID_MASK) | |
245 | return -ERANGE; | |
246 | ||
247 | if (tb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]) | |
248 | flags = nla_get_u16(tb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]); | |
249 | ||
250 | tinfo->tunid = tun_id; | |
251 | tinfo->vid = vid; | |
252 | tinfo->flags = flags; | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
257 | int br_process_vlan_tunnel_info(struct net_bridge *br, | |
258 | struct net_bridge_port *p, int cmd, | |
259 | struct vtunnel_info *tinfo_curr, | |
e19b42a1 NA |
260 | struct vtunnel_info *tinfo_last, |
261 | bool *changed) | |
efa5356b RP |
262 | { |
263 | int err; | |
264 | ||
265 | if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) { | |
266 | if (tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) | |
267 | return -EINVAL; | |
268 | memcpy(tinfo_last, tinfo_curr, sizeof(struct vtunnel_info)); | |
269 | } else if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_END) { | |
270 | int t, v; | |
271 | ||
272 | if (!(tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN)) | |
273 | return -EINVAL; | |
274 | if ((tinfo_curr->vid - tinfo_last->vid) != | |
275 | (tinfo_curr->tunid - tinfo_last->tunid)) | |
276 | return -EINVAL; | |
277 | t = tinfo_last->tunid; | |
278 | for (v = tinfo_last->vid; v <= tinfo_curr->vid; v++) { | |
e19b42a1 | 279 | err = br_vlan_tunnel_info(p, cmd, v, t, changed); |
efa5356b RP |
280 | if (err) |
281 | return err; | |
282 | t++; | |
283 | } | |
284 | memset(tinfo_last, 0, sizeof(struct vtunnel_info)); | |
285 | memset(tinfo_curr, 0, sizeof(struct vtunnel_info)); | |
286 | } else { | |
287 | if (tinfo_last->flags) | |
288 | return -EINVAL; | |
289 | err = br_vlan_tunnel_info(p, cmd, tinfo_curr->vid, | |
e19b42a1 | 290 | tinfo_curr->tunid, changed); |
efa5356b RP |
291 | if (err) |
292 | return err; | |
293 | memset(tinfo_last, 0, sizeof(struct vtunnel_info)); | |
294 | memset(tinfo_curr, 0, sizeof(struct vtunnel_info)); | |
295 | } | |
296 | ||
297 | return 0; | |
298 | } |