1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * SR-IPv6 implementation |
4 | * |
5 | * Author: |
6 | * David Lebrun <david.lebrun@uclouvain.be> |
7 | */ |
8 | |
9 | #include <linux/errno.h> |
10 | #include <linux/types.h> |
11 | #include <linux/socket.h> |
12 | #include <linux/net.h> |
13 | #include <linux/in6.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/rhashtable.h> |
16 | |
17 | #include <net/ipv6.h> |
18 | #include <net/protocol.h> |
19 | |
20 | #include <net/seg6.h> |
21 | #include <net/genetlink.h> |
22 | #include <linux/seg6.h> |
23 | #include <linux/seg6_genl.h> |
24 | #ifdef CONFIG_IPV6_SEG6_HMAC |
25 | #include <net/seg6_hmac.h> |
26 | #endif |
27 | |
28 | bool seg6_validate_srh(struct ipv6_sr_hdr *srh, int len, bool reduced) |
29 | { |
30 | unsigned int tlv_offset; |
31 | int max_last_entry; |
32 | int trailing; |
33 | |
34 | if (srh->type != IPV6_SRCRT_TYPE_4) |
35 | return false; |
36 | |
37 | if (((srh->hdrlen + 1) << 3) != len) |
38 | return false; |
39 | |
40 | if (!reduced && srh->segments_left > srh->first_segment) { |
41 | return false; |
42 | } else { |
43 | max_last_entry = (srh->hdrlen / 2) - 1; |
44 | |
45 | if (srh->first_segment > max_last_entry) |
46 | return false; |
47 | |
48 | if (srh->segments_left > srh->first_segment + 1) |
49 | return false; |
50 | } |
51 | |
52 | tlv_offset = sizeof(*srh) + ((srh->first_segment + 1) << 4); |
53 | |
54 | trailing = len - tlv_offset; |
55 | if (trailing < 0) |
56 | return false; |
57 | |
58 | while (trailing) { |
59 | struct sr6_tlv *tlv; |
60 | unsigned int tlv_len; |
61 | |
62 | if (trailing < sizeof(*tlv)) |
63 | return false; |
64 | |
65 | tlv = (struct sr6_tlv *)((unsigned char *)srh + tlv_offset); |
66 | tlv_len = sizeof(*tlv) + tlv->len; |
67 | |
68 | trailing -= tlv_len; |
69 | if (trailing < 0) |
70 | return false; |
71 | |
72 | tlv_offset += tlv_len; |
73 | } |
74 | |
75 | return true; |
76 | } |
77 | |
78 | struct ipv6_sr_hdr *seg6_get_srh(struct sk_buff *skb, int flags) |
79 | { |
80 | struct ipv6_sr_hdr *srh; |
81 | int len, srhoff = 0; |
82 | |
83 | if (ipv6_find_hdr(skb, offset: &srhoff, IPPROTO_ROUTING, NULL, fragflg: &flags) < 0) |
84 | return NULL; |
85 | |
86 | if (!pskb_may_pull(skb, len: srhoff + sizeof(*srh))) |
87 | return NULL; |
88 | |
89 | srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); |
90 | |
91 | len = (srh->hdrlen + 1) << 3; |
92 | |
93 | if (!pskb_may_pull(skb, len: srhoff + len)) |
94 | return NULL; |
95 | |
96 | /* note that pskb_may_pull may change pointers in header; |
97 | * for this reason it is necessary to reload them when needed. |
98 | */ |
99 | srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); |
100 | |
101 | if (!seg6_validate_srh(srh, len, reduced: true)) |
102 | return NULL; |
103 | |
104 | return srh; |
105 | } |
106 | |
107 | /* Determine if an ICMP invoking packet contains a segment routing |
108 | * header. If it does, extract the offset to the true destination |
109 | * address, which is in the first segment address. |
110 | */ |
111 | void seg6_icmp_srh(struct sk_buff *skb, struct inet6_skb_parm *opt) |
112 | { |
113 | __u16 = skb->network_header; |
114 | struct ipv6_sr_hdr *srh; |
115 | |
116 | /* Update network header to point to the invoking packet |
117 | * inside the ICMP packet, so we can use the seg6_get_srh() |
118 | * helper. |
119 | */ |
120 | skb_reset_network_header(skb); |
121 | |
122 | srh = seg6_get_srh(skb, flags: 0); |
123 | if (!srh) |
124 | goto out; |
125 | |
126 | if (srh->type != IPV6_SRCRT_TYPE_4) |
127 | goto out; |
128 | |
129 | opt->flags |= IP6SKB_SEG6; |
130 | opt->srhoff = (unsigned char *)srh - skb->data; |
131 | |
132 | out: |
133 | /* Restore the network header back to the ICMP packet */ |
134 | skb->network_header = network_header; |
135 | } |
136 | |
137 | static struct genl_family seg6_genl_family; |
138 | |
139 | static const struct nla_policy seg6_genl_policy[SEG6_ATTR_MAX + 1] = { |
140 | [SEG6_ATTR_DST] = { .type = NLA_BINARY, |
141 | .len = sizeof(struct in6_addr) }, |
142 | [SEG6_ATTR_DSTLEN] = { .type = NLA_S32, }, |
143 | [SEG6_ATTR_HMACKEYID] = { .type = NLA_U32, }, |
144 | [SEG6_ATTR_SECRET] = { .type = NLA_BINARY, }, |
145 | [SEG6_ATTR_SECRETLEN] = { .type = NLA_U8, }, |
146 | [SEG6_ATTR_ALGID] = { .type = NLA_U8, }, |
147 | [SEG6_ATTR_HMACINFO] = { .type = NLA_NESTED, }, |
148 | }; |
149 | |
150 | #ifdef CONFIG_IPV6_SEG6_HMAC |
151 | |
152 | static int seg6_genl_sethmac(struct sk_buff *skb, struct genl_info *info) |
153 | { |
154 | struct net *net = genl_info_net(info); |
155 | struct seg6_pernet_data *sdata; |
156 | struct seg6_hmac_info *hinfo; |
157 | u32 hmackeyid; |
158 | char *secret; |
159 | int err = 0; |
160 | u8 algid; |
161 | u8 slen; |
162 | |
163 | sdata = seg6_pernet(net); |
164 | |
165 | if (!info->attrs[SEG6_ATTR_HMACKEYID] || |
166 | !info->attrs[SEG6_ATTR_SECRETLEN] || |
167 | !info->attrs[SEG6_ATTR_ALGID]) |
168 | return -EINVAL; |
169 | |
170 | hmackeyid = nla_get_u32(nla: info->attrs[SEG6_ATTR_HMACKEYID]); |
171 | slen = nla_get_u8(nla: info->attrs[SEG6_ATTR_SECRETLEN]); |
172 | algid = nla_get_u8(nla: info->attrs[SEG6_ATTR_ALGID]); |
173 | |
174 | if (hmackeyid == 0) |
175 | return -EINVAL; |
176 | |
177 | if (slen > SEG6_HMAC_SECRET_LEN) |
178 | return -EINVAL; |
179 | |
180 | mutex_lock(&sdata->lock); |
181 | hinfo = seg6_hmac_info_lookup(net, key: hmackeyid); |
182 | |
183 | if (!slen) { |
184 | err = seg6_hmac_info_del(net, key: hmackeyid); |
185 | |
186 | goto out_unlock; |
187 | } |
188 | |
189 | if (!info->attrs[SEG6_ATTR_SECRET]) { |
190 | err = -EINVAL; |
191 | goto out_unlock; |
192 | } |
193 | |
194 | if (slen > nla_len(nla: info->attrs[SEG6_ATTR_SECRET])) { |
195 | err = -EINVAL; |
196 | goto out_unlock; |
197 | } |
198 | |
199 | if (hinfo) { |
200 | err = seg6_hmac_info_del(net, key: hmackeyid); |
201 | if (err) |
202 | goto out_unlock; |
203 | } |
204 | |
205 | secret = (char *)nla_data(nla: info->attrs[SEG6_ATTR_SECRET]); |
206 | |
207 | hinfo = kzalloc(size: sizeof(*hinfo), GFP_KERNEL); |
208 | if (!hinfo) { |
209 | err = -ENOMEM; |
210 | goto out_unlock; |
211 | } |
212 | |
213 | memcpy(hinfo->secret, secret, slen); |
214 | hinfo->slen = slen; |
215 | hinfo->alg_id = algid; |
216 | hinfo->hmackeyid = hmackeyid; |
217 | |
218 | err = seg6_hmac_info_add(net, key: hmackeyid, hinfo); |
219 | if (err) |
220 | kfree(objp: hinfo); |
221 | |
222 | out_unlock: |
223 | mutex_unlock(lock: &sdata->lock); |
224 | return err; |
225 | } |
226 | |
227 | #else |
228 | |
229 | static int seg6_genl_sethmac(struct sk_buff *skb, struct genl_info *info) |
230 | { |
231 | return -ENOTSUPP; |
232 | } |
233 | |
234 | #endif |
235 | |
236 | static int seg6_genl_set_tunsrc(struct sk_buff *skb, struct genl_info *info) |
237 | { |
238 | struct net *net = genl_info_net(info); |
239 | struct in6_addr *val, *t_old, *t_new; |
240 | struct seg6_pernet_data *sdata; |
241 | |
242 | sdata = seg6_pernet(net); |
243 | |
244 | if (!info->attrs[SEG6_ATTR_DST]) |
245 | return -EINVAL; |
246 | |
247 | val = nla_data(nla: info->attrs[SEG6_ATTR_DST]); |
248 | t_new = kmemdup(p: val, size: sizeof(*val), GFP_KERNEL); |
249 | if (!t_new) |
250 | return -ENOMEM; |
251 | |
252 | mutex_lock(&sdata->lock); |
253 | |
254 | t_old = sdata->tun_src; |
255 | rcu_assign_pointer(sdata->tun_src, t_new); |
256 | |
257 | mutex_unlock(lock: &sdata->lock); |
258 | |
259 | synchronize_net(); |
260 | kfree(objp: t_old); |
261 | |
262 | return 0; |
263 | } |
264 | |
265 | static int seg6_genl_get_tunsrc(struct sk_buff *skb, struct genl_info *info) |
266 | { |
267 | struct net *net = genl_info_net(info); |
268 | struct in6_addr *tun_src; |
269 | struct sk_buff *msg; |
270 | void *hdr; |
271 | |
272 | msg = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); |
273 | if (!msg) |
274 | return -ENOMEM; |
275 | |
276 | hdr = genlmsg_put(skb: msg, portid: info->snd_portid, seq: info->snd_seq, |
277 | family: &seg6_genl_family, flags: 0, cmd: SEG6_CMD_GET_TUNSRC); |
278 | if (!hdr) |
279 | goto free_msg; |
280 | |
281 | rcu_read_lock(); |
282 | tun_src = rcu_dereference(seg6_pernet(net)->tun_src); |
283 | |
284 | if (nla_put(skb: msg, attrtype: SEG6_ATTR_DST, attrlen: sizeof(struct in6_addr), data: tun_src)) |
285 | goto nla_put_failure; |
286 | |
287 | rcu_read_unlock(); |
288 | |
289 | genlmsg_end(skb: msg, hdr); |
290 | return genlmsg_reply(skb: msg, info); |
291 | |
292 | nla_put_failure: |
293 | rcu_read_unlock(); |
294 | free_msg: |
295 | nlmsg_free(skb: msg); |
296 | return -ENOMEM; |
297 | } |
298 | |
299 | #ifdef CONFIG_IPV6_SEG6_HMAC |
300 | |
301 | static int __seg6_hmac_fill_info(struct seg6_hmac_info *hinfo, |
302 | struct sk_buff *msg) |
303 | { |
304 | if (nla_put_u32(skb: msg, attrtype: SEG6_ATTR_HMACKEYID, value: hinfo->hmackeyid) || |
305 | nla_put_u8(skb: msg, attrtype: SEG6_ATTR_SECRETLEN, value: hinfo->slen) || |
306 | nla_put(skb: msg, attrtype: SEG6_ATTR_SECRET, attrlen: hinfo->slen, data: hinfo->secret) || |
307 | nla_put_u8(skb: msg, attrtype: SEG6_ATTR_ALGID, value: hinfo->alg_id)) |
308 | return -1; |
309 | |
310 | return 0; |
311 | } |
312 | |
313 | static int __seg6_genl_dumphmac_element(struct seg6_hmac_info *hinfo, |
314 | u32 portid, u32 seq, u32 flags, |
315 | struct sk_buff *skb, u8 cmd) |
316 | { |
317 | void *hdr; |
318 | |
319 | hdr = genlmsg_put(skb, portid, seq, family: &seg6_genl_family, flags, cmd); |
320 | if (!hdr) |
321 | return -ENOMEM; |
322 | |
323 | if (__seg6_hmac_fill_info(hinfo, msg: skb) < 0) |
324 | goto nla_put_failure; |
325 | |
326 | genlmsg_end(skb, hdr); |
327 | return 0; |
328 | |
329 | nla_put_failure: |
330 | genlmsg_cancel(skb, hdr); |
331 | return -EMSGSIZE; |
332 | } |
333 | |
334 | static int seg6_genl_dumphmac_start(struct netlink_callback *cb) |
335 | { |
336 | struct net *net = sock_net(sk: cb->skb->sk); |
337 | struct seg6_pernet_data *sdata; |
338 | struct rhashtable_iter *iter; |
339 | |
340 | sdata = seg6_pernet(net); |
341 | iter = (struct rhashtable_iter *)cb->args[0]; |
342 | |
343 | if (!iter) { |
344 | iter = kmalloc(size: sizeof(*iter), GFP_KERNEL); |
345 | if (!iter) |
346 | return -ENOMEM; |
347 | |
348 | cb->args[0] = (long)iter; |
349 | } |
350 | |
351 | rhashtable_walk_enter(ht: &sdata->hmac_infos, iter); |
352 | |
353 | return 0; |
354 | } |
355 | |
356 | static int seg6_genl_dumphmac_done(struct netlink_callback *cb) |
357 | { |
358 | struct rhashtable_iter *iter = (struct rhashtable_iter *)cb->args[0]; |
359 | |
360 | rhashtable_walk_exit(iter); |
361 | |
362 | kfree(objp: iter); |
363 | |
364 | return 0; |
365 | } |
366 | |
367 | static int seg6_genl_dumphmac(struct sk_buff *skb, struct netlink_callback *cb) |
368 | { |
369 | struct rhashtable_iter *iter = (struct rhashtable_iter *)cb->args[0]; |
370 | struct seg6_hmac_info *hinfo; |
371 | int ret; |
372 | |
373 | rhashtable_walk_start(iter); |
374 | |
375 | for (;;) { |
376 | hinfo = rhashtable_walk_next(iter); |
377 | |
378 | if (IS_ERR(ptr: hinfo)) { |
379 | if (PTR_ERR(ptr: hinfo) == -EAGAIN) |
380 | continue; |
381 | ret = PTR_ERR(ptr: hinfo); |
382 | goto done; |
383 | } else if (!hinfo) { |
384 | break; |
385 | } |
386 | |
387 | ret = __seg6_genl_dumphmac_element(hinfo, |
388 | NETLINK_CB(cb->skb).portid, |
389 | seq: cb->nlh->nlmsg_seq, |
390 | NLM_F_MULTI, |
391 | skb, cmd: SEG6_CMD_DUMPHMAC); |
392 | if (ret) |
393 | goto done; |
394 | } |
395 | |
396 | ret = skb->len; |
397 | |
398 | done: |
399 | rhashtable_walk_stop(iter); |
400 | return ret; |
401 | } |
402 | |
403 | #else |
404 | |
405 | static int seg6_genl_dumphmac_start(struct netlink_callback *cb) |
406 | { |
407 | return 0; |
408 | } |
409 | |
410 | static int seg6_genl_dumphmac_done(struct netlink_callback *cb) |
411 | { |
412 | return 0; |
413 | } |
414 | |
415 | static int seg6_genl_dumphmac(struct sk_buff *skb, struct netlink_callback *cb) |
416 | { |
417 | return -ENOTSUPP; |
418 | } |
419 | |
420 | #endif |
421 | |
422 | static int __net_init seg6_net_init(struct net *net) |
423 | { |
424 | struct seg6_pernet_data *sdata; |
425 | |
426 | sdata = kzalloc(size: sizeof(*sdata), GFP_KERNEL); |
427 | if (!sdata) |
428 | return -ENOMEM; |
429 | |
430 | mutex_init(&sdata->lock); |
431 | |
432 | sdata->tun_src = kzalloc(size: sizeof(*sdata->tun_src), GFP_KERNEL); |
433 | if (!sdata->tun_src) { |
434 | kfree(objp: sdata); |
435 | return -ENOMEM; |
436 | } |
437 | |
438 | net->ipv6.seg6_data = sdata; |
439 | |
440 | #ifdef CONFIG_IPV6_SEG6_HMAC |
441 | if (seg6_hmac_net_init(net)) { |
442 | kfree(rcu_dereference_raw(sdata->tun_src)); |
443 | kfree(objp: sdata); |
444 | return -ENOMEM; |
445 | } |
446 | #endif |
447 | |
448 | return 0; |
449 | } |
450 | |
451 | static void __net_exit seg6_net_exit(struct net *net) |
452 | { |
453 | struct seg6_pernet_data *sdata = seg6_pernet(net); |
454 | |
455 | #ifdef CONFIG_IPV6_SEG6_HMAC |
456 | seg6_hmac_net_exit(net); |
457 | #endif |
458 | |
459 | kfree(rcu_dereference_raw(sdata->tun_src)); |
460 | kfree(objp: sdata); |
461 | } |
462 | |
463 | static struct pernet_operations ip6_segments_ops = { |
464 | .init = seg6_net_init, |
465 | .exit = seg6_net_exit, |
466 | }; |
467 | |
468 | static const struct genl_ops seg6_genl_ops[] = { |
469 | { |
470 | .cmd = SEG6_CMD_SETHMAC, |
471 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
472 | .doit = seg6_genl_sethmac, |
473 | .flags = GENL_ADMIN_PERM, |
474 | }, |
475 | { |
476 | .cmd = SEG6_CMD_DUMPHMAC, |
477 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
478 | .start = seg6_genl_dumphmac_start, |
479 | .dumpit = seg6_genl_dumphmac, |
480 | .done = seg6_genl_dumphmac_done, |
481 | .flags = GENL_ADMIN_PERM, |
482 | }, |
483 | { |
484 | .cmd = SEG6_CMD_SET_TUNSRC, |
485 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
486 | .doit = seg6_genl_set_tunsrc, |
487 | .flags = GENL_ADMIN_PERM, |
488 | }, |
489 | { |
490 | .cmd = SEG6_CMD_GET_TUNSRC, |
491 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
492 | .doit = seg6_genl_get_tunsrc, |
493 | .flags = GENL_ADMIN_PERM, |
494 | }, |
495 | }; |
496 | |
497 | static struct genl_family seg6_genl_family __ro_after_init = { |
498 | .hdrsize = 0, |
499 | .name = SEG6_GENL_NAME, |
500 | .version = SEG6_GENL_VERSION, |
501 | .maxattr = SEG6_ATTR_MAX, |
502 | .policy = seg6_genl_policy, |
503 | .netnsok = true, |
504 | .parallel_ops = true, |
505 | .ops = seg6_genl_ops, |
506 | .n_ops = ARRAY_SIZE(seg6_genl_ops), |
507 | .resv_start_op = SEG6_CMD_GET_TUNSRC + 1, |
508 | .module = THIS_MODULE, |
509 | }; |
510 | |
511 | int __init seg6_init(void) |
512 | { |
513 | int err; |
514 | |
515 | err = genl_register_family(family: &seg6_genl_family); |
516 | if (err) |
517 | goto out; |
518 | |
519 | err = register_pernet_subsys(&ip6_segments_ops); |
520 | if (err) |
521 | goto out_unregister_genl; |
522 | |
523 | #ifdef CONFIG_IPV6_SEG6_LWTUNNEL |
524 | err = seg6_iptunnel_init(); |
525 | if (err) |
526 | goto out_unregister_pernet; |
527 | |
528 | err = seg6_local_init(); |
529 | if (err) |
530 | goto out_unregister_pernet; |
531 | #endif |
532 | |
533 | #ifdef CONFIG_IPV6_SEG6_HMAC |
534 | err = seg6_hmac_init(); |
535 | if (err) |
536 | goto out_unregister_iptun; |
537 | #endif |
538 | |
539 | pr_info("Segment Routing with IPv6\n" ); |
540 | |
541 | out: |
542 | return err; |
543 | #ifdef CONFIG_IPV6_SEG6_HMAC |
544 | out_unregister_iptun: |
545 | #ifdef CONFIG_IPV6_SEG6_LWTUNNEL |
546 | seg6_local_exit(); |
547 | seg6_iptunnel_exit(); |
548 | #endif |
549 | #endif |
550 | #ifdef CONFIG_IPV6_SEG6_LWTUNNEL |
551 | out_unregister_pernet: |
552 | unregister_pernet_subsys(&ip6_segments_ops); |
553 | #endif |
554 | out_unregister_genl: |
555 | genl_unregister_family(family: &seg6_genl_family); |
556 | goto out; |
557 | } |
558 | |
559 | void seg6_exit(void) |
560 | { |
561 | #ifdef CONFIG_IPV6_SEG6_HMAC |
562 | seg6_hmac_exit(); |
563 | #endif |
564 | #ifdef CONFIG_IPV6_SEG6_LWTUNNEL |
565 | seg6_iptunnel_exit(); |
566 | #endif |
567 | unregister_pernet_subsys(&ip6_segments_ops); |
568 | genl_unregister_family(family: &seg6_genl_family); |
569 | } |
570 | |