]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-ndisc.c
sd-ndisc: clean up state enum
[thirdparty/systemd.git] / src / libsystemd-network / sd-ndisc.c
1 /***
2 This file is part of systemd.
3
4 Copyright (C) 2014 Intel Corporation. All rights reserved.
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <netinet/icmp6.h>
21 #include <netinet/ip6.h>
22 #include <string.h>
23 #include <stdbool.h>
24 #include <netinet/in.h>
25 #include <sys/ioctl.h>
26
27 #include "async.h"
28 #include "list.h"
29 #include "socket-util.h"
30
31 #include "icmp6-util.h"
32 #include "sd-ndisc.h"
33
34 #define NDISC_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC
35 #define NDISC_MAX_ROUTER_SOLICITATIONS 3
36
37 enum NDiscState {
38 NDISC_STATE_IDLE,
39 NDISC_STATE_SOLICITATION_SENT,
40 NDISC_STATE_ADVERTISMENT_LISTEN,
41 _NDISC_STATE_MAX,
42 _NDISC_STATE_INVALID = -1,
43 };
44
45 #define IP6_MIN_MTU (unsigned)1280
46 #define ICMP6_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
47 #define NDISC_OPT_LEN_UNITS 8
48
49 typedef struct NDiscPrefix NDiscPrefix;
50
51 struct NDiscPrefix {
52 unsigned n_ref;
53
54 LIST_FIELDS(NDiscPrefix, prefixes);
55
56 uint8_t len;
57 sd_event_source *timeout_valid;
58 struct in6_addr addr;
59 };
60
61 struct sd_ndisc {
62 unsigned n_ref;
63
64 enum NDiscState state;
65 sd_event *event;
66 int event_priority;
67 int index;
68 struct ether_addr mac_addr;
69 uint32_t mtu;
70 NDiscPrefix *expired_prefix;
71 LIST_HEAD(NDiscPrefix, prefixes);
72 int fd;
73 sd_event_source *recv;
74 sd_event_source *timeout;
75 int nd_sent;
76 sd_ndisc_callback_t callback;
77 void *userdata;
78 };
79
80 #define log_ndisc(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "NDisc CLIENT: " fmt, ##__VA_ARGS__)
81
82 static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) {
83
84 if (!prefix)
85 return NULL;
86
87 assert(prefix->n_ref > 0);
88 prefix->n_ref--;
89
90 if (prefix->n_ref > 0)
91 return NULL;
92
93 prefix->timeout_valid = sd_event_source_unref(prefix->timeout_valid);
94 free(prefix);
95 return NULL;
96 }
97
98 static int ndisc_prefix_new(NDiscPrefix **ret) {
99 _cleanup_free_ NDiscPrefix *prefix = NULL;
100
101 assert(ret);
102
103 prefix = new0(NDiscPrefix, 1);
104 if (!prefix)
105 return -ENOMEM;
106
107 prefix->n_ref = 1;
108 LIST_INIT(prefixes, prefix);
109
110 *ret = prefix;
111 prefix = NULL;
112
113 return 0;
114 }
115
116 static void ndisc_notify(sd_ndisc *nd, int event) {
117 if (nd->callback)
118 nd->callback(nd, event, nd->userdata);
119 }
120
121 int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t callback,
122 void *userdata) {
123 assert(nd);
124
125 nd->callback = callback;
126 nd->userdata = userdata;
127
128 return 0;
129 }
130
131 int sd_ndisc_set_index(sd_ndisc *nd, int interface_index) {
132 assert(nd);
133 assert(interface_index >= -1);
134
135 nd->index = interface_index;
136
137 return 0;
138 }
139
140 int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
141 assert(nd);
142
143 if (mac_addr)
144 memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
145 else
146 zero(nd->mac_addr);
147
148 return 0;
149
150 }
151
152 int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int priority) {
153 int r;
154
155 assert_return(nd, -EINVAL);
156 assert_return(!nd->event, -EBUSY);
157
158 if (event)
159 nd->event = sd_event_ref(event);
160 else {
161 r = sd_event_default(&nd->event);
162 if (r < 0)
163 return 0;
164 }
165
166 nd->event_priority = priority;
167
168 return 0;
169 }
170
171 int sd_ndisc_detach_event(sd_ndisc *nd) {
172 assert_return(nd, -EINVAL);
173
174 nd->event = sd_event_unref(nd->event);
175
176 return 0;
177 }
178
179 sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
180 assert(nd);
181
182 return nd->event;
183 }
184
185 sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
186
187 if (!nd)
188 return NULL;
189
190 assert(nd->n_ref > 0);
191 nd->n_ref++;
192
193 return nd;
194 }
195
196 static int ndisc_init(sd_ndisc *nd) {
197 assert(nd);
198
199 nd->recv = sd_event_source_unref(nd->recv);
200 nd->fd = asynchronous_close(nd->fd);
201 nd->timeout = sd_event_source_unref(nd->timeout);
202
203 return 0;
204 }
205
206 sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
207 NDiscPrefix *prefix, *p;
208
209 if (!nd)
210 return NULL;
211
212 assert(nd->n_ref > 0);
213 nd->n_ref--;
214
215 if (nd->n_ref > 0)
216 return NULL;
217
218 ndisc_init(nd);
219 sd_ndisc_detach_event(nd);
220
221 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
222 LIST_REMOVE(prefixes, nd->prefixes, prefix);
223
224 prefix = ndisc_prefix_unref(prefix);
225 }
226
227 free(nd);
228
229 return NULL;
230 }
231
232 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ndisc*, sd_ndisc_unref);
233 #define _cleanup_sd_ndisc_free_ _cleanup_(sd_ndisc_unrefp)
234
235 int sd_ndisc_new(sd_ndisc **ret) {
236 _cleanup_sd_ndisc_free_ sd_ndisc *nd = NULL;
237
238 assert(ret);
239
240 nd = new0(sd_ndisc, 1);
241 if (!nd)
242 return -ENOMEM;
243
244 nd->n_ref = 1;
245
246 nd->index = -1;
247 nd->fd = -1;
248
249 LIST_HEAD_INIT(nd->prefixes);
250
251 *ret = nd;
252 nd = NULL;
253
254 return 0;
255 }
256
257 int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
258 assert_return(nd, -EINVAL);
259 assert_return(mtu, -EINVAL);
260
261 if (nd->mtu == 0)
262 return -ENOMSG;
263
264 *mtu = nd->mtu;
265
266 return 0;
267 }
268
269 static int ndisc_prefix_timeout(sd_event_source *s, uint64_t usec,
270 void *userdata) {
271 sd_ndisc *nd = userdata;
272 NDiscPrefix *prefix, *p;
273
274 assert(nd);
275
276 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
277 if (prefix->timeout_valid != s)
278 continue;
279
280 log_ndisc(nd, "Prefix expired "SD_NDISC_ADDRESS_FORMAT_STR"/%d",
281 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
282 prefix->len);
283
284 LIST_REMOVE(prefixes, nd->prefixes, prefix);
285
286 nd->expired_prefix = prefix;
287 ndisc_notify(nd,
288 SD_NDISC_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED);
289 nd->expired_prefix = NULL;
290
291 prefix = ndisc_prefix_unref(prefix);
292
293 break;
294 }
295
296 return 0;
297 }
298
299 static int ndisc_prefix_set_timeout(sd_ndisc *nd,
300 NDiscPrefix *prefix,
301 usec_t valid) {
302 usec_t time_now;
303 int r;
304
305 assert_return(prefix, -EINVAL);
306
307 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
308 if (r < 0)
309 return r;
310
311 prefix->timeout_valid = sd_event_source_unref(prefix->timeout_valid);
312
313 r = sd_event_add_time(nd->event, &prefix->timeout_valid,
314 clock_boottime_or_monotonic(), time_now + valid,
315 USEC_PER_SEC, ndisc_prefix_timeout, nd);
316 if (r < 0)
317 goto error;
318
319 r = sd_event_source_set_priority(prefix->timeout_valid,
320 nd->event_priority);
321 if (r < 0)
322 goto error;
323
324 r = sd_event_source_set_description(prefix->timeout_valid,
325 "ndisc-prefix-timeout");
326
327 error:
328 if (r < 0)
329 prefix->timeout_valid =
330 sd_event_source_unref(prefix->timeout_valid);
331
332 return r;
333 }
334
335 static int prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
336 const struct in6_addr *addr,
337 uint8_t addr_prefixlen) {
338 uint8_t bytes, mask, len;
339
340 assert_return(prefix, -EINVAL);
341 assert_return(addr, -EINVAL);
342
343 len = MIN(prefixlen, addr_prefixlen);
344
345 bytes = len / 8;
346 mask = 0xff << (8 - len % 8);
347
348 if (memcmp(prefix, addr, bytes) != 0 ||
349 (prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask))
350 return -EADDRNOTAVAIL;
351
352 return 0;
353 }
354
355 static int ndisc_prefix_match(NDiscPrefix *head, const struct in6_addr *addr,
356 uint8_t addr_len, NDiscPrefix **result) {
357 NDiscPrefix *prefix;
358
359 LIST_FOREACH(prefixes, prefix, head) {
360 if (prefix_match(&prefix->addr, prefix->len, addr, addr_len) >= 0) {
361 *result = prefix;
362 return 0;
363 }
364 }
365
366 return -EADDRNOTAVAIL;
367 }
368
369 int sd_ndisc_prefix_match(struct in6_addr *prefix, uint8_t prefixlen,
370 struct in6_addr *addr) {
371 return prefix_match(prefix, prefixlen, addr, sizeof(addr->s6_addr) * 8);
372 }
373
374 int sd_ndisc_get_prefixlen(sd_ndisc *nd, const struct in6_addr *addr,
375 uint8_t *prefixlen) {
376 int r;
377 NDiscPrefix *prefix;
378
379 assert_return(nd, -EINVAL);
380 assert_return(addr, -EINVAL);
381 assert_return(prefixlen, -EINVAL);
382
383 r = ndisc_prefix_match(nd->prefixes, addr,
384 sizeof(addr->s6_addr) * 8, &prefix);
385 if (r < 0)
386 return r;
387
388 *prefixlen = prefix->len;
389
390 return 0;
391 }
392
393 int sd_ndisc_get_expired_prefix(sd_ndisc *nd, struct in6_addr **addr, uint8_t *prefixlen) {
394 assert_return(nd, -EINVAL);
395 assert_return(addr, -EINVAL);
396 assert_return(prefixlen, -EINVAL);
397
398 if (!nd->expired_prefix)
399 return -EADDRNOTAVAIL;
400
401 *addr = &nd->expired_prefix->addr;
402 *prefixlen = nd->expired_prefix->len;
403
404 return 0;
405 }
406
407 static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
408 const struct nd_opt_prefix_info *prefix_opt) {
409 int r;
410 NDiscPrefix *prefix;
411 uint32_t lifetime;
412 char time_string[FORMAT_TIMESPAN_MAX];
413
414 assert_return(nd, -EINVAL);
415 assert_return(prefix_opt, -EINVAL);
416
417 if (len < prefix_opt->nd_opt_pi_len)
418 return -ENOMSG;
419
420 if (!(prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK))
421 return 0;
422
423 lifetime = be32toh(prefix_opt->nd_opt_pi_valid_time);
424
425 r = ndisc_prefix_match(nd->prefixes,
426 &prefix_opt->nd_opt_pi_prefix,
427 prefix_opt->nd_opt_pi_prefix_len, &prefix);
428
429 if (r < 0 && r != -EADDRNOTAVAIL)
430 return r;
431
432 /* if router advertisment prefix valid timeout is zero, the timeout
433 callback will be called immediately to clean up the prefix */
434
435 if (r == -EADDRNOTAVAIL) {
436 r = ndisc_prefix_new(&prefix);
437 if (r < 0)
438 return r;
439
440 prefix->len = prefix_opt->nd_opt_pi_prefix_len;
441
442 memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
443 sizeof(prefix->addr));
444
445 log_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
446 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
447 prefix->len, lifetime,
448 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime * USEC_PER_SEC, USEC_PER_SEC));
449
450 LIST_PREPEND(prefixes, nd->prefixes, prefix);
451
452 } else {
453 if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
454 uint8_t prefixlen;
455
456 prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
457
458 log_ndisc(nd, "Prefix length mismatch %d/%d using %d",
459 prefix->len,
460 prefix_opt->nd_opt_pi_prefix_len,
461 prefixlen);
462
463 prefix->len = prefixlen;
464 }
465
466 log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
467 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
468 prefix->len, lifetime,
469 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime * USEC_PER_SEC, USEC_PER_SEC));
470 }
471
472 r = ndisc_prefix_set_timeout(nd, prefix, lifetime * USEC_PER_SEC);
473
474 return r;
475 }
476
477 static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra,
478 ssize_t len) {
479 void *opt;
480 struct nd_opt_hdr *opt_hdr;
481
482 assert_return(nd, -EINVAL);
483 assert_return(ra, -EINVAL);
484
485 len -= sizeof(*ra);
486 if (len < NDISC_OPT_LEN_UNITS) {
487 log_ndisc(nd, "Router Advertisement below minimum length");
488
489 return -ENOMSG;
490 }
491
492 opt = ra + 1;
493 opt_hdr = opt;
494
495 while (len != 0 && len >= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS) {
496 struct nd_opt_mtu *opt_mtu;
497 uint32_t mtu;
498 struct nd_opt_prefix_info *opt_prefix;
499
500 if (opt_hdr->nd_opt_len == 0)
501 return -ENOMSG;
502
503 switch (opt_hdr->nd_opt_type) {
504 case ND_OPT_MTU:
505 opt_mtu = opt;
506
507 mtu = be32toh(opt_mtu->nd_opt_mtu_mtu);
508
509 if (mtu != nd->mtu) {
510 nd->mtu = MAX(mtu, IP6_MIN_MTU);
511
512 log_ndisc(nd, "Router Advertisement link MTU %d using %d",
513 mtu, nd->mtu);
514 }
515
516 break;
517
518 case ND_OPT_PREFIX_INFORMATION:
519 opt_prefix = opt;
520
521 ndisc_prefix_update(nd, len, opt_prefix);
522
523 break;
524 }
525
526 len -= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS;
527 opt = (void *)((char *)opt +
528 opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS);
529 opt_hdr = opt;
530 }
531
532 if (len > 0)
533 log_ndisc(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
534
535 return 0;
536 }
537
538 static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
539 sd_ndisc *nd = userdata;
540 int r, buflen = 0;
541 ssize_t len;
542 _cleanup_free_ struct nd_router_advert *ra = NULL;
543 int event = SD_NDISC_EVENT_ROUTER_ADVERTISMENT_NONE;
544
545 assert(s);
546 assert(nd);
547 assert(nd->event);
548
549 r = ioctl(fd, FIONREAD, &buflen);
550 if (r < 0 || buflen <= 0)
551 buflen = ICMP6_RECV_SIZE;
552
553 ra = malloc(buflen);
554 if (!ra)
555 return -ENOMEM;
556
557 len = read(fd, ra, buflen);
558 if (len < 0) {
559 log_ndisc(nd, "Could not receive message from UDP socket: %m");
560 return 0;
561 }
562
563 if (ra->nd_ra_type != ND_ROUTER_ADVERT)
564 return 0;
565
566 if (ra->nd_ra_code != 0)
567 return 0;
568
569 nd->timeout = sd_event_source_unref(nd->timeout);
570
571 nd->state = NDISC_STATE_ADVERTISMENT_LISTEN;
572
573 if (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
574 event = SD_NDISC_EVENT_ROUTER_ADVERTISMENT_OTHER;
575
576 if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
577 event = SD_NDISC_EVENT_ROUTER_ADVERTISMENT_MANAGED;
578
579 log_ndisc(nd, "Received Router Advertisement flags %s/%s",
580 ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED? "MANAGED": "none",
581 ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER? "OTHER": "none");
582
583 if (event != SD_NDISC_EVENT_ROUTER_ADVERTISMENT_NONE) {
584 r = ndisc_ra_parse(nd, ra, len);
585 if (r < 0) {
586 log_ndisc(nd, "Could not parse Router Advertisement: %s",
587 strerror(-r));
588 return 0;
589 }
590 }
591
592 ndisc_notify(nd, event);
593
594 return 0;
595 }
596
597 static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
598 sd_ndisc *nd = userdata;
599 uint64_t time_now, next_timeout;
600 struct ether_addr unset = { };
601 struct ether_addr *addr = NULL;
602 int r;
603
604 assert(s);
605 assert(nd);
606 assert(nd->event);
607
608 nd->timeout = sd_event_source_unref(nd->timeout);
609
610 if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
611 ndisc_notify(nd, SD_NDISC_EVENT_ROUTER_ADVERTISMENT_TIMEOUT);
612 nd->state = NDISC_STATE_ADVERTISMENT_LISTEN;
613 } else {
614 if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr)))
615 addr = &nd->mac_addr;
616
617 r = icmp6_send_router_solicitation(nd->fd, addr);
618 if (r < 0)
619 log_ndisc(nd, "Error sending Router Solicitation");
620 else {
621 nd->state = NDISC_STATE_SOLICITATION_SENT;
622 log_ndisc(nd, "Sent Router Solicitation");
623 }
624
625 nd->nd_sent++;
626
627 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
628 if (r < 0) {
629 ndisc_notify(nd, r);
630 return 0;
631 }
632
633 next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
634
635 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
636 next_timeout, 0,
637 ndisc_router_solicitation_timeout, nd);
638 if (r < 0) {
639 ndisc_notify(nd, r);
640 return 0;
641 }
642
643 r = sd_event_source_set_priority(nd->timeout,
644 nd->event_priority);
645 if (r < 0) {
646 ndisc_notify(nd, r);
647 return 0;
648 }
649
650 r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
651 if (r < 0) {
652 ndisc_notify(nd, r);
653 return 0;
654 }
655 }
656
657 return 0;
658 }
659
660 int sd_ndisc_stop(sd_ndisc *nd) {
661 assert_return(nd, -EINVAL);
662 assert_return(nd->event, -EINVAL);
663
664 log_ndisc(client, "Stop NDisc");
665
666 ndisc_init(nd);
667
668 nd->state = NDISC_STATE_IDLE;
669
670 return 0;
671 }
672
673 int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
674 int r;
675
676 assert(nd);
677 assert(nd->event);
678
679 if (nd->state != NDISC_STATE_IDLE)
680 return -EINVAL;
681
682 if (nd->index < 1)
683 return -EINVAL;
684
685 r = icmp6_bind_router_solicitation(nd->index);
686 if (r < 0)
687 return r;
688
689 nd->fd = r;
690
691 r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN,
692 ndisc_router_advertisment_recv, nd);
693 if (r < 0)
694 goto error;
695
696 r = sd_event_source_set_priority(nd->recv, nd->event_priority);
697 if (r < 0)
698 goto error;
699
700 r = sd_event_source_set_description(nd->recv, "ndisc-receive-message");
701 if (r < 0)
702 goto error;
703
704 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
705 0, 0, ndisc_router_solicitation_timeout, nd);
706 if (r < 0)
707 goto error;
708
709 r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
710 if (r < 0)
711 goto error;
712
713 r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
714 error:
715 if (r < 0)
716 ndisc_init(nd);
717 else
718 log_ndisc(client, "Start Router Solicitation");
719
720 return r;
721 }