]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - 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
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/in.h>
22#include <netinet/ip6.h>
23#include <stdbool.h>
24#include <string.h>
25#include <sys/ioctl.h>
26
27#include "sd-ndisc.h"
28
29#include "alloc-util.h"
30#include "async.h"
31#include "icmp6-util.h"
32#include "list.h"
33#include "socket-util.h"
34
35#define NDISC_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC
36#define NDISC_MAX_ROUTER_SOLICITATIONS 3
37
38enum NDiscState {
39 NDISC_STATE_IDLE,
40 NDISC_STATE_SOLICITATION_SENT,
41 NDISC_STATE_ADVERTISMENT_LISTEN,
42 _NDISC_STATE_MAX,
43 _NDISC_STATE_INVALID = -1,
44};
45
46#define IP6_MIN_MTU (unsigned)1280
47#define ICMP6_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
48#define NDISC_OPT_LEN_UNITS 8
49
50typedef struct NDiscPrefix NDiscPrefix;
51
52struct NDiscPrefix {
53 unsigned n_ref;
54
55 sd_ndisc *nd;
56
57 LIST_FIELDS(NDiscPrefix, prefixes);
58
59 uint8_t len;
60 usec_t valid_until;
61 struct in6_addr addr;
62};
63
64struct sd_ndisc {
65 unsigned n_ref;
66
67 enum NDiscState state;
68 sd_event *event;
69 int event_priority;
70 int index;
71 struct ether_addr mac_addr;
72 uint32_t mtu;
73 LIST_HEAD(NDiscPrefix, prefixes);
74 int fd;
75 sd_event_source *recv;
76 sd_event_source *timeout;
77 int nd_sent;
78 sd_ndisc_callback_t callback;
79 void *userdata;
80};
81
82#define log_ndisc(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "NDisc CLIENT: " fmt, ##__VA_ARGS__)
83
84static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) {
85
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;
94
95 if (prefix->nd)
96 LIST_REMOVE(prefixes, prefix->nd->prefixes, prefix);
97
98 free(prefix);
99
100 return NULL;
101}
102
103static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) {
104 _cleanup_free_ NDiscPrefix *prefix = NULL;
105
106 assert(ret);
107
108 prefix = new0(NDiscPrefix, 1);
109 if (!prefix)
110 return -ENOMEM;
111
112 prefix->n_ref = 1;
113 LIST_INIT(prefixes, prefix);
114 prefix->nd = nd;
115
116 *ret = prefix;
117 prefix = NULL;
118
119 return 0;
120}
121
122static void ndisc_notify(sd_ndisc *nd, int event) {
123 if (nd->callback)
124 nd->callback(nd, event, nd->userdata);
125}
126
127int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t callback,
128 void *userdata) {
129 assert(nd);
130
131 nd->callback = callback;
132 nd->userdata = userdata;
133
134 return 0;
135}
136
137int sd_ndisc_set_index(sd_ndisc *nd, int interface_index) {
138 assert(nd);
139 assert(interface_index >= -1);
140
141 nd->index = interface_index;
142
143 return 0;
144}
145
146int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
147 assert(nd);
148
149 if (mac_addr)
150 memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
151 else
152 zero(nd->mac_addr);
153
154 return 0;
155
156}
157
158int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int priority) {
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
177int sd_ndisc_detach_event(sd_ndisc *nd) {
178 assert_return(nd, -EINVAL);
179
180 nd->event = sd_event_unref(nd->event);
181
182 return 0;
183}
184
185sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
186 assert(nd);
187
188 return nd->event;
189}
190
191sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
192
193 if (!nd)
194 return NULL;
195
196 assert(nd->n_ref > 0);
197 nd->n_ref++;
198
199 return nd;
200}
201
202static int ndisc_init(sd_ndisc *nd) {
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
212sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
213 NDiscPrefix *prefix, *p;
214
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;
223
224 ndisc_init(nd);
225 sd_ndisc_detach_event(nd);
226
227 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes)
228 prefix = ndisc_prefix_unref(prefix);
229
230 free(nd);
231
232 return NULL;
233}
234
235DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ndisc*, sd_ndisc_unref);
236#define _cleanup_sd_ndisc_free_ _cleanup_(sd_ndisc_unrefp)
237
238int sd_ndisc_new(sd_ndisc **ret) {
239 _cleanup_sd_ndisc_free_ sd_ndisc *nd = NULL;
240
241 assert(ret);
242
243 nd = new0(sd_ndisc, 1);
244 if (!nd)
245 return -ENOMEM;
246
247 nd->n_ref = 1;
248
249 nd->index = -1;
250 nd->fd = -1;
251
252 LIST_HEAD_INIT(nd->prefixes);
253
254 *ret = nd;
255 nd = NULL;
256
257 return 0;
258}
259
260int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
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
272static int prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
273 const struct in6_addr *addr,
274 uint8_t addr_prefixlen) {
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
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 }
310
311 if (prefix_match(&prefix->addr, prefix->len, addr, addr_len) >= 0) {
312 *result = prefix;
313 return 0;
314 }
315 }
316
317 return -EADDRNOTAVAIL;
318}
319
320static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
321 const struct nd_opt_prefix_info *prefix_opt) {
322 NDiscPrefix *prefix;
323 uint32_t lifetime;
324 usec_t time_now;
325 char time_string[FORMAT_TIMESPAN_MAX];
326 int r;
327
328 assert(nd);
329 assert(prefix_opt);
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
339 r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix,
340 prefix_opt->nd_opt_pi_prefix_len, &prefix);
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) {
349 r = ndisc_prefix_new(nd, &prefix);
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
358 log_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
359 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
360 prefix->len, lifetime,
361 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime * USEC_PER_SEC, USEC_PER_SEC));
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
371 log_ndisc(nd, "Prefix length mismatch %d/%d using %d",
372 prefix->len,
373 prefix_opt->nd_opt_pi_prefix_len,
374 prefixlen);
375
376 prefix->len = prefixlen;
377 }
378
379 log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
380 SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
381 prefix->len, lifetime,
382 format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime * USEC_PER_SEC, USEC_PER_SEC));
383 }
384
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;
390
391 return r;
392}
393
394static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra,
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);
403 if (len < NDISC_OPT_LEN_UNITS) {
404 log_ndisc(nd, "Router Advertisement below minimum length");
405
406 return -ENOMSG;
407 }
408
409 opt = ra + 1;
410 opt_hdr = opt;
411
412 while (len != 0 && len >= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS) {
413 struct nd_opt_mtu *opt_mtu;
414 uint32_t mtu;
415 struct nd_opt_prefix_info *opt_prefix;
416
417 if (opt_hdr->nd_opt_len == 0)
418 return -ENOMSG;
419
420 switch (opt_hdr->nd_opt_type) {
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
429 log_ndisc(nd, "Router Advertisement link MTU %d using %d",
430 mtu, nd->mtu);
431 }
432
433 break;
434
435 case ND_OPT_PREFIX_INFORMATION:
436 opt_prefix = opt;
437
438 ndisc_prefix_update(nd, len, opt_prefix);
439
440 break;
441 }
442
443 len -= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS;
444 opt = (void *)((char *)opt +
445 opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS);
446 opt_hdr = opt;
447 }
448
449 if (len > 0)
450 log_ndisc(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
451
452 return 0;
453}
454
455static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
456 sd_ndisc *nd = userdata;
457 int r, buflen = 0;
458 ssize_t len;
459 _cleanup_free_ struct nd_router_advert *ra = NULL;
460 int event = SD_NDISC_EVENT_ROUTER_ADVERTISMENT_NONE;
461
462 assert(s);
463 assert(nd);
464 assert(nd->event);
465
466 r = ioctl(fd, FIONREAD, &buflen);
467 if (r < 0 || buflen <= 0)
468 buflen = ICMP6_RECV_SIZE;
469
470 ra = malloc(buflen);
471 if (!ra)
472 return -ENOMEM;
473
474 len = read(fd, ra, buflen);
475 if (len < 0) {
476 log_ndisc(nd, "Could not receive message from UDP socket: %m");
477 return 0;
478 }
479
480 if (ra->nd_ra_type != ND_ROUTER_ADVERT)
481 return 0;
482
483 if (ra->nd_ra_code != 0)
484 return 0;
485
486 nd->timeout = sd_event_source_unref(nd->timeout);
487
488 nd->state = NDISC_STATE_ADVERTISMENT_LISTEN;
489
490 if (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
491 event = SD_NDISC_EVENT_ROUTER_ADVERTISMENT_OTHER;
492
493 if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
494 event = SD_NDISC_EVENT_ROUTER_ADVERTISMENT_MANAGED;
495
496 log_ndisc(nd, "Received Router Advertisement flags %s/%s",
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
500 if (event != SD_NDISC_EVENT_ROUTER_ADVERTISMENT_NONE) {
501 r = ndisc_ra_parse(nd, ra, len);
502 if (r < 0) {
503 log_ndisc(nd, "Could not parse Router Advertisement: %s",
504 strerror(-r));
505 return 0;
506 }
507 }
508
509 ndisc_notify(nd, event);
510
511 return 0;
512}
513
514static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
515 sd_ndisc *nd = userdata;
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
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;
530 } else {
531 if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr)))
532 addr = &nd->mac_addr;
533
534 r = icmp6_send_router_solicitation(nd->fd, addr);
535 if (r < 0)
536 log_ndisc(nd, "Error sending Router Solicitation");
537 else {
538 nd->state = NDISC_STATE_SOLICITATION_SENT;
539 log_ndisc(nd, "Sent Router Solicitation");
540 }
541
542 nd->nd_sent++;
543
544 assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
545
546 next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
547
548 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
549 next_timeout, 0,
550 ndisc_router_solicitation_timeout, nd);
551 if (r < 0) {
552 ndisc_notify(nd, r);
553 return 0;
554 }
555
556 r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
557 if (r < 0)
558 return 0;
559
560 r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
561 if (r < 0)
562 return 0;
563 }
564
565 return 0;
566}
567
568int sd_ndisc_stop(sd_ndisc *nd) {
569 assert_return(nd, -EINVAL);
570 assert_return(nd->event, -EINVAL);
571
572 log_ndisc(client, "Stop NDisc");
573
574 ndisc_init(nd);
575
576 nd->state = NDISC_STATE_IDLE;
577
578 return 0;
579}
580
581int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
582 int r;
583
584 assert(nd);
585 assert(nd->event);
586
587 if (nd->state != NDISC_STATE_IDLE)
588 return -EINVAL;
589
590 if (nd->index < 1)
591 return -EINVAL;
592
593 r = icmp6_bind_router_solicitation(nd->index);
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,
600 ndisc_router_advertisment_recv, nd);
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
608 r = sd_event_source_set_description(nd->recv, "ndisc-receive-message");
609 if (r < 0)
610 goto error;
611
612 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
613 0, 0, ndisc_router_solicitation_timeout, nd);
614 if (r < 0)
615 goto error;
616
617 r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
618 if (r < 0)
619 goto error;
620
621 r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
622error:
623 if (r < 0)
624 ndisc_init(nd);
625 else
626 log_ndisc(client, "Start Router Solicitation");
627
628 return r;
629}