]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-ndisc.c
Merge pull request #2049 from evverx/journal-test-dont-run-on-incomplete-setup
[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"
9d96e6c3 32#include "in-addr-util.h"
940367a0
TG
33#include "list.h"
34#include "socket-util.h"
d7fa4380 35#include "string-util.h"
e3169126 36
46ec6687
TG
37#define NDISC_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC
38#define NDISC_MAX_ROUTER_SOLICITATIONS 3
e3169126 39
46ec6687 40enum NDiscState {
c93578f5
TG
41 NDISC_STATE_IDLE,
42 NDISC_STATE_SOLICITATION_SENT,
43 NDISC_STATE_ADVERTISMENT_LISTEN,
44 _NDISC_STATE_MAX,
45 _NDISC_STATE_INVALID = -1,
e3169126
PF
46};
47
09667885 48#define IP6_MIN_MTU (unsigned)1280
46ec6687
TG
49#define ICMP6_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
50#define NDISC_OPT_LEN_UNITS 8
09667885 51
9d96e6c3
TG
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
46ec6687 58typedef struct NDiscPrefix NDiscPrefix;
5624c480 59
46ec6687 60struct NDiscPrefix {
9c8e3101 61 unsigned n_ref;
5624c480 62
272f5cd9
TG
63 sd_ndisc *nd;
64
46ec6687 65 LIST_FIELDS(NDiscPrefix, prefixes);
5624c480
PF
66
67 uint8_t len;
f6e0ce66 68 usec_t valid_until;
5624c480
PF
69 struct in6_addr addr;
70};
71
4d7b83da 72struct sd_ndisc {
9c8e3101 73 unsigned n_ref;
e3169126 74
46ec6687 75 enum NDiscState state;
e3169126
PF
76 sd_event *event;
77 int event_priority;
78 int index;
79 struct ether_addr mac_addr;
5624c480 80 uint32_t mtu;
46ec6687 81 LIST_HEAD(NDiscPrefix, prefixes);
e3169126
PF
82 int fd;
83 sd_event_source *recv;
84 sd_event_source *timeout;
85 int nd_sent;
9d96e6c3
TG
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;
4d7b83da 89 sd_ndisc_callback_t callback;
e3169126
PF
90 void *userdata;
91};
92
46ec6687 93#define log_ndisc(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "NDisc CLIENT: " fmt, ##__VA_ARGS__)
e3169126 94
46ec6687 95static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) {
5624c480 96
9c8e3101
LP
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;
5624c480 105
272f5cd9
TG
106 if (prefix->nd)
107 LIST_REMOVE(prefixes, prefix->nd->prefixes, prefix);
108
9c8e3101 109 free(prefix);
272f5cd9 110
5624c480
PF
111 return NULL;
112}
113
272f5cd9 114static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) {
46ec6687 115 _cleanup_free_ NDiscPrefix *prefix = NULL;
5624c480
PF
116
117 assert(ret);
118
46ec6687 119 prefix = new0(NDiscPrefix, 1);
5624c480
PF
120 if (!prefix)
121 return -ENOMEM;
122
9c8e3101 123 prefix->n_ref = 1;
5624c480 124 LIST_INIT(prefixes, prefix);
272f5cd9 125 prefix->nd = nd;
5624c480
PF
126
127 *ret = prefix;
128 prefix = NULL;
129
130 return 0;
131}
132
9d96e6c3
TG
133int 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) {
e3169126
PF
139 assert(nd);
140
9d96e6c3
TG
141 nd->router_callback = router_callback;
142 nd->prefix_onlink_callback = prefix_onlink_callback;
143 nd->prefix_autonomous_callback = prefix_autonomous_callback;
e3169126
PF
144 nd->callback = callback;
145 nd->userdata = userdata;
146
147 return 0;
148}
149
4d7b83da 150int sd_ndisc_set_index(sd_ndisc *nd, int interface_index) {
e3169126
PF
151 assert(nd);
152 assert(interface_index >= -1);
153
154 nd->index = interface_index;
155
156 return 0;
157}
158
4d7b83da 159int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
e3169126
PF
160 assert(nd);
161
162 if (mac_addr)
163 memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
164 else
eccaf899 165 zero(nd->mac_addr);
e3169126
PF
166
167 return 0;
168
169}
170
4d7b83da 171int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int priority) {
e3169126
PF
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
4d7b83da 190int sd_ndisc_detach_event(sd_ndisc *nd) {
e3169126
PF
191 assert_return(nd, -EINVAL);
192
193 nd->event = sd_event_unref(nd->event);
194
195 return 0;
196}
197
4d7b83da 198sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
e3169126
PF
199 assert(nd);
200
201 return nd->event;
202}
203
4d7b83da 204sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
e3169126 205
9c8e3101
LP
206 if (!nd)
207 return NULL;
208
209 assert(nd->n_ref > 0);
210 nd->n_ref++;
e3169126
PF
211
212 return nd;
213}
214
46ec6687 215static int ndisc_init(sd_ndisc *nd) {
e3169126
PF
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
4d7b83da 225sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
46ec6687 226 NDiscPrefix *prefix, *p;
e3169126 227
9c8e3101
LP
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;
e3169126 236
46ec6687 237 ndisc_init(nd);
4d7b83da 238 sd_ndisc_detach_event(nd);
5624c480 239
272f5cd9 240 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes)
46ec6687 241 prefix = ndisc_prefix_unref(prefix);
e3169126 242
9c8e3101
LP
243 free(nd);
244
e3169126
PF
245 return NULL;
246}
247
4d7b83da
TG
248DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ndisc*, sd_ndisc_unref);
249#define _cleanup_sd_ndisc_free_ _cleanup_(sd_ndisc_unrefp)
e3169126 250
4d7b83da
TG
251int sd_ndisc_new(sd_ndisc **ret) {
252 _cleanup_sd_ndisc_free_ sd_ndisc *nd = NULL;
e3169126
PF
253
254 assert(ret);
255
4d7b83da 256 nd = new0(sd_ndisc, 1);
e3169126
PF
257 if (!nd)
258 return -ENOMEM;
259
9c8e3101 260 nd->n_ref = 1;
e3169126
PF
261
262 nd->index = -1;
03de7ed9 263 nd->fd = -1;
e3169126 264
5624c480
PF
265 LIST_HEAD_INIT(nd->prefixes);
266
e3169126
PF
267 *ret = nd;
268 nd = NULL;
269
270 return 0;
271}
272
4d7b83da 273int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
d14b5bc6
PF
274 assert_return(nd, -EINVAL);
275 assert_return(mtu, -EINVAL);
276
277 if (nd->mtu == 0)
278 return -ENOMSG;
279
280 *mtu = nd->mtu;
281
282 return 0;
283}
284
46ec6687
TG
285static int prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
286 const struct in6_addr *addr,
287 uint8_t addr_prefixlen) {
d77bde34
PF
288 uint8_t bytes, mask, len;
289
290 assert_return(prefix, -EINVAL);
291 assert_return(addr, -EINVAL);
292
293 len = MIN(prefixlen, addr_prefixlen);
294
295 bytes = len / 8;
296 mask = 0xff << (8 - len % 8);
297
298 if (memcmp(prefix, addr, bytes) != 0 ||
299 (prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask))
300 return -EADDRNOTAVAIL;
301
302 return 0;
303}
304
f6e0ce66
TG
305static int ndisc_prefix_match(sd_ndisc *nd, const struct in6_addr *addr,
306 uint8_t addr_len, NDiscPrefix **result) {
307 NDiscPrefix *prefix, *p;
308 usec_t time_now;
309 int r;
310
311 assert(nd);
312
313 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
314 if (r < 0)
315 return r;
316
317 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
318 if (prefix->valid_until < time_now) {
319 prefix = ndisc_prefix_unref(prefix);
320
321 continue;
322 }
d77bde34 323
46ec6687 324 if (prefix_match(&prefix->addr, prefix->len, addr, addr_len) >= 0) {
d77bde34
PF
325 *result = prefix;
326 return 0;
327 }
328 }
329
330 return -EADDRNOTAVAIL;
331}
332
46ec6687 333static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
f6e0ce66 334 const struct nd_opt_prefix_info *prefix_opt) {
46ec6687 335 NDiscPrefix *prefix;
9d96e6c3 336 uint32_t lifetime_valid, lifetime_preferred;
f6e0ce66 337 usec_t time_now;
d77bde34 338 char time_string[FORMAT_TIMESPAN_MAX];
f6e0ce66 339 int r;
d77bde34 340
f6e0ce66
TG
341 assert(nd);
342 assert(prefix_opt);
d77bde34
PF
343
344 if (len < prefix_opt->nd_opt_pi_len)
345 return -ENOMSG;
346
9d96e6c3
TG
347 if (!(prefix_opt->nd_opt_pi_flags_reserved & (ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO)))
348 return 0;
349
350 if (in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &prefix_opt->nd_opt_pi_prefix) > 0)
d77bde34
PF
351 return 0;
352
9d96e6c3
TG
353 lifetime_valid = be32toh(prefix_opt->nd_opt_pi_valid_time);
354 lifetime_preferred = be32toh(prefix_opt->nd_opt_pi_preferred_time);
355
356 if (lifetime_valid < lifetime_preferred)
357 return 0;
d77bde34 358
f6e0ce66
TG
359 r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix,
360 prefix_opt->nd_opt_pi_prefix_len, &prefix);
d77bde34
PF
361
362 if (r < 0 && r != -EADDRNOTAVAIL)
363 return r;
364
365 /* if router advertisment prefix valid timeout is zero, the timeout
366 callback will be called immediately to clean up the prefix */
367
368 if (r == -EADDRNOTAVAIL) {
272f5cd9 369 r = ndisc_prefix_new(nd, &prefix);
d77bde34
PF
370 if (r < 0)
371 return r;
372
373 prefix->len = prefix_opt->nd_opt_pi_prefix_len;
374
375 memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
376 sizeof(prefix->addr));
377
46ec6687 378 log_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
4d7b83da 379 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
9d96e6c3
TG
380 prefix->len, lifetime_valid,
381 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
d77bde34
PF
382
383 LIST_PREPEND(prefixes, nd->prefixes, prefix);
384
385 } else {
386 if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
387 uint8_t prefixlen;
388
389 prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
390
46ec6687 391 log_ndisc(nd, "Prefix length mismatch %d/%d using %d",
d77bde34
PF
392 prefix->len,
393 prefix_opt->nd_opt_pi_prefix_len,
394 prefixlen);
395
396 prefix->len = prefixlen;
397 }
398
46ec6687 399 log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
4d7b83da 400 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
9d96e6c3
TG
401 prefix->len, lifetime_valid,
402 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
d77bde34
PF
403 }
404
f6e0ce66
TG
405 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
406 if (r < 0)
407 return r;
408
9d96e6c3
TG
409 prefix->valid_until = time_now + lifetime_valid * USEC_PER_SEC;
410
411 if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) && nd->prefix_onlink_callback)
412 nd->prefix_onlink_callback(nd, &prefix->addr, prefix->len, prefix->valid_until, nd->userdata);
413
414 if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) && nd->prefix_autonomous_callback)
415 nd->prefix_autonomous_callback(nd, &prefix->addr, prefix->len, lifetime_preferred, lifetime_valid,
416 nd->userdata);
d77bde34 417
9d96e6c3 418 return 0;
d77bde34
PF
419}
420
cddf4d81 421static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, ssize_t len) {
09667885
PF
422 void *opt;
423 struct nd_opt_hdr *opt_hdr;
424
425 assert_return(nd, -EINVAL);
426 assert_return(ra, -EINVAL);
427
428 len -= sizeof(*ra);
46ec6687
TG
429 if (len < NDISC_OPT_LEN_UNITS) {
430 log_ndisc(nd, "Router Advertisement below minimum length");
09667885
PF
431
432 return -ENOMSG;
433 }
434
435 opt = ra + 1;
436 opt_hdr = opt;
437
46ec6687 438 while (len != 0 && len >= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS) {
d14b5bc6
PF
439 struct nd_opt_mtu *opt_mtu;
440 uint32_t mtu;
d77bde34 441 struct nd_opt_prefix_info *opt_prefix;
09667885
PF
442
443 if (opt_hdr->nd_opt_len == 0)
444 return -ENOMSG;
445
446 switch (opt_hdr->nd_opt_type) {
d14b5bc6
PF
447 case ND_OPT_MTU:
448 opt_mtu = opt;
449
450 mtu = be32toh(opt_mtu->nd_opt_mtu_mtu);
451
452 if (mtu != nd->mtu) {
453 nd->mtu = MAX(mtu, IP6_MIN_MTU);
454
46ec6687 455 log_ndisc(nd, "Router Advertisement link MTU %d using %d",
d14b5bc6
PF
456 mtu, nd->mtu);
457 }
458
459 break;
09667885 460
d77bde34
PF
461 case ND_OPT_PREFIX_INFORMATION:
462 opt_prefix = opt;
463
46ec6687 464 ndisc_prefix_update(nd, len, opt_prefix);
d77bde34
PF
465
466 break;
09667885
PF
467 }
468
46ec6687 469 len -= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS;
09667885 470 opt = (void *)((char *)opt +
46ec6687 471 opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS);
09667885
PF
472 opt_hdr = opt;
473 }
474
475 if (len > 0)
46ec6687 476 log_ndisc(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
09667885
PF
477
478 return 0;
479}
480
46ec6687 481static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
9d96e6c3 482 _cleanup_free_ struct nd_router_advert *ra = NULL;
4d7b83da 483 sd_ndisc *nd = userdata;
cddf4d81
TG
484 union {
485 struct cmsghdr cmsghdr;
486 uint8_t buf[CMSG_LEN(sizeof(int))];
487 } control = {};
488 struct iovec iov = {};
489 union sockaddr_union sa = {};
490 struct msghdr msg = {
491 .msg_name = &sa.sa,
492 .msg_namelen = sizeof(sa),
493 .msg_iov = &iov,
494 .msg_iovlen = 1,
495 .msg_control = &control,
496 .msg_controllen = sizeof(control),
497 };
498 struct cmsghdr *cmsg;
d7fa4380 499 struct in6_addr *gw;
9d96e6c3 500 unsigned lifetime;
e3169126 501 ssize_t len;
cddf4d81 502 int r, pref, stateful, buflen = 0;
e3169126
PF
503
504 assert(s);
505 assert(nd);
506 assert(nd->event);
507
09667885 508 r = ioctl(fd, FIONREAD, &buflen);
0d43d2fc
TG
509 if (r < 0)
510 return -errno;
511 else if (buflen < 0)
512 /* This really should not happen */
513 return -EIO;
09667885 514
cddf4d81
TG
515 iov.iov_len = buflen;
516
517 ra = malloc(iov.iov_len);
09667885
PF
518 if (!ra)
519 return -ENOMEM;
520
cddf4d81
TG
521 iov.iov_base = ra;
522
523 len = recvmsg(fd, &msg, 0);
09667885 524 if (len < 0) {
0d43d2fc
TG
525 if (errno == EAGAIN || errno == EINTR)
526 return 0;
527
9d96e6c3 528 log_ndisc(nd, "Could not receive message from ICMPv6 socket: %m");
0d43d2fc 529 return -errno;
cddf4d81
TG
530 } else if ((size_t)len < sizeof(struct nd_router_advert)) {
531 return 0;
532 } else if (msg.msg_namelen == 0)
d7fa4380 533 gw = NULL; /* only happens when running the test-suite over a socketpair */
cddf4d81
TG
534 else if (msg.msg_namelen != sizeof(sa.in6)) {
535 log_ndisc(nd, "Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t)msg.msg_namelen);
e3169126 536 return 0;
d7fa4380 537 } else
cddf4d81
TG
538 gw = &sa.in6.sin6_addr;
539
540 assert(!(msg.msg_flags & MSG_CTRUNC));
541 assert(!(msg.msg_flags & MSG_TRUNC));
542
543 CMSG_FOREACH(cmsg, &msg) {
544 if (cmsg->cmsg_level == SOL_IPV6 &&
545 cmsg->cmsg_type == IPV6_HOPLIMIT &&
546 cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
547 int hops = *(int*)CMSG_DATA(cmsg);
548
549 if (hops != 255) {
550 log_ndisc(nd, "Received RA with invalid hop limit %d. Ignoring.", hops);
551 return 0;
552 }
553
554 break;
555 }
556 }
d7fa4380
TG
557
558 if (gw && !in_addr_is_link_local(AF_INET6, (const union in_addr_union*) gw)) {
559 _cleanup_free_ char *addr = NULL;
e3169126 560
d7fa4380
TG
561 (void)in_addr_to_string(AF_INET6, (const union in_addr_union*) gw, &addr);
562
563 log_ndisc(nd, "Received RA from non-link-local address %s. Ignoring.", strna(addr));
3ccd3163 564 return 0;
d7fa4380 565 }
3ccd3163 566
09667885 567 if (ra->nd_ra_type != ND_ROUTER_ADVERT)
e3169126
PF
568 return 0;
569
09667885 570 if (ra->nd_ra_code != 0)
e3169126
PF
571 return 0;
572
573 nd->timeout = sd_event_source_unref(nd->timeout);
574
46ec6687 575 nd->state = NDISC_STATE_ADVERTISMENT_LISTEN;
e3169126 576
9d96e6c3 577 stateful = ra->nd_ra_flags_reserved & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER);
133dd71f 578 pref = (ra->nd_ra_flags_reserved & ND_RA_FLAG_PREF) >> 3;
3b015d40
TG
579
580 switch (pref) {
9d96e6c3 581 case ND_RA_FLAG_PREF_LOW:
9d96e6c3 582 case ND_RA_FLAG_PREF_HIGH:
9d96e6c3
TG
583 break;
584 default:
3b015d40 585 pref = ND_RA_FLAG_PREF_MEDIUM;
9d96e6c3
TG
586 break;
587 }
e3169126 588
9d96e6c3 589 lifetime = be16toh(ra->nd_ra_router_lifetime);
e3169126 590
9d96e6c3
TG
591 log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %u sec",
592 stateful & ND_RA_FLAG_MANAGED ? "MANAGED" : stateful & ND_RA_FLAG_OTHER ? "OTHER" : "none",
3b015d40 593 pref == ND_RA_FLAG_PREF_HIGH ? "high" : pref == ND_RA_FLAG_PREF_LOW ? "low" : "medium",
9d96e6c3 594 lifetime);
09667885 595
9d96e6c3
TG
596 r = ndisc_ra_parse(nd, ra, len);
597 if (r < 0) {
598 log_ndisc(nd, "Could not parse Router Advertisement: %s", strerror(-r));
599 return 0;
09667885 600 }
e3169126 601
9d96e6c3 602 if (nd->router_callback)
d7fa4380 603 nd->router_callback(nd, stateful, gw, lifetime, pref, nd->userdata);
e3169126
PF
604
605 return 0;
606}
607
46ec6687 608static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
4d7b83da 609 sd_ndisc *nd = userdata;
e3169126 610 uint64_t time_now, next_timeout;
e3169126
PF
611 int r;
612
613 assert(s);
614 assert(nd);
615 assert(nd->event);
616
617 nd->timeout = sd_event_source_unref(nd->timeout);
618
46ec6687 619 if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
9d96e6c3
TG
620 if (nd->callback)
621 nd->callback(nd, SD_NDISC_EVENT_TIMEOUT, nd->userdata);
46ec6687 622 nd->state = NDISC_STATE_ADVERTISMENT_LISTEN;
e3169126 623 } else {
6d06ac1f 624 r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
e3169126 625 if (r < 0)
46ec6687 626 log_ndisc(nd, "Error sending Router Solicitation");
e3169126 627 else {
46ec6687
TG
628 nd->state = NDISC_STATE_SOLICITATION_SENT;
629 log_ndisc(nd, "Sent Router Solicitation");
e3169126
PF
630 }
631
632 nd->nd_sent++;
633
cbe91b3c 634 assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
e3169126 635
46ec6687 636 next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
e3169126 637
fa94c34b 638 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
e3169126 639 next_timeout, 0,
46ec6687 640 ndisc_router_solicitation_timeout, nd);
e3169126 641 if (r < 0) {
cb53894d
TG
642 /* we cannot continue if we are unable to rearm the timer */
643 sd_ndisc_stop(nd);
e3169126
PF
644 return 0;
645 }
646
cbe91b3c
TG
647 r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
648 if (r < 0)
e3169126 649 return 0;
9021bb9f 650
46ec6687 651 r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
cbe91b3c 652 if (r < 0)
9021bb9f 653 return 0;
e3169126
PF
654 }
655
656 return 0;
657}
658
4d7b83da 659int sd_ndisc_stop(sd_ndisc *nd) {
836cf090
PF
660 assert_return(nd, -EINVAL);
661 assert_return(nd->event, -EINVAL);
662
46ec6687 663 log_ndisc(client, "Stop NDisc");
836cf090 664
46ec6687 665 ndisc_init(nd);
836cf090 666
46ec6687 667 nd->state = NDISC_STATE_IDLE;
836cf090 668
9d96e6c3
TG
669 if (nd->callback)
670 nd->callback(nd, SD_NDISC_EVENT_STOP, nd->userdata);
cb53894d 671
836cf090
PF
672 return 0;
673}
674
4d7b83da 675int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
e3169126
PF
676 int r;
677
678 assert(nd);
679 assert(nd->event);
680
46ec6687 681 if (nd->state != NDISC_STATE_IDLE)
63348d13 682 return -EBUSY;
e3169126
PF
683
684 if (nd->index < 1)
685 return -EINVAL;
686
940367a0 687 r = icmp6_bind_router_solicitation(nd->index);
e3169126
PF
688 if (r < 0)
689 return r;
690
691 nd->fd = r;
692
693 r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN,
46ec6687 694 ndisc_router_advertisment_recv, nd);
e3169126
PF
695 if (r < 0)
696 goto error;
697
698 r = sd_event_source_set_priority(nd->recv, nd->event_priority);
699 if (r < 0)
700 goto error;
701
46ec6687 702 r = sd_event_source_set_description(nd->recv, "ndisc-receive-message");
9021bb9f
TG
703 if (r < 0)
704 goto error;
705
fa94c34b 706 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
46ec6687 707 0, 0, ndisc_router_solicitation_timeout, nd);
e3169126
PF
708 if (r < 0)
709 goto error;
710
711 r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
9021bb9f
TG
712 if (r < 0)
713 goto error;
e3169126 714
46ec6687 715 r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
e3169126
PF
716error:
717 if (r < 0)
46ec6687 718 ndisc_init(nd);
e3169126 719 else
46ec6687 720 log_ndisc(client, "Start Router Solicitation");
e3169126
PF
721
722 return r;
723}