]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-ndisc.c
Merge pull request #1934 from martinpitt/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 4 * USEC_PER_SEC
38 #define NDISC_MAX_ROUTER_SOLICITATIONS 3
39
40 enum NDiscState {
41 NDISC_STATE_IDLE,
42 NDISC_STATE_SOLICITATION_SENT,
43 NDISC_STATE_ADVERTISMENT_LISTEN,
44 _NDISC_STATE_MAX,
45 _NDISC_STATE_INVALID = -1,
46 };
47
48 #define IP6_MIN_MTU (unsigned)1280
49 #define ICMP6_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
50 #define NDISC_OPT_LEN_UNITS 8
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 sd_event *event;
77 int event_priority;
78 int index;
79 struct ether_addr mac_addr;
80 uint32_t mtu;
81 LIST_HEAD(NDiscPrefix, prefixes);
82 int fd;
83 sd_event_source *recv;
84 sd_event_source *timeout;
85 int nd_sent;
86 sd_ndisc_router_callback_t router_callback;
87 sd_ndisc_prefix_autonomous_callback_t prefix_autonomous_callback;
88 sd_ndisc_prefix_onlink_callback_t prefix_onlink_callback;
89 sd_ndisc_callback_t callback;
90 void *userdata;
91 };
92
93 #define log_ndisc(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "NDisc CLIENT: " fmt, ##__VA_ARGS__)
94
95 static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) {
96
97 if (!prefix)
98 return NULL;
99
100 assert(prefix->n_ref > 0);
101 prefix->n_ref--;
102
103 if (prefix->n_ref > 0)
104 return NULL;
105
106 if (prefix->nd)
107 LIST_REMOVE(prefixes, prefix->nd->prefixes, prefix);
108
109 free(prefix);
110
111 return NULL;
112 }
113
114 static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) {
115 _cleanup_free_ NDiscPrefix *prefix = NULL;
116
117 assert(ret);
118
119 prefix = new0(NDiscPrefix, 1);
120 if (!prefix)
121 return -ENOMEM;
122
123 prefix->n_ref = 1;
124 LIST_INIT(prefixes, prefix);
125 prefix->nd = nd;
126
127 *ret = prefix;
128 prefix = NULL;
129
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
318 continue;
319 }
320
321 if (prefix_match(&prefix->addr, prefix->len, addr, addr_len) >= 0) {
322 *result = prefix;
323 return 0;
324 }
325 }
326
327 return -EADDRNOTAVAIL;
328 }
329
330 static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
331 const struct nd_opt_prefix_info *prefix_opt) {
332 NDiscPrefix *prefix;
333 uint32_t lifetime_valid, lifetime_preferred;
334 usec_t time_now;
335 char time_string[FORMAT_TIMESPAN_MAX];
336 int r;
337
338 assert(nd);
339 assert(prefix_opt);
340
341 if (len < prefix_opt->nd_opt_pi_len)
342 return -ENOMSG;
343
344 if (!(prefix_opt->nd_opt_pi_flags_reserved & (ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO)))
345 return 0;
346
347 if (in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &prefix_opt->nd_opt_pi_prefix) > 0)
348 return 0;
349
350 lifetime_valid = be32toh(prefix_opt->nd_opt_pi_valid_time);
351 lifetime_preferred = be32toh(prefix_opt->nd_opt_pi_preferred_time);
352
353 if (lifetime_valid < lifetime_preferred)
354 return 0;
355
356 r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix,
357 prefix_opt->nd_opt_pi_prefix_len, &prefix);
358
359 if (r < 0 && r != -EADDRNOTAVAIL)
360 return r;
361
362 /* if router advertisment prefix valid timeout is zero, the timeout
363 callback will be called immediately to clean up the prefix */
364
365 if (r == -EADDRNOTAVAIL) {
366 r = ndisc_prefix_new(nd, &prefix);
367 if (r < 0)
368 return r;
369
370 prefix->len = prefix_opt->nd_opt_pi_prefix_len;
371
372 memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
373 sizeof(prefix->addr));
374
375 log_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
376 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
377 prefix->len, lifetime_valid,
378 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
379
380 LIST_PREPEND(prefixes, nd->prefixes, prefix);
381
382 } else {
383 if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
384 uint8_t prefixlen;
385
386 prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
387
388 log_ndisc(nd, "Prefix length mismatch %d/%d using %d",
389 prefix->len,
390 prefix_opt->nd_opt_pi_prefix_len,
391 prefixlen);
392
393 prefix->len = prefixlen;
394 }
395
396 log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
397 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
398 prefix->len, lifetime_valid,
399 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
400 }
401
402 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
403 if (r < 0)
404 return r;
405
406 prefix->valid_until = time_now + lifetime_valid * USEC_PER_SEC;
407
408 if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) && nd->prefix_onlink_callback)
409 nd->prefix_onlink_callback(nd, &prefix->addr, prefix->len, prefix->valid_until, nd->userdata);
410
411 if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) && nd->prefix_autonomous_callback)
412 nd->prefix_autonomous_callback(nd, &prefix->addr, prefix->len, lifetime_preferred, lifetime_valid,
413 nd->userdata);
414
415 return 0;
416 }
417
418 static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, ssize_t len) {
419 void *opt;
420 struct nd_opt_hdr *opt_hdr;
421
422 assert_return(nd, -EINVAL);
423 assert_return(ra, -EINVAL);
424
425 len -= sizeof(*ra);
426 if (len < NDISC_OPT_LEN_UNITS) {
427 log_ndisc(nd, "Router Advertisement below minimum length");
428
429 return -ENOMSG;
430 }
431
432 opt = ra + 1;
433 opt_hdr = opt;
434
435 while (len != 0 && len >= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS) {
436 struct nd_opt_mtu *opt_mtu;
437 uint32_t mtu;
438 struct nd_opt_prefix_info *opt_prefix;
439
440 if (opt_hdr->nd_opt_len == 0)
441 return -ENOMSG;
442
443 switch (opt_hdr->nd_opt_type) {
444 case ND_OPT_MTU:
445 opt_mtu = opt;
446
447 mtu = be32toh(opt_mtu->nd_opt_mtu_mtu);
448
449 if (mtu != nd->mtu) {
450 nd->mtu = MAX(mtu, IP6_MIN_MTU);
451
452 log_ndisc(nd, "Router Advertisement link MTU %d using %d",
453 mtu, nd->mtu);
454 }
455
456 break;
457
458 case ND_OPT_PREFIX_INFORMATION:
459 opt_prefix = opt;
460
461 ndisc_prefix_update(nd, len, opt_prefix);
462
463 break;
464 }
465
466 len -= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS;
467 opt = (void *)((char *)opt +
468 opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS);
469 opt_hdr = opt;
470 }
471
472 if (len > 0)
473 log_ndisc(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
474
475 return 0;
476 }
477
478 static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
479 _cleanup_free_ struct nd_router_advert *ra = NULL;
480 sd_ndisc *nd = userdata;
481 union {
482 struct cmsghdr cmsghdr;
483 uint8_t buf[CMSG_LEN(sizeof(int))];
484 } control = {};
485 struct iovec iov = {};
486 union sockaddr_union sa = {};
487 struct msghdr msg = {
488 .msg_name = &sa.sa,
489 .msg_namelen = sizeof(sa),
490 .msg_iov = &iov,
491 .msg_iovlen = 1,
492 .msg_control = &control,
493 .msg_controllen = sizeof(control),
494 };
495 struct cmsghdr *cmsg;
496 struct in6_addr *gw;
497 unsigned lifetime;
498 ssize_t len;
499 int r, pref, stateful, buflen = 0;
500
501 assert(s);
502 assert(nd);
503 assert(nd->event);
504
505 r = ioctl(fd, FIONREAD, &buflen);
506 if (r < 0)
507 return -errno;
508 else if (buflen < 0)
509 /* This really should not happen */
510 return -EIO;
511
512 iov.iov_len = buflen;
513
514 ra = malloc(iov.iov_len);
515 if (!ra)
516 return -ENOMEM;
517
518 iov.iov_base = ra;
519
520 len = recvmsg(fd, &msg, 0);
521 if (len < 0) {
522 if (errno == EAGAIN || errno == EINTR)
523 return 0;
524
525 log_ndisc(nd, "Could not receive message from ICMPv6 socket: %m");
526 return -errno;
527 } else if ((size_t)len < sizeof(struct nd_router_advert)) {
528 return 0;
529 } else if (msg.msg_namelen == 0)
530 gw = NULL; /* only happens when running the test-suite over a socketpair */
531 else if (msg.msg_namelen != sizeof(sa.in6)) {
532 log_ndisc(nd, "Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t)msg.msg_namelen);
533 return 0;
534 } else
535 gw = &sa.in6.sin6_addr;
536
537 assert(!(msg.msg_flags & MSG_CTRUNC));
538 assert(!(msg.msg_flags & MSG_TRUNC));
539
540 CMSG_FOREACH(cmsg, &msg) {
541 if (cmsg->cmsg_level == SOL_IPV6 &&
542 cmsg->cmsg_type == IPV6_HOPLIMIT &&
543 cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
544 int hops = *(int*)CMSG_DATA(cmsg);
545
546 if (hops != 255) {
547 log_ndisc(nd, "Received RA with invalid hop limit %d. Ignoring.", hops);
548 return 0;
549 }
550
551 break;
552 }
553 }
554
555 if (gw && !in_addr_is_link_local(AF_INET6, (const union in_addr_union*) gw)) {
556 _cleanup_free_ char *addr = NULL;
557
558 (void)in_addr_to_string(AF_INET6, (const union in_addr_union*) gw, &addr);
559
560 log_ndisc(nd, "Received RA from non-link-local address %s. Ignoring.", strna(addr));
561 return 0;
562 }
563
564 if (ra->nd_ra_type != ND_ROUTER_ADVERT)
565 return 0;
566
567 if (ra->nd_ra_code != 0)
568 return 0;
569
570 nd->timeout = sd_event_source_unref(nd->timeout);
571
572 nd->state = NDISC_STATE_ADVERTISMENT_LISTEN;
573
574 stateful = ra->nd_ra_flags_reserved & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER);
575 pref = (ra->nd_ra_flags_reserved & ND_RA_FLAG_PREF) >> 3;
576
577 switch (pref) {
578 case ND_RA_FLAG_PREF_LOW:
579 case ND_RA_FLAG_PREF_HIGH:
580 break;
581 default:
582 pref = ND_RA_FLAG_PREF_MEDIUM;
583 break;
584 }
585
586 lifetime = be16toh(ra->nd_ra_router_lifetime);
587
588 log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %u sec",
589 stateful & ND_RA_FLAG_MANAGED ? "MANAGED" : stateful & ND_RA_FLAG_OTHER ? "OTHER" : "none",
590 pref == ND_RA_FLAG_PREF_HIGH ? "high" : pref == ND_RA_FLAG_PREF_LOW ? "low" : "medium",
591 lifetime);
592
593 r = ndisc_ra_parse(nd, ra, len);
594 if (r < 0) {
595 log_ndisc(nd, "Could not parse Router Advertisement: %s", strerror(-r));
596 return 0;
597 }
598
599 if (nd->router_callback)
600 nd->router_callback(nd, stateful, gw, lifetime, pref, nd->userdata);
601
602 return 0;
603 }
604
605 static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
606 sd_ndisc *nd = userdata;
607 uint64_t time_now, next_timeout;
608 int r;
609
610 assert(s);
611 assert(nd);
612 assert(nd->event);
613
614 nd->timeout = sd_event_source_unref(nd->timeout);
615
616 if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
617 if (nd->callback)
618 nd->callback(nd, SD_NDISC_EVENT_TIMEOUT, nd->userdata);
619 nd->state = NDISC_STATE_ADVERTISMENT_LISTEN;
620 } else {
621 r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
622 if (r < 0)
623 log_ndisc(nd, "Error sending Router Solicitation");
624 else {
625 nd->state = NDISC_STATE_SOLICITATION_SENT;
626 log_ndisc(nd, "Sent Router Solicitation");
627 }
628
629 nd->nd_sent++;
630
631 assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
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 /* we cannot continue if we are unable to rearm the timer */
640 sd_ndisc_stop(nd);
641 return 0;
642 }
643
644 r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
645 if (r < 0)
646 return 0;
647
648 r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
649 if (r < 0)
650 return 0;
651 }
652
653 return 0;
654 }
655
656 int sd_ndisc_stop(sd_ndisc *nd) {
657 assert_return(nd, -EINVAL);
658 assert_return(nd->event, -EINVAL);
659
660 log_ndisc(client, "Stop NDisc");
661
662 ndisc_init(nd);
663
664 nd->state = NDISC_STATE_IDLE;
665
666 if (nd->callback)
667 nd->callback(nd, SD_NDISC_EVENT_STOP, nd->userdata);
668
669 return 0;
670 }
671
672 int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
673 int r;
674
675 assert(nd);
676 assert(nd->event);
677
678 if (nd->state != NDISC_STATE_IDLE)
679 return -EBUSY;
680
681 if (nd->index < 1)
682 return -EINVAL;
683
684 r = icmp6_bind_router_solicitation(nd->index);
685 if (r < 0)
686 return r;
687
688 nd->fd = r;
689
690 r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN,
691 ndisc_router_advertisment_recv, nd);
692 if (r < 0)
693 goto error;
694
695 r = sd_event_source_set_priority(nd->recv, nd->event_priority);
696 if (r < 0)
697 goto error;
698
699 r = sd_event_source_set_description(nd->recv, "ndisc-receive-message");
700 if (r < 0)
701 goto error;
702
703 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
704 0, 0, ndisc_router_solicitation_timeout, nd);
705 if (r < 0)
706 goto error;
707
708 r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
709 if (r < 0)
710 goto error;
711
712 r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
713 error:
714 if (r < 0)
715 ndisc_init(nd);
716 else
717 log_ndisc(client, "Start Router Solicitation");
718
719 return r;
720 }