]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
499a2425 RP |
2 | /* |
3 | * lwtunnel Infrastructure for light weight tunnels like mpls | |
4 | * | |
5 | * Authors: Roopa Prabhu, <[email protected]> | |
499a2425 RP |
6 | */ |
7 | ||
8 | #include <linux/capability.h> | |
9 | #include <linux/module.h> | |
10 | #include <linux/types.h> | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/slab.h> | |
13 | #include <linux/uaccess.h> | |
14 | #include <linux/skbuff.h> | |
15 | #include <linux/netdevice.h> | |
16 | #include <linux/lwtunnel.h> | |
17 | #include <linux/in.h> | |
18 | #include <linux/init.h> | |
19 | #include <linux/err.h> | |
20 | ||
21 | #include <net/lwtunnel.h> | |
22 | #include <net/rtnetlink.h> | |
ffce4196 | 23 | #include <net/ip6_fib.h> |
3c618c1d | 24 | #include <net/rtnh.h> |
499a2425 | 25 | |
745041e2 RS |
26 | #ifdef CONFIG_MODULES |
27 | ||
28 | static const char *lwtunnel_encap_str(enum lwtunnel_encap_types encap_type) | |
29 | { | |
30 | /* Only lwt encaps implemented without using an interface for | |
31 | * the encap need to return a string here. | |
32 | */ | |
33 | switch (encap_type) { | |
34 | case LWTUNNEL_ENCAP_MPLS: | |
35 | return "MPLS"; | |
36 | case LWTUNNEL_ENCAP_ILA: | |
37 | return "ILA"; | |
6c8702c6 DL |
38 | case LWTUNNEL_ENCAP_SEG6: |
39 | return "SEG6"; | |
3a0af8fd TG |
40 | case LWTUNNEL_ENCAP_BPF: |
41 | return "BPF"; | |
d1df6fd8 DL |
42 | case LWTUNNEL_ENCAP_SEG6_LOCAL: |
43 | return "SEG6LOCAL"; | |
745041e2 RS |
44 | case LWTUNNEL_ENCAP_IP6: |
45 | case LWTUNNEL_ENCAP_IP: | |
46 | case LWTUNNEL_ENCAP_NONE: | |
47 | case __LWTUNNEL_ENCAP_MAX: | |
48 | /* should not have got here */ | |
49 | WARN_ON(1); | |
50 | break; | |
51 | } | |
52 | return NULL; | |
53 | } | |
54 | ||
55 | #endif /* CONFIG_MODULES */ | |
56 | ||
499a2425 RP |
57 | struct lwtunnel_state *lwtunnel_state_alloc(int encap_len) |
58 | { | |
59 | struct lwtunnel_state *lws; | |
60 | ||
61 | lws = kzalloc(sizeof(*lws) + encap_len, GFP_ATOMIC); | |
62 | ||
63 | return lws; | |
64 | } | |
08bd10ff | 65 | EXPORT_SYMBOL_GPL(lwtunnel_state_alloc); |
499a2425 | 66 | |
92a99bf3 | 67 | static const struct lwtunnel_encap_ops __rcu * |
499a2425 RP |
68 | lwtun_encaps[LWTUNNEL_ENCAP_MAX + 1] __read_mostly; |
69 | ||
70 | int lwtunnel_encap_add_ops(const struct lwtunnel_encap_ops *ops, | |
71 | unsigned int num) | |
72 | { | |
73 | if (num > LWTUNNEL_ENCAP_MAX) | |
74 | return -ERANGE; | |
75 | ||
76 | return !cmpxchg((const struct lwtunnel_encap_ops **) | |
77 | &lwtun_encaps[num], | |
78 | NULL, ops) ? 0 : -1; | |
79 | } | |
08bd10ff | 80 | EXPORT_SYMBOL_GPL(lwtunnel_encap_add_ops); |
499a2425 RP |
81 | |
82 | int lwtunnel_encap_del_ops(const struct lwtunnel_encap_ops *ops, | |
83 | unsigned int encap_type) | |
84 | { | |
85 | int ret; | |
86 | ||
87 | if (encap_type == LWTUNNEL_ENCAP_NONE || | |
88 | encap_type > LWTUNNEL_ENCAP_MAX) | |
89 | return -ERANGE; | |
90 | ||
91 | ret = (cmpxchg((const struct lwtunnel_encap_ops **) | |
92 | &lwtun_encaps[encap_type], | |
93 | ops, NULL) == ops) ? 0 : -1; | |
94 | ||
95 | synchronize_net(); | |
96 | ||
97 | return ret; | |
98 | } | |
08bd10ff | 99 | EXPORT_SYMBOL_GPL(lwtunnel_encap_del_ops); |
499a2425 | 100 | |
30357d7d | 101 | int lwtunnel_build_state(u16 encap_type, |
127eb7cd | 102 | struct nlattr *encap, unsigned int family, |
9ae28727 DA |
103 | const void *cfg, struct lwtunnel_state **lws, |
104 | struct netlink_ext_ack *extack) | |
499a2425 RP |
105 | { |
106 | const struct lwtunnel_encap_ops *ops; | |
9ae28727 | 107 | bool found = false; |
499a2425 RP |
108 | int ret = -EINVAL; |
109 | ||
110 | if (encap_type == LWTUNNEL_ENCAP_NONE || | |
9ae28727 DA |
111 | encap_type > LWTUNNEL_ENCAP_MAX) { |
112 | NL_SET_ERR_MSG_ATTR(extack, encap, | |
113 | "Unknown LWT encapsulation type"); | |
499a2425 | 114 | return ret; |
9ae28727 | 115 | } |
499a2425 RP |
116 | |
117 | ret = -EOPNOTSUPP; | |
118 | rcu_read_lock(); | |
119 | ops = rcu_dereference(lwtun_encaps[encap_type]); | |
3d25eabb | 120 | if (likely(ops && ops->build_state && try_module_get(ops->owner))) |
9ae28727 | 121 | found = true; |
3d25eabb | 122 | rcu_read_unlock(); |
123 | ||
124 | if (found) { | |
9ae28727 | 125 | ret = ops->build_state(encap, family, cfg, lws, extack); |
85c81401 RS |
126 | if (ret) |
127 | module_put(ops->owner); | |
3d25eabb | 128 | } else { |
129 | /* don't rely on -EOPNOTSUPP to detect match as build_state | |
130 | * handlers could return it | |
131 | */ | |
9ae28727 DA |
132 | NL_SET_ERR_MSG_ATTR(extack, encap, |
133 | "LWT encapsulation type not supported"); | |
134 | } | |
135 | ||
9ed59592 DA |
136 | return ret; |
137 | } | |
08bd10ff | 138 | EXPORT_SYMBOL_GPL(lwtunnel_build_state); |
9ed59592 | 139 | |
c255bd68 | 140 | int lwtunnel_valid_encap_type(u16 encap_type, struct netlink_ext_ack *extack) |
9ed59592 DA |
141 | { |
142 | const struct lwtunnel_encap_ops *ops; | |
143 | int ret = -EINVAL; | |
144 | ||
145 | if (encap_type == LWTUNNEL_ENCAP_NONE || | |
c255bd68 DA |
146 | encap_type > LWTUNNEL_ENCAP_MAX) { |
147 | NL_SET_ERR_MSG(extack, "Unknown lwt encapsulation type"); | |
9ed59592 | 148 | return ret; |
c255bd68 | 149 | } |
9ed59592 DA |
150 | |
151 | rcu_read_lock(); | |
152 | ops = rcu_dereference(lwtun_encaps[encap_type]); | |
153 | rcu_read_unlock(); | |
745041e2 RS |
154 | #ifdef CONFIG_MODULES |
155 | if (!ops) { | |
156 | const char *encap_type_str = lwtunnel_encap_str(encap_type); | |
157 | ||
158 | if (encap_type_str) { | |
9ed59592 | 159 | __rtnl_unlock(); |
745041e2 | 160 | request_module("rtnl-lwt-%s", encap_type_str); |
9ed59592 DA |
161 | rtnl_lock(); |
162 | ||
745041e2 RS |
163 | rcu_read_lock(); |
164 | ops = rcu_dereference(lwtun_encaps[encap_type]); | |
9ed59592 | 165 | rcu_read_unlock(); |
745041e2 RS |
166 | } |
167 | } | |
168 | #endif | |
c255bd68 DA |
169 | ret = ops ? 0 : -EOPNOTSUPP; |
170 | if (ret < 0) | |
171 | NL_SET_ERR_MSG(extack, "lwt encapsulation type not supported"); | |
172 | ||
173 | return ret; | |
9ed59592 | 174 | } |
08bd10ff | 175 | EXPORT_SYMBOL_GPL(lwtunnel_valid_encap_type); |
499a2425 | 176 | |
c255bd68 DA |
177 | int lwtunnel_valid_encap_type_attr(struct nlattr *attr, int remaining, |
178 | struct netlink_ext_ack *extack) | |
9ed59592 DA |
179 | { |
180 | struct rtnexthop *rtnh = (struct rtnexthop *)attr; | |
181 | struct nlattr *nla_entype; | |
182 | struct nlattr *attrs; | |
9ed59592 DA |
183 | u16 encap_type; |
184 | int attrlen; | |
185 | ||
186 | while (rtnh_ok(rtnh, remaining)) { | |
187 | attrlen = rtnh_attrlen(rtnh); | |
188 | if (attrlen > 0) { | |
189 | attrs = rtnh_attrs(rtnh); | |
9ed59592 DA |
190 | nla_entype = nla_find(attrs, attrlen, RTA_ENCAP_TYPE); |
191 | ||
192 | if (nla_entype) { | |
193 | encap_type = nla_get_u16(nla_entype); | |
194 | ||
c255bd68 DA |
195 | if (lwtunnel_valid_encap_type(encap_type, |
196 | extack) != 0) | |
9ed59592 DA |
197 | return -EOPNOTSUPP; |
198 | } | |
199 | } | |
200 | rtnh = rtnh_next(rtnh, &remaining); | |
201 | } | |
202 | ||
203 | return 0; | |
499a2425 | 204 | } |
08bd10ff | 205 | EXPORT_SYMBOL_GPL(lwtunnel_valid_encap_type_attr); |
499a2425 | 206 | |
1104d9ba TH |
207 | void lwtstate_free(struct lwtunnel_state *lws) |
208 | { | |
209 | const struct lwtunnel_encap_ops *ops = lwtun_encaps[lws->type]; | |
210 | ||
211 | if (ops->destroy_state) { | |
212 | ops->destroy_state(lws); | |
213 | kfree_rcu(lws, rcu); | |
214 | } else { | |
215 | kfree(lws); | |
216 | } | |
85c81401 | 217 | module_put(ops->owner); |
1104d9ba | 218 | } |
08bd10ff | 219 | EXPORT_SYMBOL_GPL(lwtstate_free); |
1104d9ba | 220 | |
ffa8ce54 DA |
221 | int lwtunnel_fill_encap(struct sk_buff *skb, struct lwtunnel_state *lwtstate, |
222 | int encap_attr, int encap_type_attr) | |
499a2425 RP |
223 | { |
224 | const struct lwtunnel_encap_ops *ops; | |
225 | struct nlattr *nest; | |
39f37095 | 226 | int ret; |
499a2425 RP |
227 | |
228 | if (!lwtstate) | |
229 | return 0; | |
230 | ||
231 | if (lwtstate->type == LWTUNNEL_ENCAP_NONE || | |
232 | lwtstate->type > LWTUNNEL_ENCAP_MAX) | |
233 | return 0; | |
234 | ||
ae0be8de | 235 | nest = nla_nest_start_noflag(skb, encap_attr); |
a50fe0ff | 236 | if (!nest) |
39f37095 DC |
237 | return -EMSGSIZE; |
238 | ||
239 | ret = -EOPNOTSUPP; | |
499a2425 RP |
240 | rcu_read_lock(); |
241 | ops = rcu_dereference(lwtun_encaps[lwtstate->type]); | |
242 | if (likely(ops && ops->fill_encap)) | |
243 | ret = ops->fill_encap(skb, lwtstate); | |
244 | rcu_read_unlock(); | |
245 | ||
246 | if (ret) | |
247 | goto nla_put_failure; | |
248 | nla_nest_end(skb, nest); | |
ffa8ce54 | 249 | ret = nla_put_u16(skb, encap_type_attr, lwtstate->type); |
499a2425 RP |
250 | if (ret) |
251 | goto nla_put_failure; | |
252 | ||
253 | return 0; | |
254 | ||
255 | nla_put_failure: | |
256 | nla_nest_cancel(skb, nest); | |
257 | ||
258 | return (ret == -EOPNOTSUPP ? 0 : ret); | |
259 | } | |
08bd10ff | 260 | EXPORT_SYMBOL_GPL(lwtunnel_fill_encap); |
499a2425 RP |
261 | |
262 | int lwtunnel_get_encap_size(struct lwtunnel_state *lwtstate) | |
263 | { | |
264 | const struct lwtunnel_encap_ops *ops; | |
265 | int ret = 0; | |
266 | ||
267 | if (!lwtstate) | |
268 | return 0; | |
269 | ||
270 | if (lwtstate->type == LWTUNNEL_ENCAP_NONE || | |
271 | lwtstate->type > LWTUNNEL_ENCAP_MAX) | |
272 | return 0; | |
273 | ||
274 | rcu_read_lock(); | |
275 | ops = rcu_dereference(lwtun_encaps[lwtstate->type]); | |
276 | if (likely(ops && ops->get_encap_size)) | |
277 | ret = nla_total_size(ops->get_encap_size(lwtstate)); | |
278 | rcu_read_unlock(); | |
279 | ||
280 | return ret; | |
281 | } | |
08bd10ff | 282 | EXPORT_SYMBOL_GPL(lwtunnel_get_encap_size); |
499a2425 RP |
283 | |
284 | int lwtunnel_cmp_encap(struct lwtunnel_state *a, struct lwtunnel_state *b) | |
285 | { | |
286 | const struct lwtunnel_encap_ops *ops; | |
287 | int ret = 0; | |
288 | ||
289 | if (!a && !b) | |
290 | return 0; | |
291 | ||
292 | if (!a || !b) | |
293 | return 1; | |
294 | ||
295 | if (a->type != b->type) | |
296 | return 1; | |
297 | ||
298 | if (a->type == LWTUNNEL_ENCAP_NONE || | |
299 | a->type > LWTUNNEL_ENCAP_MAX) | |
300 | return 0; | |
301 | ||
302 | rcu_read_lock(); | |
303 | ops = rcu_dereference(lwtun_encaps[a->type]); | |
304 | if (likely(ops && ops->cmp_encap)) | |
305 | ret = ops->cmp_encap(a, b); | |
306 | rcu_read_unlock(); | |
307 | ||
308 | return ret; | |
309 | } | |
08bd10ff | 310 | EXPORT_SYMBOL_GPL(lwtunnel_cmp_encap); |
ffce4196 | 311 | |
ede2059d | 312 | int lwtunnel_output(struct net *net, struct sock *sk, struct sk_buff *skb) |
ffce4196 | 313 | { |
61adedf3 | 314 | struct dst_entry *dst = skb_dst(skb); |
ffce4196 | 315 | const struct lwtunnel_encap_ops *ops; |
61adedf3 | 316 | struct lwtunnel_state *lwtstate; |
ffce4196 RP |
317 | int ret = -EINVAL; |
318 | ||
61adedf3 | 319 | if (!dst) |
ffce4196 | 320 | goto drop; |
61adedf3 | 321 | lwtstate = dst->lwtstate; |
ffce4196 RP |
322 | |
323 | if (lwtstate->type == LWTUNNEL_ENCAP_NONE || | |
324 | lwtstate->type > LWTUNNEL_ENCAP_MAX) | |
325 | return 0; | |
326 | ||
327 | ret = -EOPNOTSUPP; | |
328 | rcu_read_lock(); | |
329 | ops = rcu_dereference(lwtun_encaps[lwtstate->type]); | |
330 | if (likely(ops && ops->output)) | |
ede2059d | 331 | ret = ops->output(net, sk, skb); |
ffce4196 RP |
332 | rcu_read_unlock(); |
333 | ||
334 | if (ret == -EOPNOTSUPP) | |
335 | goto drop; | |
336 | ||
337 | return ret; | |
338 | ||
339 | drop: | |
e11f40b9 | 340 | kfree_skb(skb); |
ffce4196 RP |
341 | |
342 | return ret; | |
343 | } | |
08bd10ff | 344 | EXPORT_SYMBOL_GPL(lwtunnel_output); |
25368623 | 345 | |
14972cbd RP |
346 | int lwtunnel_xmit(struct sk_buff *skb) |
347 | { | |
348 | struct dst_entry *dst = skb_dst(skb); | |
349 | const struct lwtunnel_encap_ops *ops; | |
350 | struct lwtunnel_state *lwtstate; | |
351 | int ret = -EINVAL; | |
352 | ||
353 | if (!dst) | |
354 | goto drop; | |
355 | ||
356 | lwtstate = dst->lwtstate; | |
357 | ||
358 | if (lwtstate->type == LWTUNNEL_ENCAP_NONE || | |
359 | lwtstate->type > LWTUNNEL_ENCAP_MAX) | |
360 | return 0; | |
361 | ||
362 | ret = -EOPNOTSUPP; | |
363 | rcu_read_lock(); | |
364 | ops = rcu_dereference(lwtun_encaps[lwtstate->type]); | |
365 | if (likely(ops && ops->xmit)) | |
366 | ret = ops->xmit(skb); | |
367 | rcu_read_unlock(); | |
368 | ||
369 | if (ret == -EOPNOTSUPP) | |
370 | goto drop; | |
371 | ||
372 | return ret; | |
373 | ||
374 | drop: | |
375 | kfree_skb(skb); | |
376 | ||
377 | return ret; | |
378 | } | |
08bd10ff | 379 | EXPORT_SYMBOL_GPL(lwtunnel_xmit); |
14972cbd | 380 | |
61adedf3 | 381 | int lwtunnel_input(struct sk_buff *skb) |
25368623 | 382 | { |
61adedf3 | 383 | struct dst_entry *dst = skb_dst(skb); |
25368623 | 384 | const struct lwtunnel_encap_ops *ops; |
61adedf3 | 385 | struct lwtunnel_state *lwtstate; |
25368623 TH |
386 | int ret = -EINVAL; |
387 | ||
61adedf3 | 388 | if (!dst) |
25368623 | 389 | goto drop; |
61adedf3 | 390 | lwtstate = dst->lwtstate; |
25368623 TH |
391 | |
392 | if (lwtstate->type == LWTUNNEL_ENCAP_NONE || | |
393 | lwtstate->type > LWTUNNEL_ENCAP_MAX) | |
394 | return 0; | |
395 | ||
396 | ret = -EOPNOTSUPP; | |
397 | rcu_read_lock(); | |
398 | ops = rcu_dereference(lwtun_encaps[lwtstate->type]); | |
399 | if (likely(ops && ops->input)) | |
400 | ret = ops->input(skb); | |
401 | rcu_read_unlock(); | |
402 | ||
403 | if (ret == -EOPNOTSUPP) | |
404 | goto drop; | |
405 | ||
406 | return ret; | |
407 | ||
408 | drop: | |
409 | kfree_skb(skb); | |
410 | ||
411 | return ret; | |
412 | } | |
08bd10ff | 413 | EXPORT_SYMBOL_GPL(lwtunnel_input); |