]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-ndisc.c
util-lib: split our string related calls from util.[ch] into its own file string...
[thirdparty/systemd.git] / src / libsystemd-network / sd-ndisc.c
CommitLineData
e3169126
PF
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>
07630cea 21#include <netinet/in.h>
09667885 22#include <netinet/ip6.h>
e3169126 23#include <stdbool.h>
07630cea 24#include <string.h>
09667885 25#include <sys/ioctl.h>
e3169126 26
07630cea
LP
27#include "sd-ndisc.h"
28
e3169126 29#include "async.h"
07630cea 30#include "icmp6-util.h"
940367a0
TG
31#include "list.h"
32#include "socket-util.h"
e3169126 33
46ec6687
TG
34#define NDISC_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC
35#define NDISC_MAX_ROUTER_SOLICITATIONS 3
e3169126 36
46ec6687 37enum NDiscState {
c93578f5
TG
38 NDISC_STATE_IDLE,
39 NDISC_STATE_SOLICITATION_SENT,
40 NDISC_STATE_ADVERTISMENT_LISTEN,
41 _NDISC_STATE_MAX,
42 _NDISC_STATE_INVALID = -1,
e3169126
PF
43};
44
09667885 45#define IP6_MIN_MTU (unsigned)1280
46ec6687
TG
46#define ICMP6_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
47#define NDISC_OPT_LEN_UNITS 8
09667885 48
46ec6687 49typedef struct NDiscPrefix NDiscPrefix;
5624c480 50
46ec6687 51struct NDiscPrefix {
9c8e3101 52 unsigned n_ref;
5624c480 53
272f5cd9
TG
54 sd_ndisc *nd;
55
46ec6687 56 LIST_FIELDS(NDiscPrefix, prefixes);
5624c480
PF
57
58 uint8_t len;
f6e0ce66 59 usec_t valid_until;
5624c480
PF
60 struct in6_addr addr;
61};
62
4d7b83da 63struct sd_ndisc {
9c8e3101 64 unsigned n_ref;
e3169126 65
46ec6687 66 enum NDiscState state;
e3169126
PF
67 sd_event *event;
68 int event_priority;
69 int index;
70 struct ether_addr mac_addr;
5624c480 71 uint32_t mtu;
46ec6687 72 LIST_HEAD(NDiscPrefix, prefixes);
e3169126
PF
73 int fd;
74 sd_event_source *recv;
75 sd_event_source *timeout;
76 int nd_sent;
4d7b83da 77 sd_ndisc_callback_t callback;
e3169126
PF
78 void *userdata;
79};
80
46ec6687 81#define log_ndisc(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "NDisc CLIENT: " fmt, ##__VA_ARGS__)
e3169126 82
46ec6687 83static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) {
5624c480 84
9c8e3101
LP
85 if (!prefix)
86 return NULL;
87
88 assert(prefix->n_ref > 0);
89 prefix->n_ref--;
90
91 if (prefix->n_ref > 0)
92 return NULL;
5624c480 93
272f5cd9
TG
94 if (prefix->nd)
95 LIST_REMOVE(prefixes, prefix->nd->prefixes, prefix);
96
9c8e3101 97 free(prefix);
272f5cd9 98
5624c480
PF
99 return NULL;
100}
101
272f5cd9 102static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) {
46ec6687 103 _cleanup_free_ NDiscPrefix *prefix = NULL;
5624c480
PF
104
105 assert(ret);
106
46ec6687 107 prefix = new0(NDiscPrefix, 1);
5624c480
PF
108 if (!prefix)
109 return -ENOMEM;
110
9c8e3101 111 prefix->n_ref = 1;
5624c480 112 LIST_INIT(prefixes, prefix);
272f5cd9 113 prefix->nd = nd;
5624c480
PF
114
115 *ret = prefix;
116 prefix = NULL;
117
118 return 0;
119}
120
46ec6687 121static void ndisc_notify(sd_ndisc *nd, int event) {
e3169126
PF
122 if (nd->callback)
123 nd->callback(nd, event, nd->userdata);
124}
125
4d7b83da 126int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t callback,
e3169126
PF
127 void *userdata) {
128 assert(nd);
129
130 nd->callback = callback;
131 nd->userdata = userdata;
132
133 return 0;
134}
135
4d7b83da 136int sd_ndisc_set_index(sd_ndisc *nd, int interface_index) {
e3169126
PF
137 assert(nd);
138 assert(interface_index >= -1);
139
140 nd->index = interface_index;
141
142 return 0;
143}
144
4d7b83da 145int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
e3169126
PF
146 assert(nd);
147
148 if (mac_addr)
149 memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
150 else
eccaf899 151 zero(nd->mac_addr);
e3169126
PF
152
153 return 0;
154
155}
156
4d7b83da 157int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int priority) {
e3169126
PF
158 int r;
159
160 assert_return(nd, -EINVAL);
161 assert_return(!nd->event, -EBUSY);
162
163 if (event)
164 nd->event = sd_event_ref(event);
165 else {
166 r = sd_event_default(&nd->event);
167 if (r < 0)
168 return 0;
169 }
170
171 nd->event_priority = priority;
172
173 return 0;
174}
175
4d7b83da 176int sd_ndisc_detach_event(sd_ndisc *nd) {
e3169126
PF
177 assert_return(nd, -EINVAL);
178
179 nd->event = sd_event_unref(nd->event);
180
181 return 0;
182}
183
4d7b83da 184sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
e3169126
PF
185 assert(nd);
186
187 return nd->event;
188}
189
4d7b83da 190sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
e3169126 191
9c8e3101
LP
192 if (!nd)
193 return NULL;
194
195 assert(nd->n_ref > 0);
196 nd->n_ref++;
e3169126
PF
197
198 return nd;
199}
200
46ec6687 201static int ndisc_init(sd_ndisc *nd) {
e3169126
PF
202 assert(nd);
203
204 nd->recv = sd_event_source_unref(nd->recv);
205 nd->fd = asynchronous_close(nd->fd);
206 nd->timeout = sd_event_source_unref(nd->timeout);
207
208 return 0;
209}
210
4d7b83da 211sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
46ec6687 212 NDiscPrefix *prefix, *p;
e3169126 213
9c8e3101
LP
214 if (!nd)
215 return NULL;
216
217 assert(nd->n_ref > 0);
218 nd->n_ref--;
219
220 if (nd->n_ref > 0)
221 return NULL;
e3169126 222
46ec6687 223 ndisc_init(nd);
4d7b83da 224 sd_ndisc_detach_event(nd);
5624c480 225
272f5cd9 226 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes)
46ec6687 227 prefix = ndisc_prefix_unref(prefix);
e3169126 228
9c8e3101
LP
229 free(nd);
230
e3169126
PF
231 return NULL;
232}
233
4d7b83da
TG
234DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ndisc*, sd_ndisc_unref);
235#define _cleanup_sd_ndisc_free_ _cleanup_(sd_ndisc_unrefp)
e3169126 236
4d7b83da
TG
237int sd_ndisc_new(sd_ndisc **ret) {
238 _cleanup_sd_ndisc_free_ sd_ndisc *nd = NULL;
e3169126
PF
239
240 assert(ret);
241
4d7b83da 242 nd = new0(sd_ndisc, 1);
e3169126
PF
243 if (!nd)
244 return -ENOMEM;
245
9c8e3101 246 nd->n_ref = 1;
e3169126
PF
247
248 nd->index = -1;
03de7ed9 249 nd->fd = -1;
e3169126 250
5624c480
PF
251 LIST_HEAD_INIT(nd->prefixes);
252
e3169126
PF
253 *ret = nd;
254 nd = NULL;
255
256 return 0;
257}
258
4d7b83da 259int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
d14b5bc6
PF
260 assert_return(nd, -EINVAL);
261 assert_return(mtu, -EINVAL);
262
263 if (nd->mtu == 0)
264 return -ENOMSG;
265
266 *mtu = nd->mtu;
267
268 return 0;
269}
270
46ec6687
TG
271static int prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
272 const struct in6_addr *addr,
273 uint8_t addr_prefixlen) {
d77bde34
PF
274 uint8_t bytes, mask, len;
275
276 assert_return(prefix, -EINVAL);
277 assert_return(addr, -EINVAL);
278
279 len = MIN(prefixlen, addr_prefixlen);
280
281 bytes = len / 8;
282 mask = 0xff << (8 - len % 8);
283
284 if (memcmp(prefix, addr, bytes) != 0 ||
285 (prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask))
286 return -EADDRNOTAVAIL;
287
288 return 0;
289}
290
f6e0ce66
TG
291static int ndisc_prefix_match(sd_ndisc *nd, const struct in6_addr *addr,
292 uint8_t addr_len, NDiscPrefix **result) {
293 NDiscPrefix *prefix, *p;
294 usec_t time_now;
295 int r;
296
297 assert(nd);
298
299 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
300 if (r < 0)
301 return r;
302
303 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
304 if (prefix->valid_until < time_now) {
305 prefix = ndisc_prefix_unref(prefix);
306
307 continue;
308 }
d77bde34 309
46ec6687 310 if (prefix_match(&prefix->addr, prefix->len, addr, addr_len) >= 0) {
d77bde34
PF
311 *result = prefix;
312 return 0;
313 }
314 }
315
316 return -EADDRNOTAVAIL;
317}
318
46ec6687 319static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
f6e0ce66 320 const struct nd_opt_prefix_info *prefix_opt) {
46ec6687 321 NDiscPrefix *prefix;
d77bde34 322 uint32_t lifetime;
f6e0ce66 323 usec_t time_now;
d77bde34 324 char time_string[FORMAT_TIMESPAN_MAX];
f6e0ce66 325 int r;
d77bde34 326
f6e0ce66
TG
327 assert(nd);
328 assert(prefix_opt);
d77bde34
PF
329
330 if (len < prefix_opt->nd_opt_pi_len)
331 return -ENOMSG;
332
333 if (!(prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK))
334 return 0;
335
336 lifetime = be32toh(prefix_opt->nd_opt_pi_valid_time);
337
f6e0ce66
TG
338 r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix,
339 prefix_opt->nd_opt_pi_prefix_len, &prefix);
d77bde34
PF
340
341 if (r < 0 && r != -EADDRNOTAVAIL)
342 return r;
343
344 /* if router advertisment prefix valid timeout is zero, the timeout
345 callback will be called immediately to clean up the prefix */
346
347 if (r == -EADDRNOTAVAIL) {
272f5cd9 348 r = ndisc_prefix_new(nd, &prefix);
d77bde34
PF
349 if (r < 0)
350 return r;
351
352 prefix->len = prefix_opt->nd_opt_pi_prefix_len;
353
354 memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
355 sizeof(prefix->addr));
356
46ec6687 357 log_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
4d7b83da 358 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
d77bde34 359 prefix->len, lifetime,
ed19c567 360 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime * USEC_PER_SEC, USEC_PER_SEC));
d77bde34
PF
361
362 LIST_PREPEND(prefixes, nd->prefixes, prefix);
363
364 } else {
365 if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
366 uint8_t prefixlen;
367
368 prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
369
46ec6687 370 log_ndisc(nd, "Prefix length mismatch %d/%d using %d",
d77bde34
PF
371 prefix->len,
372 prefix_opt->nd_opt_pi_prefix_len,
373 prefixlen);
374
375 prefix->len = prefixlen;
376 }
377
46ec6687 378 log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
4d7b83da 379 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
d77bde34 380 prefix->len, lifetime,
ed19c567 381 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime * USEC_PER_SEC, USEC_PER_SEC));
d77bde34
PF
382 }
383
f6e0ce66
TG
384 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
385 if (r < 0)
386 return r;
387
388 prefix->valid_until = time_now + lifetime * USEC_PER_SEC;
d77bde34
PF
389
390 return r;
391}
392
46ec6687 393static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra,
09667885
PF
394 ssize_t len) {
395 void *opt;
396 struct nd_opt_hdr *opt_hdr;
397
398 assert_return(nd, -EINVAL);
399 assert_return(ra, -EINVAL);
400
401 len -= sizeof(*ra);
46ec6687
TG
402 if (len < NDISC_OPT_LEN_UNITS) {
403 log_ndisc(nd, "Router Advertisement below minimum length");
09667885
PF
404
405 return -ENOMSG;
406 }
407
408 opt = ra + 1;
409 opt_hdr = opt;
410
46ec6687 411 while (len != 0 && len >= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS) {
d14b5bc6
PF
412 struct nd_opt_mtu *opt_mtu;
413 uint32_t mtu;
d77bde34 414 struct nd_opt_prefix_info *opt_prefix;
09667885
PF
415
416 if (opt_hdr->nd_opt_len == 0)
417 return -ENOMSG;
418
419 switch (opt_hdr->nd_opt_type) {
d14b5bc6
PF
420 case ND_OPT_MTU:
421 opt_mtu = opt;
422
423 mtu = be32toh(opt_mtu->nd_opt_mtu_mtu);
424
425 if (mtu != nd->mtu) {
426 nd->mtu = MAX(mtu, IP6_MIN_MTU);
427
46ec6687 428 log_ndisc(nd, "Router Advertisement link MTU %d using %d",
d14b5bc6
PF
429 mtu, nd->mtu);
430 }
431
432 break;
09667885 433
d77bde34
PF
434 case ND_OPT_PREFIX_INFORMATION:
435 opt_prefix = opt;
436
46ec6687 437 ndisc_prefix_update(nd, len, opt_prefix);
d77bde34
PF
438
439 break;
09667885
PF
440 }
441
46ec6687 442 len -= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS;
09667885 443 opt = (void *)((char *)opt +
46ec6687 444 opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS);
09667885
PF
445 opt_hdr = opt;
446 }
447
448 if (len > 0)
46ec6687 449 log_ndisc(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
09667885
PF
450
451 return 0;
452}
453
46ec6687 454static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
4d7b83da 455 sd_ndisc *nd = userdata;
09667885 456 int r, buflen = 0;
e3169126 457 ssize_t len;
09667885 458 _cleanup_free_ struct nd_router_advert *ra = NULL;
4d7b83da 459 int event = SD_NDISC_EVENT_ROUTER_ADVERTISMENT_NONE;
e3169126
PF
460
461 assert(s);
462 assert(nd);
463 assert(nd->event);
464
09667885
PF
465 r = ioctl(fd, FIONREAD, &buflen);
466 if (r < 0 || buflen <= 0)
46ec6687 467 buflen = ICMP6_RECV_SIZE;
09667885
PF
468
469 ra = malloc(buflen);
470 if (!ra)
471 return -ENOMEM;
472
473 len = read(fd, ra, buflen);
474 if (len < 0) {
46ec6687 475 log_ndisc(nd, "Could not receive message from UDP socket: %m");
e3169126 476 return 0;
09667885 477 }
e3169126 478
09667885 479 if (ra->nd_ra_type != ND_ROUTER_ADVERT)
e3169126
PF
480 return 0;
481
09667885 482 if (ra->nd_ra_code != 0)
e3169126
PF
483 return 0;
484
485 nd->timeout = sd_event_source_unref(nd->timeout);
486
46ec6687 487 nd->state = NDISC_STATE_ADVERTISMENT_LISTEN;
e3169126 488
09667885 489 if (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
4d7b83da 490 event = SD_NDISC_EVENT_ROUTER_ADVERTISMENT_OTHER;
e3169126 491
09667885 492 if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
4d7b83da 493 event = SD_NDISC_EVENT_ROUTER_ADVERTISMENT_MANAGED;
e3169126 494
46ec6687 495 log_ndisc(nd, "Received Router Advertisement flags %s/%s",
09667885
PF
496 ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED? "MANAGED": "none",
497 ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER? "OTHER": "none");
498
4d7b83da 499 if (event != SD_NDISC_EVENT_ROUTER_ADVERTISMENT_NONE) {
46ec6687 500 r = ndisc_ra_parse(nd, ra, len);
09667885 501 if (r < 0) {
46ec6687 502 log_ndisc(nd, "Could not parse Router Advertisement: %s",
09667885
PF
503 strerror(-r));
504 return 0;
505 }
506 }
e3169126 507
46ec6687 508 ndisc_notify(nd, event);
e3169126
PF
509
510 return 0;
511}
512
46ec6687 513static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
4d7b83da 514 sd_ndisc *nd = userdata;
e3169126
PF
515 uint64_t time_now, next_timeout;
516 struct ether_addr unset = { };
517 struct ether_addr *addr = NULL;
518 int r;
519
520 assert(s);
521 assert(nd);
522 assert(nd->event);
523
524 nd->timeout = sd_event_source_unref(nd->timeout);
525
46ec6687
TG
526 if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
527 ndisc_notify(nd, SD_NDISC_EVENT_ROUTER_ADVERTISMENT_TIMEOUT);
528 nd->state = NDISC_STATE_ADVERTISMENT_LISTEN;
e3169126
PF
529 } else {
530 if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr)))
531 addr = &nd->mac_addr;
532
940367a0 533 r = icmp6_send_router_solicitation(nd->fd, addr);
e3169126 534 if (r < 0)
46ec6687 535 log_ndisc(nd, "Error sending Router Solicitation");
e3169126 536 else {
46ec6687
TG
537 nd->state = NDISC_STATE_SOLICITATION_SENT;
538 log_ndisc(nd, "Sent Router Solicitation");
e3169126
PF
539 }
540
541 nd->nd_sent++;
542
cbe91b3c 543 assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
e3169126 544
46ec6687 545 next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
e3169126 546
fa94c34b 547 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
e3169126 548 next_timeout, 0,
46ec6687 549 ndisc_router_solicitation_timeout, nd);
e3169126 550 if (r < 0) {
46ec6687 551 ndisc_notify(nd, r);
e3169126
PF
552 return 0;
553 }
554
cbe91b3c
TG
555 r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
556 if (r < 0)
e3169126 557 return 0;
9021bb9f 558
46ec6687 559 r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
cbe91b3c 560 if (r < 0)
9021bb9f 561 return 0;
e3169126
PF
562 }
563
564 return 0;
565}
566
4d7b83da 567int sd_ndisc_stop(sd_ndisc *nd) {
836cf090
PF
568 assert_return(nd, -EINVAL);
569 assert_return(nd->event, -EINVAL);
570
46ec6687 571 log_ndisc(client, "Stop NDisc");
836cf090 572
46ec6687 573 ndisc_init(nd);
836cf090 574
46ec6687 575 nd->state = NDISC_STATE_IDLE;
836cf090
PF
576
577 return 0;
578}
579
4d7b83da 580int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
e3169126
PF
581 int r;
582
583 assert(nd);
584 assert(nd->event);
585
46ec6687 586 if (nd->state != NDISC_STATE_IDLE)
e3169126
PF
587 return -EINVAL;
588
589 if (nd->index < 1)
590 return -EINVAL;
591
940367a0 592 r = icmp6_bind_router_solicitation(nd->index);
e3169126
PF
593 if (r < 0)
594 return r;
595
596 nd->fd = r;
597
598 r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN,
46ec6687 599 ndisc_router_advertisment_recv, nd);
e3169126
PF
600 if (r < 0)
601 goto error;
602
603 r = sd_event_source_set_priority(nd->recv, nd->event_priority);
604 if (r < 0)
605 goto error;
606
46ec6687 607 r = sd_event_source_set_description(nd->recv, "ndisc-receive-message");
9021bb9f
TG
608 if (r < 0)
609 goto error;
610
fa94c34b 611 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
46ec6687 612 0, 0, ndisc_router_solicitation_timeout, nd);
e3169126
PF
613 if (r < 0)
614 goto error;
615
616 r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
9021bb9f
TG
617 if (r < 0)
618 goto error;
e3169126 619
46ec6687 620 r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
e3169126
PF
621error:
622 if (r < 0)
46ec6687 623 ndisc_init(nd);
e3169126 624 else
46ec6687 625 log_ndisc(client, "Start Router Solicitation");
e3169126
PF
626
627 return r;
628}