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