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