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