]>
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 | ||
33 | static bool vlan_tunnel_id_isrange(struct net_bridge_vlan *v, | |
34 | struct net_bridge_vlan *v_end) | |
35 | { | |
36 | __be32 tunid_curr = tunnel_id_to_key32(v->tinfo.tunnel_id); | |
37 | __be32 tunid_end = tunnel_id_to_key32(v_end->tinfo.tunnel_id); | |
38 | ||
39 | return (be32_to_cpu(tunid_curr) - be32_to_cpu(tunid_end)) == 1; | |
40 | } | |
41 | ||
42 | static int __get_num_vlan_tunnel_infos(struct net_bridge_vlan_group *vg) | |
43 | { | |
44 | struct net_bridge_vlan *v, *v_start = NULL, *v_end = NULL; | |
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 | ||
53 | if (!v_start) { | |
54 | goto initvars; | |
55 | } else if ((v->vid - v_end->vid) == 1 && | |
56 | vlan_tunnel_id_isrange(v_end, v) == 1) { | |
57 | v_end = v; | |
58 | continue; | |
59 | } else { | |
60 | if ((v_end->vid - v->vid) > 0 && | |
61 | vlan_tunnel_id_isrange(v_end, v) > 0) | |
62 | num_tinfos += 2; | |
63 | else | |
64 | num_tinfos += 1; | |
65 | } | |
66 | initvars: | |
67 | v_start = v; | |
68 | v_end = v; | |
69 | } | |
70 | ||
71 | if (v_start) { | |
72 | if ((v_end->vid - v->vid) > 0 && | |
73 | vlan_tunnel_id_isrange(v_end, v) > 0) | |
74 | num_tinfos += 2; | |
75 | else | |
76 | num_tinfos += 1; | |
77 | } | |
78 | ||
79 | return num_tinfos; | |
80 | } | |
81 | ||
82 | int br_get_vlan_tunnel_info_size(struct net_bridge_vlan_group *vg) | |
83 | { | |
84 | int num_tinfos; | |
85 | ||
86 | if (!vg) | |
87 | return 0; | |
88 | ||
89 | rcu_read_lock(); | |
90 | num_tinfos = __get_num_vlan_tunnel_infos(vg); | |
91 | rcu_read_unlock(); | |
92 | ||
93 | return num_tinfos * __get_vlan_tinfo_size(); | |
94 | } | |
95 | ||
96 | static int br_fill_vlan_tinfo(struct sk_buff *skb, u16 vid, | |
97 | __be64 tunnel_id, u16 flags) | |
98 | { | |
99 | __be32 tid = tunnel_id_to_key32(tunnel_id); | |
100 | struct nlattr *tmap; | |
101 | ||
102 | tmap = nla_nest_start(skb, IFLA_BRIDGE_VLAN_TUNNEL_INFO); | |
103 | if (!tmap) | |
104 | return -EMSGSIZE; | |
105 | if (nla_put_u32(skb, IFLA_BRIDGE_VLAN_TUNNEL_ID, | |
106 | be32_to_cpu(tid))) | |
107 | goto nla_put_failure; | |
108 | if (nla_put_u16(skb, IFLA_BRIDGE_VLAN_TUNNEL_VID, | |
109 | vid)) | |
110 | goto nla_put_failure; | |
111 | if (nla_put_u16(skb, IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, | |
112 | flags)) | |
113 | goto nla_put_failure; | |
114 | nla_nest_end(skb, tmap); | |
115 | ||
116 | return 0; | |
117 | ||
118 | nla_put_failure: | |
119 | nla_nest_cancel(skb, tmap); | |
120 | ||
121 | return -EMSGSIZE; | |
122 | } | |
123 | ||
124 | static int br_fill_vlan_tinfo_range(struct sk_buff *skb, | |
125 | struct net_bridge_vlan *vtbegin, | |
126 | struct net_bridge_vlan *vtend) | |
127 | { | |
128 | int err; | |
129 | ||
130 | if (vtbegin && vtend && (vtend->vid - vtbegin->vid) > 0) { | |
131 | /* add range to skb */ | |
132 | err = br_fill_vlan_tinfo(skb, vtbegin->vid, | |
133 | vtbegin->tinfo.tunnel_id, | |
134 | BRIDGE_VLAN_INFO_RANGE_BEGIN); | |
135 | if (err) | |
136 | return err; | |
137 | ||
138 | err = br_fill_vlan_tinfo(skb, vtend->vid, | |
139 | vtend->tinfo.tunnel_id, | |
140 | BRIDGE_VLAN_INFO_RANGE_END); | |
141 | if (err) | |
142 | return err; | |
143 | } else { | |
144 | err = br_fill_vlan_tinfo(skb, vtbegin->vid, | |
145 | vtbegin->tinfo.tunnel_id, | |
146 | 0); | |
147 | if (err) | |
148 | return err; | |
149 | } | |
150 | ||
151 | return 0; | |
152 | } | |
153 | ||
154 | int br_fill_vlan_tunnel_info(struct sk_buff *skb, | |
155 | struct net_bridge_vlan_group *vg) | |
156 | { | |
157 | struct net_bridge_vlan *vtbegin = NULL; | |
158 | struct net_bridge_vlan *vtend = NULL; | |
159 | struct net_bridge_vlan *v; | |
160 | int err; | |
161 | ||
162 | /* Count number of vlan infos */ | |
163 | list_for_each_entry_rcu(v, &vg->vlan_list, vlist) { | |
164 | /* only a context, bridge vlan not activated */ | |
165 | if (!br_vlan_should_use(v)) | |
166 | continue; | |
167 | ||
168 | if (!v->tinfo.tunnel_dst) | |
169 | continue; | |
170 | ||
171 | if (!vtbegin) { | |
172 | goto initvars; | |
173 | } else if ((v->vid - vtend->vid) == 1 && | |
174 | vlan_tunnel_id_isrange(v, vtend)) { | |
175 | vtend = v; | |
176 | continue; | |
177 | } else { | |
178 | err = br_fill_vlan_tinfo_range(skb, vtbegin, vtend); | |
179 | if (err) | |
180 | return err; | |
181 | } | |
182 | initvars: | |
183 | vtbegin = v; | |
184 | vtend = v; | |
185 | } | |
186 | ||
187 | if (vtbegin) { | |
188 | err = br_fill_vlan_tinfo_range(skb, vtbegin, vtend); | |
189 | if (err) | |
190 | return err; | |
191 | } | |
192 | ||
193 | return 0; | |
194 | } | |
195 | ||
196 | static const struct nla_policy vlan_tunnel_policy[IFLA_BRIDGE_VLAN_TUNNEL_MAX + 1] = { | |
197 | [IFLA_BRIDGE_VLAN_TUNNEL_ID] = { .type = NLA_U32 }, | |
198 | [IFLA_BRIDGE_VLAN_TUNNEL_VID] = { .type = NLA_U16 }, | |
199 | [IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] = { .type = NLA_U16 }, | |
200 | }; | |
201 | ||
202 | static int br_vlan_tunnel_info(struct net_bridge_port *p, int cmd, | |
203 | u16 vid, u32 tun_id) | |
204 | { | |
205 | int err = 0; | |
206 | ||
207 | if (!p) | |
208 | return -EINVAL; | |
209 | ||
210 | switch (cmd) { | |
211 | case RTM_SETLINK: | |
212 | err = nbp_vlan_tunnel_info_add(p, vid, tun_id); | |
213 | break; | |
214 | case RTM_DELLINK: | |
215 | nbp_vlan_tunnel_info_delete(p, vid); | |
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 | ||
efa5356b RP |
232 | err = nla_parse_nested(tb, IFLA_BRIDGE_VLAN_TUNNEL_MAX, |
233 | attr, vlan_tunnel_policy); | |
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 | ||
256 | int br_process_vlan_tunnel_info(struct net_bridge *br, | |
257 | struct net_bridge_port *p, int cmd, | |
258 | struct vtunnel_info *tinfo_curr, | |
259 | struct vtunnel_info *tinfo_last) | |
260 | { | |
261 | int err; | |
262 | ||
263 | if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) { | |
264 | if (tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) | |
265 | return -EINVAL; | |
266 | memcpy(tinfo_last, tinfo_curr, sizeof(struct vtunnel_info)); | |
267 | } else if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_END) { | |
268 | int t, v; | |
269 | ||
270 | if (!(tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN)) | |
271 | return -EINVAL; | |
272 | if ((tinfo_curr->vid - tinfo_last->vid) != | |
273 | (tinfo_curr->tunid - tinfo_last->tunid)) | |
274 | return -EINVAL; | |
275 | t = tinfo_last->tunid; | |
276 | for (v = tinfo_last->vid; v <= tinfo_curr->vid; v++) { | |
277 | err = br_vlan_tunnel_info(p, cmd, v, t); | |
278 | if (err) | |
279 | return err; | |
280 | t++; | |
281 | } | |
282 | memset(tinfo_last, 0, sizeof(struct vtunnel_info)); | |
283 | memset(tinfo_curr, 0, sizeof(struct vtunnel_info)); | |
284 | } else { | |
285 | if (tinfo_last->flags) | |
286 | return -EINVAL; | |
287 | err = br_vlan_tunnel_info(p, cmd, tinfo_curr->vid, | |
288 | tinfo_curr->tunid); | |
289 | if (err) | |
290 | return err; | |
291 | memset(tinfo_last, 0, sizeof(struct vtunnel_info)); | |
292 | memset(tinfo_curr, 0, sizeof(struct vtunnel_info)); | |
293 | } | |
294 | ||
295 | return 0; | |
296 | } |