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