]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-ndisc.c
Merge pull request #3360 from glaubitz/master
[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/in.h>
22 #include <netinet/ip6.h>
23 #include <stdbool.h>
24 #include <string.h>
25 #include <sys/ioctl.h>
26
27 #include "sd-ndisc.h"
28
29 #include "alloc-util.h"
30 #include "async.h"
31 #include "icmp6-util.h"
32 #include "in-addr-util.h"
33 #include "list.h"
34 #include "socket-util.h"
35 #include "string-util.h"
36
37 #define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
38 #define NDISC_MAX_ROUTER_SOLICITATIONS 3U
39
40 enum NDiscState {
41 NDISC_STATE_IDLE,
42 NDISC_STATE_SOLICITATION_SENT,
43 NDISC_STATE_ADVERTISEMENT_LISTEN,
44 _NDISC_STATE_MAX,
45 _NDISC_STATE_INVALID = -1,
46 };
47
48 #define IP6_MIN_MTU 1280U
49 #define ICMP6_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
50 #define NDISC_OPT_LEN_UNITS 8U
51
52 #define ND_RA_FLAG_PREF 0x18
53 #define ND_RA_FLAG_PREF_LOW 0x03
54 #define ND_RA_FLAG_PREF_MEDIUM 0x0
55 #define ND_RA_FLAG_PREF_HIGH 0x1
56 #define ND_RA_FLAG_PREF_INVALID 0x2
57
58 typedef struct NDiscPrefix NDiscPrefix;
59
60 struct NDiscPrefix {
61 unsigned n_ref;
62
63 sd_ndisc *nd;
64
65 LIST_FIELDS(NDiscPrefix, prefixes);
66
67 uint8_t len;
68 usec_t valid_until;
69 struct in6_addr addr;
70 };
71
72 struct sd_ndisc {
73 unsigned n_ref;
74
75 enum NDiscState state;
76 int ifindex;
77 int fd;
78
79 sd_event *event;
80 int event_priority;
81
82 struct ether_addr mac_addr;
83 uint32_t mtu;
84
85 LIST_HEAD(NDiscPrefix, prefixes);
86
87 sd_event_source *recv_event_source;
88 sd_event_source *timeout_event_source;
89
90 unsigned nd_sent;
91
92 sd_ndisc_router_callback_t router_callback;
93 sd_ndisc_prefix_autonomous_callback_t prefix_autonomous_callback;
94 sd_ndisc_prefix_onlink_callback_t prefix_onlink_callback;
95 sd_ndisc_callback_t callback;
96 void *userdata;
97 };
98
99 #define log_ndisc_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "NDisc CLIENT: " fmt, ##__VA_ARGS__)
100 #define log_ndisc(p, fmt, ...) log_ndisc_errno(p, 0, fmt, ##__VA_ARGS__)
101
102 static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) {
103
104 if (!prefix)
105 return NULL;
106
107 assert(prefix->n_ref > 0);
108 prefix->n_ref--;
109
110 if (prefix->n_ref > 0)
111 return NULL;
112
113 if (prefix->nd)
114 LIST_REMOVE(prefixes, prefix->nd->prefixes, prefix);
115
116 free(prefix);
117
118 return NULL;
119 }
120
121 static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) {
122 NDiscPrefix *prefix;
123
124 assert(ret);
125
126 prefix = new0(NDiscPrefix, 1);
127 if (!prefix)
128 return -ENOMEM;
129
130 prefix->n_ref = 1;
131 LIST_INIT(prefixes, prefix);
132 prefix->nd = nd;
133
134 *ret = prefix;
135 return 0;
136 }
137
138 int sd_ndisc_set_callback(
139 sd_ndisc *nd,
140 sd_ndisc_router_callback_t router_callback,
141 sd_ndisc_prefix_onlink_callback_t prefix_onlink_callback,
142 sd_ndisc_prefix_autonomous_callback_t prefix_autonomous_callback,
143 sd_ndisc_callback_t callback,
144 void *userdata) {
145
146 assert_return(nd, -EINVAL);
147
148 nd->router_callback = router_callback;
149 nd->prefix_onlink_callback = prefix_onlink_callback;
150 nd->prefix_autonomous_callback = prefix_autonomous_callback;
151 nd->callback = callback;
152 nd->userdata = userdata;
153
154 return 0;
155 }
156
157 int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
158 assert_return(nd, -EINVAL);
159 assert_return(ifindex > 0, -EINVAL);
160
161 nd->ifindex = ifindex;
162 return 0;
163 }
164
165 int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
166 assert_return(nd, -EINVAL);
167
168 if (mac_addr)
169 memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
170 else
171 zero(nd->mac_addr);
172
173 return 0;
174
175 }
176
177 int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
178 int r;
179
180 assert_return(nd, -EINVAL);
181 assert_return(!nd->event, -EBUSY);
182
183 if (event)
184 nd->event = sd_event_ref(event);
185 else {
186 r = sd_event_default(&nd->event);
187 if (r < 0)
188 return 0;
189 }
190
191 nd->event_priority = priority;
192
193 return 0;
194 }
195
196 int sd_ndisc_detach_event(sd_ndisc *nd) {
197 assert_return(nd, -EINVAL);
198
199 nd->event = sd_event_unref(nd->event);
200
201 return 0;
202 }
203
204 sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
205 assert_return(nd, NULL);
206
207 return nd->event;
208 }
209
210 sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
211
212 if (!nd)
213 return NULL;
214
215 assert(nd->n_ref > 0);
216 nd->n_ref++;
217
218 return nd;
219 }
220
221 static int ndisc_reset(sd_ndisc *nd) {
222 assert(nd);
223
224 nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
225 nd->fd = asynchronous_close(nd->fd);
226 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
227
228 return 0;
229 }
230
231 sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
232 NDiscPrefix *prefix, *p;
233
234 if (!nd)
235 return NULL;
236
237 assert(nd->n_ref > 0);
238 nd->n_ref--;
239
240 if (nd->n_ref > 0)
241 return NULL;
242
243 ndisc_reset(nd);
244 sd_ndisc_detach_event(nd);
245
246 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes)
247 prefix = ndisc_prefix_unref(prefix);
248
249 free(nd);
250
251 return NULL;
252 }
253
254 int sd_ndisc_new(sd_ndisc **ret) {
255 _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
256
257 assert_return(ret, -EINVAL);
258
259 nd = new0(sd_ndisc, 1);
260 if (!nd)
261 return -ENOMEM;
262
263 nd->n_ref = 1;
264 nd->ifindex = -1;
265 nd->fd = -1;
266
267 LIST_HEAD_INIT(nd->prefixes);
268
269 *ret = nd;
270 nd = NULL;
271
272 return 0;
273 }
274
275 int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
276 assert_return(nd, -EINVAL);
277 assert_return(mtu, -EINVAL);
278
279 if (nd->mtu == 0)
280 return -ENOMSG;
281
282 *mtu = nd->mtu;
283 return 0;
284 }
285
286 static int prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
287 const struct in6_addr *addr,
288 uint8_t addr_prefixlen) {
289 uint8_t bytes, mask, len;
290
291 assert(prefix);
292 assert(addr);
293
294 len = MIN(prefixlen, addr_prefixlen);
295
296 bytes = len / 8;
297 mask = 0xff << (8 - len % 8);
298
299 if (memcmp(prefix, addr, bytes) != 0 ||
300 (prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask))
301 return -EADDRNOTAVAIL;
302
303 return 0;
304 }
305
306 static int ndisc_prefix_match(sd_ndisc *nd, const struct in6_addr *addr,
307 uint8_t addr_len, NDiscPrefix **result) {
308 NDiscPrefix *prefix, *p;
309 usec_t time_now;
310 int r;
311
312 assert(nd);
313
314 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
315 if (r < 0)
316 return r;
317
318 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
319 if (prefix->valid_until < time_now) {
320 prefix = ndisc_prefix_unref(prefix);
321 continue;
322 }
323
324 if (prefix_match(&prefix->addr, prefix->len, addr, addr_len) >= 0) {
325 *result = prefix;
326 return 0;
327 }
328 }
329
330 return -EADDRNOTAVAIL;
331 }
332
333 static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
334 const struct nd_opt_prefix_info *prefix_opt) {
335 NDiscPrefix *prefix;
336 uint32_t lifetime_valid, lifetime_preferred;
337 usec_t time_now;
338 char time_string[FORMAT_TIMESPAN_MAX];
339 int r;
340
341 assert(nd);
342 assert(prefix_opt);
343
344 if (len < prefix_opt->nd_opt_pi_len)
345 return -EBADMSG;
346
347 if (!(prefix_opt->nd_opt_pi_flags_reserved & (ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO)))
348 return 0;
349
350 if (in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &prefix_opt->nd_opt_pi_prefix) > 0)
351 return 0;
352
353 lifetime_valid = be32toh(prefix_opt->nd_opt_pi_valid_time);
354 lifetime_preferred = be32toh(prefix_opt->nd_opt_pi_preferred_time);
355
356 if (lifetime_valid < lifetime_preferred)
357 return 0;
358
359 r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix,
360 prefix_opt->nd_opt_pi_prefix_len, &prefix);
361 if (r < 0) {
362 if (r != -EADDRNOTAVAIL)
363 return r;
364
365 /* if router advertisement prefix valid timeout is zero, the timeout
366 callback will be called immediately to clean up the prefix */
367
368 r = ndisc_prefix_new(nd, &prefix);
369 if (r < 0)
370 return r;
371
372 prefix->len = prefix_opt->nd_opt_pi_prefix_len;
373
374 memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
375 sizeof(prefix->addr));
376
377 log_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
378 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
379 prefix->len, lifetime_valid,
380 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
381
382 LIST_PREPEND(prefixes, nd->prefixes, prefix);
383
384 } else {
385 if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
386 uint8_t prefixlen;
387
388 prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
389
390 log_ndisc(nd, "Prefix length mismatch %d/%d using %d",
391 prefix->len,
392 prefix_opt->nd_opt_pi_prefix_len,
393 prefixlen);
394
395 prefix->len = prefixlen;
396 }
397
398 log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
399 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
400 prefix->len, lifetime_valid,
401 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
402 }
403
404 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
405 if (r < 0)
406 return r;
407
408 prefix->valid_until = time_now + lifetime_valid * USEC_PER_SEC;
409
410 if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) && nd->prefix_onlink_callback)
411 nd->prefix_onlink_callback(nd, &prefix->addr, prefix->len, prefix->valid_until, nd->userdata);
412
413 if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) && nd->prefix_autonomous_callback)
414 nd->prefix_autonomous_callback(nd, &prefix->addr, prefix->len, lifetime_preferred, lifetime_valid,
415 nd->userdata);
416
417 return 0;
418 }
419
420 static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, size_t len) {
421 struct nd_opt_hdr *opt_hdr;
422 void *opt;
423
424 assert(nd);
425 assert(ra);
426
427 if (len < sizeof(struct nd_router_advert) + NDISC_OPT_LEN_UNITS) {
428 log_ndisc(nd, "Router Advertisement below minimum length");
429 return -EBADMSG;
430 }
431
432 len -= sizeof(struct nd_router_advert);
433 opt = ra + 1;
434 opt_hdr = opt;
435
436 while (len != 0 && len >= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS) {
437 struct nd_opt_mtu *opt_mtu;
438 struct nd_opt_prefix_info *opt_prefix;
439 uint32_t mtu;
440
441 if (opt_hdr->nd_opt_len == 0)
442 return -EBADMSG;
443
444 switch (opt_hdr->nd_opt_type) {
445
446 case ND_OPT_MTU:
447 opt_mtu = opt;
448
449 mtu = be32toh(opt_mtu->nd_opt_mtu_mtu);
450
451 if (mtu != nd->mtu) {
452 nd->mtu = MAX(mtu, IP6_MIN_MTU);
453 log_ndisc(nd, "Router Advertisement link MTU %d using %d", mtu, nd->mtu);
454 }
455
456 break;
457
458 case ND_OPT_PREFIX_INFORMATION:
459 opt_prefix = opt;
460 ndisc_prefix_update(nd, len, opt_prefix);
461 break;
462 }
463
464 len -= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS;
465 opt = (void*) ((uint8_t*) opt + opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS);
466 opt_hdr = opt;
467 }
468
469 if (len > 0)
470 log_ndisc(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
471
472 return 0;
473 }
474
475 static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
476 _cleanup_free_ struct nd_router_advert *ra = NULL;
477 sd_ndisc *nd = userdata;
478 union {
479 struct cmsghdr cmsghdr;
480 uint8_t buf[CMSG_LEN(sizeof(int))];
481 } control = {};
482 struct iovec iov = {};
483 union sockaddr_union sa = {};
484 struct msghdr msg = {
485 .msg_name = &sa.sa,
486 .msg_namelen = sizeof(sa),
487 .msg_iov = &iov,
488 .msg_iovlen = 1,
489 .msg_control = &control,
490 .msg_controllen = sizeof(control),
491 };
492 struct cmsghdr *cmsg;
493 struct in6_addr *gw;
494 unsigned lifetime;
495 ssize_t len, buflen;
496 int r, pref, stateful;
497
498 assert(s);
499 assert(nd);
500 assert(nd->event);
501
502 buflen = next_datagram_size_fd(fd);
503 if (buflen < 0)
504 return buflen;
505
506 iov.iov_len = buflen;
507
508 ra = malloc(iov.iov_len);
509 if (!ra)
510 return -ENOMEM;
511
512 iov.iov_base = ra;
513
514 len = recvmsg(fd, &msg, 0);
515 if (len < 0) {
516 if (errno == EAGAIN || errno == EINTR)
517 return 0;
518
519 return log_ndisc_errno(nd, errno, "Could not receive message from ICMPv6 socket: %m");
520 }
521 if ((size_t) len < sizeof(struct nd_router_advert)) {
522 log_ndisc(nd, "Too small to be a router advertisement: ignoring");
523 return 0;
524 }
525
526 if (msg.msg_namelen == 0)
527 gw = NULL; /* only happens when running the test-suite over a socketpair */
528 else if (msg.msg_namelen != sizeof(sa.in6)) {
529 log_ndisc(nd, "Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t)msg.msg_namelen);
530 return 0;
531 } else
532 gw = &sa.in6.sin6_addr;
533
534 assert(!(msg.msg_flags & MSG_CTRUNC));
535 assert(!(msg.msg_flags & MSG_TRUNC));
536
537 CMSG_FOREACH(cmsg, &msg) {
538 if (cmsg->cmsg_level == SOL_IPV6 &&
539 cmsg->cmsg_type == IPV6_HOPLIMIT &&
540 cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
541 int hops = *(int*)CMSG_DATA(cmsg);
542
543 if (hops != 255) {
544 log_ndisc(nd, "Received RA with invalid hop limit %d. Ignoring.", hops);
545 return 0;
546 }
547
548 break;
549 }
550 }
551
552 if (gw && !in_addr_is_link_local(AF_INET6, (const union in_addr_union*) gw)) {
553 _cleanup_free_ char *addr = NULL;
554
555 (void)in_addr_to_string(AF_INET6, (const union in_addr_union*) gw, &addr);
556
557 log_ndisc(nd, "Received RA from non-link-local address %s. Ignoring.", strna(addr));
558 return 0;
559 }
560
561 if (ra->nd_ra_type != ND_ROUTER_ADVERT)
562 return 0;
563
564 if (ra->nd_ra_code != 0)
565 return 0;
566
567 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
568 nd->state = NDISC_STATE_ADVERTISEMENT_LISTEN;
569
570 stateful = ra->nd_ra_flags_reserved & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER);
571 pref = (ra->nd_ra_flags_reserved & ND_RA_FLAG_PREF) >> 3;
572
573 if (!IN_SET(pref, ND_RA_FLAG_PREF_LOW, ND_RA_FLAG_PREF_HIGH))
574 pref = ND_RA_FLAG_PREF_MEDIUM;
575
576 lifetime = be16toh(ra->nd_ra_router_lifetime);
577
578 log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %u sec",
579 stateful & ND_RA_FLAG_MANAGED ? "MANAGED" : stateful & ND_RA_FLAG_OTHER ? "OTHER" : "none",
580 pref == ND_RA_FLAG_PREF_HIGH ? "high" : pref == ND_RA_FLAG_PREF_LOW ? "low" : "medium",
581 lifetime);
582
583 r = ndisc_ra_parse(nd, ra, (size_t) len);
584 if (r < 0) {
585 log_ndisc_errno(nd, r, "Could not parse Router Advertisement: %m");
586 return 0;
587 }
588
589 if (nd->router_callback)
590 nd->router_callback(nd, stateful, gw, lifetime, pref, nd->userdata);
591
592 return 0;
593 }
594
595 static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
596 sd_ndisc *nd = userdata;
597 usec_t time_now, next_timeout;
598 int r;
599
600 assert(s);
601 assert(nd);
602 assert(nd->event);
603
604 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
605
606 if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
607 if (nd->callback)
608 nd->callback(nd, SD_NDISC_EVENT_TIMEOUT, nd->userdata);
609 nd->state = NDISC_STATE_ADVERTISEMENT_LISTEN;
610 } else {
611 r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
612 if (r < 0) {
613 log_ndisc_errno(nd, r, "Error sending Router Solicitation: %m");
614 goto fail;
615 } else {
616 nd->state = NDISC_STATE_SOLICITATION_SENT;
617 log_ndisc(nd, "Sent Router Solicitation");
618 }
619
620 nd->nd_sent++;
621
622 assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
623
624 next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
625
626 r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(),
627 next_timeout, 0,
628 ndisc_router_solicitation_timeout, nd);
629 if (r < 0) {
630 log_ndisc_errno(nd, r, "Failed to allocate timer event: %m");
631 goto fail;
632 }
633
634 r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
635 if (r < 0) {
636 log_ndisc_errno(nd, r, "Cannot set timer priority: %m");
637 goto fail;
638 }
639
640 (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
641 }
642
643 return 0;
644
645 fail:
646 sd_ndisc_stop(nd);
647 return 0;
648 }
649
650 int sd_ndisc_stop(sd_ndisc *nd) {
651 assert_return(nd, -EINVAL);
652
653 if (nd->state == NDISC_STATE_IDLE)
654 return 0;
655
656 log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
657
658 ndisc_reset(nd);
659 nd->state = NDISC_STATE_IDLE;
660
661 if (nd->callback)
662 nd->callback(nd, SD_NDISC_EVENT_STOP, nd->userdata);
663
664 return 0;
665 }
666
667 int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
668 int r;
669
670 assert_return(nd, -EINVAL);
671 assert_return(nd->event, -EINVAL);
672 assert_return(nd->ifindex > 0, -EINVAL);
673 assert_return(nd->state == NDISC_STATE_IDLE, -EBUSY);
674
675 r = icmp6_bind_router_solicitation(nd->ifindex);
676 if (r < 0)
677 return r;
678
679 nd->fd = r;
680
681 r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_router_advertisement_recv, nd);
682 if (r < 0)
683 goto fail;
684
685 r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
686 if (r < 0)
687 goto fail;
688
689 (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
690
691 r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_router_solicitation_timeout, nd);
692 if (r < 0)
693 goto fail;
694
695 r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
696 if (r < 0)
697 goto fail;
698
699 (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
700
701 log_ndisc(ns, "Started IPv6 Router Solicitation client");
702 return 0;
703
704 fail:
705 ndisc_reset(nd);
706 return r;
707 }