]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-ndisc.c
Merge pull request #1644 from reverendhomer/patch-1
[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 "socket-util.h"
28 #include "async.h"
29
30 #include "dhcp6-internal.h"
31 #include "sd-ndisc.h"
32
33 #define ICMP6_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC
34 #define ICMP6_MAX_ROUTER_SOLICITATIONS 3
35
36 enum icmp6_nd_state {
37 ICMP6_NEIGHBOR_DISCOVERY_IDLE = 0,
38 ICMP6_ROUTER_SOLICITATION_SENT = 10,
39 ICMP6_ROUTER_ADVERTISMENT_LISTEN = 11,
40 };
41
42 #define IP6_MIN_MTU (unsigned)1280
43 #define ICMP6_ND_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
44 #define ICMP6_OPT_LEN_UNITS 8
45
46 typedef struct ICMP6Prefix ICMP6Prefix;
47
48 struct ICMP6Prefix {
49 unsigned n_ref;
50
51 LIST_FIELDS(ICMP6Prefix, prefixes);
52
53 uint8_t len;
54 sd_event_source *timeout_valid;
55 struct in6_addr addr;
56 };
57
58 struct sd_icmp6_nd {
59 unsigned n_ref;
60
61 enum icmp6_nd_state state;
62 sd_event *event;
63 int event_priority;
64 int index;
65 struct ether_addr mac_addr;
66 uint32_t mtu;
67 ICMP6Prefix *expired_prefix;
68 LIST_HEAD(ICMP6Prefix, prefixes);
69 int fd;
70 sd_event_source *recv;
71 sd_event_source *timeout;
72 int nd_sent;
73 sd_icmp6_nd_callback_t callback;
74 void *userdata;
75 };
76
77 #define log_icmp6_nd(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "ICMPv6 CLIENT: " fmt, ##__VA_ARGS__)
78
79 static ICMP6Prefix *icmp6_prefix_unref(ICMP6Prefix *prefix) {
80
81 if (!prefix)
82 return NULL;
83
84 assert(prefix->n_ref > 0);
85 prefix->n_ref--;
86
87 if (prefix->n_ref > 0)
88 return NULL;
89
90 prefix->timeout_valid = sd_event_source_unref(prefix->timeout_valid);
91 free(prefix);
92 return NULL;
93 }
94
95 static int icmp6_prefix_new(ICMP6Prefix **ret) {
96 _cleanup_free_ ICMP6Prefix *prefix = NULL;
97
98 assert(ret);
99
100 prefix = new0(ICMP6Prefix, 1);
101 if (!prefix)
102 return -ENOMEM;
103
104 prefix->n_ref = 1;
105 LIST_INIT(prefixes, prefix);
106
107 *ret = prefix;
108 prefix = NULL;
109
110 return 0;
111 }
112
113 static void icmp6_nd_notify(sd_icmp6_nd *nd, int event) {
114 if (nd->callback)
115 nd->callback(nd, event, nd->userdata);
116 }
117
118 int sd_icmp6_nd_set_callback(sd_icmp6_nd *nd, sd_icmp6_nd_callback_t callback,
119 void *userdata) {
120 assert(nd);
121
122 nd->callback = callback;
123 nd->userdata = userdata;
124
125 return 0;
126 }
127
128 int sd_icmp6_nd_set_index(sd_icmp6_nd *nd, int interface_index) {
129 assert(nd);
130 assert(interface_index >= -1);
131
132 nd->index = interface_index;
133
134 return 0;
135 }
136
137 int sd_icmp6_nd_set_mac(sd_icmp6_nd *nd, const struct ether_addr *mac_addr) {
138 assert(nd);
139
140 if (mac_addr)
141 memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
142 else
143 zero(nd->mac_addr);
144
145 return 0;
146
147 }
148
149 int sd_icmp6_nd_attach_event(sd_icmp6_nd *nd, sd_event *event, int priority) {
150 int r;
151
152 assert_return(nd, -EINVAL);
153 assert_return(!nd->event, -EBUSY);
154
155 if (event)
156 nd->event = sd_event_ref(event);
157 else {
158 r = sd_event_default(&nd->event);
159 if (r < 0)
160 return 0;
161 }
162
163 nd->event_priority = priority;
164
165 return 0;
166 }
167
168 int sd_icmp6_nd_detach_event(sd_icmp6_nd *nd) {
169 assert_return(nd, -EINVAL);
170
171 nd->event = sd_event_unref(nd->event);
172
173 return 0;
174 }
175
176 sd_event *sd_icmp6_nd_get_event(sd_icmp6_nd *nd) {
177 assert(nd);
178
179 return nd->event;
180 }
181
182 sd_icmp6_nd *sd_icmp6_nd_ref(sd_icmp6_nd *nd) {
183
184 if (!nd)
185 return NULL;
186
187 assert(nd->n_ref > 0);
188 nd->n_ref++;
189
190 return nd;
191 }
192
193 static int icmp6_nd_init(sd_icmp6_nd *nd) {
194 assert(nd);
195
196 nd->recv = sd_event_source_unref(nd->recv);
197 nd->fd = asynchronous_close(nd->fd);
198 nd->timeout = sd_event_source_unref(nd->timeout);
199
200 return 0;
201 }
202
203 sd_icmp6_nd *sd_icmp6_nd_unref(sd_icmp6_nd *nd) {
204 ICMP6Prefix *prefix, *p;
205
206 if (!nd)
207 return NULL;
208
209 assert(nd->n_ref > 0);
210 nd->n_ref--;
211
212 if (nd->n_ref > 0)
213 return NULL;
214
215 icmp6_nd_init(nd);
216 sd_icmp6_nd_detach_event(nd);
217
218 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
219 LIST_REMOVE(prefixes, nd->prefixes, prefix);
220
221 prefix = icmp6_prefix_unref(prefix);
222 }
223
224 free(nd);
225
226 return NULL;
227 }
228
229 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_icmp6_nd*, sd_icmp6_nd_unref);
230 #define _cleanup_sd_icmp6_nd_free_ _cleanup_(sd_icmp6_nd_unrefp)
231
232 int sd_icmp6_nd_new(sd_icmp6_nd **ret) {
233 _cleanup_sd_icmp6_nd_free_ sd_icmp6_nd *nd = NULL;
234
235 assert(ret);
236
237 nd = new0(sd_icmp6_nd, 1);
238 if (!nd)
239 return -ENOMEM;
240
241 nd->n_ref = 1;
242
243 nd->index = -1;
244 nd->fd = -1;
245
246 LIST_HEAD_INIT(nd->prefixes);
247
248 *ret = nd;
249 nd = NULL;
250
251 return 0;
252 }
253
254 int sd_icmp6_ra_get_mtu(sd_icmp6_nd *nd, uint32_t *mtu) {
255 assert_return(nd, -EINVAL);
256 assert_return(mtu, -EINVAL);
257
258 if (nd->mtu == 0)
259 return -ENOMSG;
260
261 *mtu = nd->mtu;
262
263 return 0;
264 }
265
266 static int icmp6_ra_prefix_timeout(sd_event_source *s, uint64_t usec,
267 void *userdata) {
268 sd_icmp6_nd *nd = userdata;
269 ICMP6Prefix *prefix, *p;
270
271 assert(nd);
272
273 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
274 if (prefix->timeout_valid != s)
275 continue;
276
277 log_icmp6_nd(nd, "Prefix expired "SD_ICMP6_ND_ADDRESS_FORMAT_STR"/%d",
278 SD_ICMP6_ND_ADDRESS_FORMAT_VAL(prefix->addr),
279 prefix->len);
280
281 LIST_REMOVE(prefixes, nd->prefixes, prefix);
282
283 nd->expired_prefix = prefix;
284 icmp6_nd_notify(nd,
285 SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED);
286 nd->expired_prefix = NULL;
287
288 prefix = icmp6_prefix_unref(prefix);
289
290 break;
291 }
292
293 return 0;
294 }
295
296 static int icmp6_ra_prefix_set_timeout(sd_icmp6_nd *nd,
297 ICMP6Prefix *prefix,
298 usec_t valid) {
299 usec_t time_now;
300 int r;
301
302 assert_return(prefix, -EINVAL);
303
304 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
305 if (r < 0)
306 return r;
307
308 prefix->timeout_valid = sd_event_source_unref(prefix->timeout_valid);
309
310 r = sd_event_add_time(nd->event, &prefix->timeout_valid,
311 clock_boottime_or_monotonic(), time_now + valid,
312 USEC_PER_SEC, icmp6_ra_prefix_timeout, nd);
313 if (r < 0)
314 goto error;
315
316 r = sd_event_source_set_priority(prefix->timeout_valid,
317 nd->event_priority);
318 if (r < 0)
319 goto error;
320
321 r = sd_event_source_set_description(prefix->timeout_valid,
322 "icmp6-prefix-timeout");
323
324 error:
325 if (r < 0)
326 prefix->timeout_valid =
327 sd_event_source_unref(prefix->timeout_valid);
328
329 return r;
330 }
331
332 static int icmp6_prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
333 const struct in6_addr *addr,
334 uint8_t addr_prefixlen) {
335 uint8_t bytes, mask, len;
336
337 assert_return(prefix, -EINVAL);
338 assert_return(addr, -EINVAL);
339
340 len = MIN(prefixlen, addr_prefixlen);
341
342 bytes = len / 8;
343 mask = 0xff << (8 - len % 8);
344
345 if (memcmp(prefix, addr, bytes) != 0 ||
346 (prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask))
347 return -EADDRNOTAVAIL;
348
349 return 0;
350 }
351
352 static int icmp6_ra_prefix_match(ICMP6Prefix *head, const struct in6_addr *addr,
353 uint8_t addr_len, ICMP6Prefix **result) {
354 ICMP6Prefix *prefix;
355
356 LIST_FOREACH(prefixes, prefix, head) {
357 if (icmp6_prefix_match(&prefix->addr, prefix->len, addr,
358 addr_len) >= 0) {
359 *result = prefix;
360 return 0;
361 }
362 }
363
364 return -EADDRNOTAVAIL;
365 }
366
367 int sd_icmp6_prefix_match(struct in6_addr *prefix, uint8_t prefixlen,
368 struct in6_addr *addr) {
369 return icmp6_prefix_match(prefix, prefixlen, addr,
370 sizeof(addr->s6_addr) * 8);
371 }
372
373 int sd_icmp6_ra_get_prefixlen(sd_icmp6_nd *nd, const struct in6_addr *addr,
374 uint8_t *prefixlen) {
375 int r;
376 ICMP6Prefix *prefix;
377
378 assert_return(nd, -EINVAL);
379 assert_return(addr, -EINVAL);
380 assert_return(prefixlen, -EINVAL);
381
382 r = icmp6_ra_prefix_match(nd->prefixes, addr,
383 sizeof(addr->s6_addr) * 8, &prefix);
384 if (r < 0)
385 return r;
386
387 *prefixlen = prefix->len;
388
389 return 0;
390 }
391
392 int sd_icmp6_ra_get_expired_prefix(sd_icmp6_nd *nd, struct in6_addr **addr, uint8_t *prefixlen) {
393 assert_return(nd, -EINVAL);
394 assert_return(addr, -EINVAL);
395 assert_return(prefixlen, -EINVAL);
396
397 if (!nd->expired_prefix)
398 return -EADDRNOTAVAIL;
399
400 *addr = &nd->expired_prefix->addr;
401 *prefixlen = nd->expired_prefix->len;
402
403 return 0;
404 }
405
406 static int icmp6_ra_prefix_update(sd_icmp6_nd *nd, ssize_t len,
407 const struct nd_opt_prefix_info *prefix_opt) {
408 int r;
409 ICMP6Prefix *prefix;
410 uint32_t lifetime;
411 char time_string[FORMAT_TIMESPAN_MAX];
412
413 assert_return(nd, -EINVAL);
414 assert_return(prefix_opt, -EINVAL);
415
416 if (len < prefix_opt->nd_opt_pi_len)
417 return -ENOMSG;
418
419 if (!(prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK))
420 return 0;
421
422 lifetime = be32toh(prefix_opt->nd_opt_pi_valid_time);
423
424 r = icmp6_ra_prefix_match(nd->prefixes,
425 &prefix_opt->nd_opt_pi_prefix,
426 prefix_opt->nd_opt_pi_prefix_len, &prefix);
427
428 if (r < 0 && r != -EADDRNOTAVAIL)
429 return r;
430
431 /* if router advertisment prefix valid timeout is zero, the timeout
432 callback will be called immediately to clean up the prefix */
433
434 if (r == -EADDRNOTAVAIL) {
435 r = icmp6_prefix_new(&prefix);
436 if (r < 0)
437 return r;
438
439 prefix->len = prefix_opt->nd_opt_pi_prefix_len;
440
441 memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
442 sizeof(prefix->addr));
443
444 log_icmp6_nd(nd, "New prefix "SD_ICMP6_ND_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
445 SD_ICMP6_ND_ADDRESS_FORMAT_VAL(prefix->addr),
446 prefix->len, lifetime,
447 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime * USEC_PER_SEC, USEC_PER_SEC));
448
449 LIST_PREPEND(prefixes, nd->prefixes, prefix);
450
451 } else {
452 if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
453 uint8_t prefixlen;
454
455 prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
456
457 log_icmp6_nd(nd, "Prefix length mismatch %d/%d using %d",
458 prefix->len,
459 prefix_opt->nd_opt_pi_prefix_len,
460 prefixlen);
461
462 prefix->len = prefixlen;
463 }
464
465 log_icmp6_nd(nd, "Update prefix "SD_ICMP6_ND_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
466 SD_ICMP6_ND_ADDRESS_FORMAT_VAL(prefix->addr),
467 prefix->len, lifetime,
468 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime * USEC_PER_SEC, USEC_PER_SEC));
469 }
470
471 r = icmp6_ra_prefix_set_timeout(nd, prefix, lifetime * USEC_PER_SEC);
472
473 return r;
474 }
475
476 static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
477 ssize_t len) {
478 void *opt;
479 struct nd_opt_hdr *opt_hdr;
480
481 assert_return(nd, -EINVAL);
482 assert_return(ra, -EINVAL);
483
484 len -= sizeof(*ra);
485 if (len < ICMP6_OPT_LEN_UNITS) {
486 log_icmp6_nd(nd, "Router Advertisement below minimum length");
487
488 return -ENOMSG;
489 }
490
491 opt = ra + 1;
492 opt_hdr = opt;
493
494 while (len != 0 && len >= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS) {
495 struct nd_opt_mtu *opt_mtu;
496 uint32_t mtu;
497 struct nd_opt_prefix_info *opt_prefix;
498
499 if (opt_hdr->nd_opt_len == 0)
500 return -ENOMSG;
501
502 switch (opt_hdr->nd_opt_type) {
503 case ND_OPT_MTU:
504 opt_mtu = opt;
505
506 mtu = be32toh(opt_mtu->nd_opt_mtu_mtu);
507
508 if (mtu != nd->mtu) {
509 nd->mtu = MAX(mtu, IP6_MIN_MTU);
510
511 log_icmp6_nd(nd, "Router Advertisement link MTU %d using %d",
512 mtu, nd->mtu);
513 }
514
515 break;
516
517 case ND_OPT_PREFIX_INFORMATION:
518 opt_prefix = opt;
519
520 icmp6_ra_prefix_update(nd, len, opt_prefix);
521
522 break;
523 }
524
525 len -= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS;
526 opt = (void *)((char *)opt +
527 opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS);
528 opt_hdr = opt;
529 }
530
531 if (len > 0)
532 log_icmp6_nd(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
533
534 return 0;
535 }
536
537 static int icmp6_router_advertisment_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
538 sd_icmp6_nd *nd = userdata;
539 int r, buflen = 0;
540 ssize_t len;
541 _cleanup_free_ struct nd_router_advert *ra = NULL;
542 int event = SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_NONE;
543
544 assert(s);
545 assert(nd);
546 assert(nd->event);
547
548 r = ioctl(fd, FIONREAD, &buflen);
549 if (r < 0 || buflen <= 0)
550 buflen = ICMP6_ND_RECV_SIZE;
551
552 ra = malloc(buflen);
553 if (!ra)
554 return -ENOMEM;
555
556 len = read(fd, ra, buflen);
557 if (len < 0) {
558 log_icmp6_nd(nd, "Could not receive message from UDP socket: %m");
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 = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
571
572 if (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
573 event = SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_OTHER;
574
575 if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
576 event = SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_MANAGED;
577
578 log_icmp6_nd(nd, "Received Router Advertisement flags %s/%s",
579 ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED? "MANAGED": "none",
580 ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER? "OTHER": "none");
581
582 if (event != SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_NONE) {
583 r = icmp6_ra_parse(nd, ra, len);
584 if (r < 0) {
585 log_icmp6_nd(nd, "Could not parse Router Advertisement: %s",
586 strerror(-r));
587 return 0;
588 }
589 }
590
591 icmp6_nd_notify(nd, event);
592
593 return 0;
594 }
595
596 static int icmp6_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
597 sd_icmp6_nd *nd = userdata;
598 uint64_t time_now, next_timeout;
599 struct ether_addr unset = { };
600 struct ether_addr *addr = NULL;
601 int r;
602
603 assert(s);
604 assert(nd);
605 assert(nd->event);
606
607 nd->timeout = sd_event_source_unref(nd->timeout);
608
609 if (nd->nd_sent >= ICMP6_MAX_ROUTER_SOLICITATIONS) {
610 icmp6_nd_notify(nd, SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_TIMEOUT);
611 nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
612 } else {
613 if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr)))
614 addr = &nd->mac_addr;
615
616 r = dhcp_network_icmp6_send_router_solicitation(nd->fd, addr);
617 if (r < 0)
618 log_icmp6_nd(nd, "Error sending Router Solicitation");
619 else {
620 nd->state = ICMP6_ROUTER_SOLICITATION_SENT;
621 log_icmp6_nd(nd, "Sent Router Solicitation");
622 }
623
624 nd->nd_sent++;
625
626 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
627 if (r < 0) {
628 icmp6_nd_notify(nd, r);
629 return 0;
630 }
631
632 next_timeout = time_now + ICMP6_ROUTER_SOLICITATION_INTERVAL;
633
634 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
635 next_timeout, 0,
636 icmp6_router_solicitation_timeout, nd);
637 if (r < 0) {
638 icmp6_nd_notify(nd, r);
639 return 0;
640 }
641
642 r = sd_event_source_set_priority(nd->timeout,
643 nd->event_priority);
644 if (r < 0) {
645 icmp6_nd_notify(nd, r);
646 return 0;
647 }
648
649 r = sd_event_source_set_description(nd->timeout, "icmp6-timeout");
650 if (r < 0) {
651 icmp6_nd_notify(nd, r);
652 return 0;
653 }
654 }
655
656 return 0;
657 }
658
659 int sd_icmp6_nd_stop(sd_icmp6_nd *nd) {
660 assert_return(nd, -EINVAL);
661 assert_return(nd->event, -EINVAL);
662
663 log_icmp6_nd(client, "Stop ICMPv6");
664
665 icmp6_nd_init(nd);
666
667 nd->state = ICMP6_NEIGHBOR_DISCOVERY_IDLE;
668
669 return 0;
670 }
671
672 int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd) {
673 int r;
674
675 assert(nd);
676 assert(nd->event);
677
678 if (nd->state != ICMP6_NEIGHBOR_DISCOVERY_IDLE)
679 return -EINVAL;
680
681 if (nd->index < 1)
682 return -EINVAL;
683
684 r = dhcp_network_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 icmp6_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, "icmp6-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, icmp6_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, "icmp6-timeout");
713 error:
714 if (r < 0)
715 icmp6_nd_init(nd);
716 else
717 log_icmp6_nd(client, "Start Router Solicitation");
718
719 return r;
720 }