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