chiark / gitweb /
sd-icmp6-nd: Parse ICMPv6 prefix information
[elogind.git] / src / libsystemd-network / sd-icmp6-nd.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 "socket-util.h"
28 #include "refcnt.h"
29 #include "async.h"
30
31 #include "dhcp6-internal.h"
32 #include "sd-icmp6-nd.h"
33
34 #define ICMP6_ROUTER_SOLICITATION_INTERVAL      4 * USEC_PER_SEC
35 #define ICMP6_MAX_ROUTER_SOLICITATIONS          3
36
37 enum icmp6_nd_state {
38         ICMP6_NEIGHBOR_DISCOVERY_IDLE           = 0,
39         ICMP6_ROUTER_SOLICITATION_SENT          = 10,
40         ICMP6_ROUTER_ADVERTISMENT_LISTEN        = 11,
41 };
42
43 #define IP6_MIN_MTU (unsigned)1280
44 #define ICMP6_ND_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
45 #define ICMP6_OPT_LEN_UNITS 8
46
47 typedef struct ICMP6Prefix ICMP6Prefix;
48
49 struct ICMP6Prefix {
50         RefCount n_ref;
51
52         LIST_FIELDS(ICMP6Prefix, prefixes);
53
54         uint8_t len;
55         sd_event_source *timeout_valid;
56         struct in6_addr addr;
57 };
58
59 struct sd_icmp6_nd {
60         RefCount n_ref;
61
62         enum icmp6_nd_state state;
63         sd_event *event;
64         int event_priority;
65         int index;
66         struct ether_addr mac_addr;
67         uint32_t mtu;
68         LIST_HEAD(ICMP6Prefix, prefixes);
69         int fd;
70         sd_event_source *recv;
71         sd_event_source *timeout;
72         int nd_sent;
73         sd_icmp6_nd_callback_t callback;
74         void *userdata;
75 };
76
77 #define log_icmp6_nd(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "ICMPv6 CLIENT: " fmt, ##__VA_ARGS__)
78
79 static ICMP6Prefix *icmp6_prefix_unref(ICMP6Prefix *prefix) {
80         if (prefix && REFCNT_DEC(prefix->n_ref) <= 0) {
81                 prefix->timeout_valid =
82                         sd_event_source_unref(prefix->timeout_valid);
83
84                 free(prefix);
85         }
86
87         return NULL;
88 }
89
90 static int icmp6_prefix_new(ICMP6Prefix **ret) {
91         _cleanup_free_ ICMP6Prefix *prefix = NULL;
92
93         assert(ret);
94
95         prefix = new0(ICMP6Prefix, 1);
96         if (!prefix)
97                 return -ENOMEM;
98
99         prefix->n_ref = REFCNT_INIT;
100         LIST_INIT(prefixes, prefix);
101
102         *ret = prefix;
103         prefix = NULL;
104
105         return 0;
106 }
107
108 static void icmp6_nd_notify(sd_icmp6_nd *nd, int event)
109 {
110         if (nd->callback)
111                 nd->callback(nd, event, nd->userdata);
112 }
113
114 int sd_icmp6_nd_set_callback(sd_icmp6_nd *nd, sd_icmp6_nd_callback_t callback,
115                              void *userdata) {
116         assert(nd);
117
118         nd->callback = callback;
119         nd->userdata = userdata;
120
121         return 0;
122 }
123
124 int sd_icmp6_nd_set_index(sd_icmp6_nd *nd, int interface_index) {
125         assert(nd);
126         assert(interface_index >= -1);
127
128         nd->index = interface_index;
129
130         return 0;
131 }
132
133 int sd_icmp6_nd_set_mac(sd_icmp6_nd *nd, const struct ether_addr *mac_addr) {
134         assert(nd);
135
136         if (mac_addr)
137                 memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
138         else
139                 zero(nd->mac_addr);
140
141         return 0;
142
143 }
144
145 int sd_icmp6_nd_attach_event(sd_icmp6_nd *nd, sd_event *event, int priority) {
146         int r;
147
148         assert_return(nd, -EINVAL);
149         assert_return(!nd->event, -EBUSY);
150
151         if (event)
152                 nd->event = sd_event_ref(event);
153         else {
154                 r = sd_event_default(&nd->event);
155                 if (r < 0)
156                         return 0;
157         }
158
159         nd->event_priority = priority;
160
161         return 0;
162 }
163
164 int sd_icmp6_nd_detach_event(sd_icmp6_nd *nd) {
165         assert_return(nd, -EINVAL);
166
167         nd->event = sd_event_unref(nd->event);
168
169         return 0;
170 }
171
172 sd_event *sd_icmp6_nd_get_event(sd_icmp6_nd *nd) {
173         assert(nd);
174
175         return nd->event;
176 }
177
178 sd_icmp6_nd *sd_icmp6_nd_ref(sd_icmp6_nd *nd) {
179         assert (nd);
180
181         assert_se(REFCNT_INC(nd->n_ref) >= 2);
182
183         return nd;
184 }
185
186 static int icmp6_nd_init(sd_icmp6_nd *nd) {
187         assert(nd);
188
189         nd->recv = sd_event_source_unref(nd->recv);
190         nd->fd = asynchronous_close(nd->fd);
191         nd->timeout = sd_event_source_unref(nd->timeout);
192
193         return 0;
194 }
195
196 sd_icmp6_nd *sd_icmp6_nd_unref(sd_icmp6_nd *nd) {
197         if (nd && REFCNT_DEC(nd->n_ref) == 0) {
198                 ICMP6Prefix *prefix, *p;
199
200                 icmp6_nd_init(nd);
201                 sd_icmp6_nd_detach_event(nd);
202
203                 LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
204                         LIST_REMOVE(prefixes, nd->prefixes, prefix);
205
206                         prefix = icmp6_prefix_unref(prefix);
207                 }
208
209                 free(nd);
210         }
211
212         return NULL;
213 }
214
215 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_icmp6_nd*, sd_icmp6_nd_unref);
216 #define _cleanup_sd_icmp6_nd_free_ _cleanup_(sd_icmp6_nd_unrefp)
217
218 int sd_icmp6_nd_new(sd_icmp6_nd **ret) {
219         _cleanup_sd_icmp6_nd_free_ sd_icmp6_nd *nd = NULL;
220
221         assert(ret);
222
223         nd = new0(sd_icmp6_nd, 1);
224         if (!nd)
225                 return -ENOMEM;
226
227         nd->n_ref = REFCNT_INIT;
228
229         nd->index = -1;
230         nd->fd = -1;
231
232         LIST_HEAD_INIT(nd->prefixes);
233
234         *ret = nd;
235         nd = NULL;
236
237         return 0;
238 }
239
240 int sd_icmp6_ra_get_mtu(sd_icmp6_nd *nd, uint32_t *mtu) {
241         assert_return(nd, -EINVAL);
242         assert_return(mtu, -EINVAL);
243
244         if (nd->mtu == 0)
245                 return -ENOMSG;
246
247         *mtu = nd->mtu;
248
249         return 0;
250 }
251
252 static int icmp6_ra_prefix_timeout(sd_event_source *s, uint64_t usec,
253                                    void *userdata) {
254         sd_icmp6_nd *nd = userdata;
255         ICMP6Prefix *prefix, *p;
256
257         assert(nd);
258
259         LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
260                 if (prefix->timeout_valid != s)
261                         continue;
262
263                 log_icmp6_nd(nd, "Prefix expired "SD_ICMP6_ADDRESS_FORMAT_STR"/%d",
264                              SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr),
265                              prefix->len);
266
267                 LIST_REMOVE(prefixes, nd->prefixes, prefix);
268
269                 icmp6_nd_notify(nd,
270                                 ICMP6_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED);
271                 prefix = icmp6_prefix_unref(prefix);
272
273                 break;
274         }
275
276         return 0;
277 }
278
279 static int icmp6_ra_prefix_set_timeout(sd_icmp6_nd *nd,
280                                        ICMP6Prefix *prefix,
281                                        usec_t valid) {
282         usec_t time_now;
283         int r;
284
285         assert_return(prefix, -EINVAL);
286
287         r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
288         if (r < 0)
289                 return r;
290
291         prefix->timeout_valid = sd_event_source_unref(prefix->timeout_valid);
292
293         r = sd_event_add_time(nd->event, &prefix->timeout_valid,
294                         clock_boottime_or_monotonic(), time_now + valid,
295                         USEC_PER_SEC, icmp6_ra_prefix_timeout, nd);
296         if (r < 0)
297                 goto error;
298
299         r = sd_event_source_set_priority(prefix->timeout_valid,
300                                         nd->event_priority);
301         if (r < 0)
302                 goto error;
303
304         r = sd_event_source_set_description(prefix->timeout_valid,
305                                         "icmp6-prefix-timeout");
306
307 error:
308         if (r < 0)
309                 prefix->timeout_valid =
310                         sd_event_source_unref(prefix->timeout_valid);
311
312         return r;
313 }
314
315 static int icmp6_prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
316                               const struct in6_addr *addr,
317                               uint8_t addr_prefixlen) {
318         uint8_t bytes, mask, len;
319
320         assert_return(prefix, -EINVAL);
321         assert_return(addr, -EINVAL);
322
323         len = MIN(prefixlen, addr_prefixlen);
324
325         bytes = len / 8;
326         mask = 0xff << (8 - len % 8);
327
328         if (memcmp(prefix, addr, bytes) != 0 ||
329             (prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask))
330                 return -EADDRNOTAVAIL;
331
332         return 0;
333 }
334
335 static int icmp6_ra_prefix_match(ICMP6Prefix *head, const struct in6_addr *addr,
336                                  uint8_t addr_len, ICMP6Prefix **result) {
337         ICMP6Prefix *prefix;
338
339         LIST_FOREACH(prefixes, prefix, head) {
340                 if (icmp6_prefix_match(&prefix->addr, prefix->len, addr,
341                                        addr_len) >= 0) {
342                         *result = prefix;
343                         return 0;
344                 }
345         }
346
347         return -EADDRNOTAVAIL;
348 }
349
350 int sd_icmp6_prefix_match(struct in6_addr *prefix, uint8_t prefixlen,
351                           struct in6_addr *addr) {
352         return icmp6_prefix_match(prefix, prefixlen, addr,
353                                   sizeof(addr->s6_addr) * 8);
354 }
355
356 int sd_icmp6_ra_get_prefixlen(sd_icmp6_nd *nd, const struct in6_addr *addr,
357                               uint8_t *prefixlen) {
358         int r;
359         ICMP6Prefix *prefix;
360
361         assert_return(nd, -EINVAL);
362         assert_return(addr, -EINVAL);
363         assert_return(prefixlen, -EINVAL);
364
365         r = icmp6_ra_prefix_match(nd->prefixes, addr,
366                                   sizeof(addr->s6_addr) * 8, &prefix);
367         if (r < 0)
368                 return r;
369
370         *prefixlen = prefix->len;
371
372         return 0;
373 }
374
375 static int icmp6_ra_prefix_update(sd_icmp6_nd *nd, ssize_t len,
376                                   const struct nd_opt_prefix_info *prefix_opt) {
377         int r;
378         ICMP6Prefix *prefix;
379         uint32_t lifetime;
380         char time_string[FORMAT_TIMESPAN_MAX];
381
382         assert_return(nd, -EINVAL);
383         assert_return(prefix_opt, -EINVAL);
384
385         if (len < prefix_opt->nd_opt_pi_len)
386                 return -ENOMSG;
387
388         if (!(prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK))
389                 return 0;
390
391         lifetime = be32toh(prefix_opt->nd_opt_pi_valid_time);
392
393         r = icmp6_ra_prefix_match(nd->prefixes,
394                                   &prefix_opt->nd_opt_pi_prefix,
395                                   prefix_opt->nd_opt_pi_prefix_len, &prefix);
396
397         if (r < 0 && r != -EADDRNOTAVAIL)
398                 return r;
399
400         /* if router advertisment prefix valid timeout is zero, the timeout
401            callback will be called immediately to clean up the prefix */
402
403         if (r == -EADDRNOTAVAIL) {
404                 r = icmp6_prefix_new(&prefix);
405                 if (r < 0)
406                         return r;
407
408                 prefix->len = prefix_opt->nd_opt_pi_prefix_len;
409
410                 memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
411                         sizeof(prefix->addr));
412
413                 log_icmp6_nd(nd, "New prefix "SD_ICMP6_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
414                              SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr),
415                              prefix->len, lifetime,
416                              format_timespan(time_string, FORMAT_TIMESPAN_MAX,
417                                              lifetime * USEC_PER_SEC, 0));
418
419                 LIST_PREPEND(prefixes, nd->prefixes, prefix);
420
421         } else {
422                 if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
423                         uint8_t prefixlen;
424
425                         prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
426
427                         log_icmp6_nd(nd, "Prefix length mismatch %d/%d using %d",
428                                      prefix->len,
429                                      prefix_opt->nd_opt_pi_prefix_len,
430                                      prefixlen);
431
432                         prefix->len = prefixlen;
433                 }
434
435                 log_icmp6_nd(nd, "Update prefix "SD_ICMP6_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
436                              SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr),
437                              prefix->len, lifetime,
438                              format_timespan(time_string, FORMAT_TIMESPAN_MAX,
439                                              lifetime * USEC_PER_SEC, 0));
440         }
441
442         r = icmp6_ra_prefix_set_timeout(nd, prefix, lifetime * USEC_PER_SEC);
443
444         return r;
445 }
446
447 static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
448                           ssize_t len) {
449         void *opt;
450         struct nd_opt_hdr *opt_hdr;
451
452         assert_return(nd, -EINVAL);
453         assert_return(ra, -EINVAL);
454
455         len -= sizeof(*ra);
456         if (len < ICMP6_OPT_LEN_UNITS) {
457                 log_icmp6_nd(nd, "Router Advertisement below minimum length");
458
459                 return -ENOMSG;
460         }
461
462         opt = ra + 1;
463         opt_hdr = opt;
464
465         while (len != 0 && len >= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS) {
466                 struct nd_opt_mtu *opt_mtu;
467                 uint32_t mtu;
468                 struct nd_opt_prefix_info *opt_prefix;
469
470                 if (opt_hdr->nd_opt_len == 0)
471                         return -ENOMSG;
472
473                 switch (opt_hdr->nd_opt_type) {
474                 case ND_OPT_MTU:
475                         opt_mtu = opt;
476
477                         mtu = be32toh(opt_mtu->nd_opt_mtu_mtu);
478
479                         if (mtu != nd->mtu) {
480                                 nd->mtu = MAX(mtu, IP6_MIN_MTU);
481
482                                 log_icmp6_nd(nd, "Router Advertisement link MTU %d using %d",
483                                              mtu, nd->mtu);
484                         }
485
486                         break;
487
488                 case ND_OPT_PREFIX_INFORMATION:
489                         opt_prefix = opt;
490
491                         icmp6_ra_prefix_update(nd, len, opt_prefix);
492
493                         break;
494                 }
495
496                 len -= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS;
497                 opt = (void *)((char *)opt +
498                         opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS);
499                 opt_hdr = opt;
500         }
501
502         if (len > 0)
503                 log_icmp6_nd(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
504
505         return 0;
506 }
507
508 static int icmp6_router_advertisment_recv(sd_event_source *s, int fd,
509                                           uint32_t revents, void *userdata)
510 {
511         sd_icmp6_nd *nd = userdata;
512         int r, buflen = 0;
513         ssize_t len;
514         _cleanup_free_ struct nd_router_advert *ra = NULL;
515         int event = ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE;
516
517         assert(s);
518         assert(nd);
519         assert(nd->event);
520
521         r = ioctl(fd, FIONREAD, &buflen);
522         if (r < 0 || buflen <= 0)
523                 buflen = ICMP6_ND_RECV_SIZE;
524
525         ra = malloc(buflen);
526         if (!ra)
527                 return -ENOMEM;
528
529         len = read(fd, ra, buflen);
530         if (len < 0) {
531                 log_icmp6_nd(nd, "Could not receive message from UDP socket: %m");
532                 return 0;
533         }
534
535         if (ra->nd_ra_type != ND_ROUTER_ADVERT)
536                 return 0;
537
538         if (ra->nd_ra_code != 0)
539                 return 0;
540
541         nd->timeout = sd_event_source_unref(nd->timeout);
542
543         nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
544
545         if (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
546                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER;
547
548         if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
549                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED;
550
551         log_icmp6_nd(nd, "Received Router Advertisement flags %s/%s",
552                      ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED? "MANAGED": "none",
553                      ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER? "OTHER": "none");
554
555         if (event != ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE) {
556                 r = icmp6_ra_parse(nd, ra, len);
557                 if (r < 0) {
558                         log_icmp6_nd(nd, "Could not parse Router Advertisement: %s",
559                                      strerror(-r));
560                         return 0;
561                 }
562         }
563
564         icmp6_nd_notify(nd, event);
565
566         return 0;
567 }
568
569 static int icmp6_router_solicitation_timeout(sd_event_source *s, uint64_t usec,
570                                              void *userdata)
571 {
572         sd_icmp6_nd *nd = userdata;
573         uint64_t time_now, next_timeout;
574         struct ether_addr unset = { };
575         struct ether_addr *addr = NULL;
576         int r;
577
578         assert(s);
579         assert(nd);
580         assert(nd->event);
581
582         nd->timeout = sd_event_source_unref(nd->timeout);
583
584         if (nd->nd_sent >= ICMP6_MAX_ROUTER_SOLICITATIONS) {
585                 icmp6_nd_notify(nd, ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT);
586                 nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
587         } else {
588                 if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr)))
589                         addr = &nd->mac_addr;
590
591                 r = dhcp_network_icmp6_send_router_solicitation(nd->fd, addr);
592                 if (r < 0)
593                         log_icmp6_nd(nd, "Error sending Router Solicitation");
594                 else {
595                         nd->state = ICMP6_ROUTER_SOLICITATION_SENT;
596                         log_icmp6_nd(nd, "Sent Router Solicitation");
597                 }
598
599                 nd->nd_sent++;
600
601                 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
602                 if (r < 0) {
603                         icmp6_nd_notify(nd, r);
604                         return 0;
605                 }
606
607                 next_timeout = time_now + ICMP6_ROUTER_SOLICITATION_INTERVAL;
608
609                 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
610                                       next_timeout, 0,
611                                       icmp6_router_solicitation_timeout, nd);
612                 if (r < 0) {
613                         icmp6_nd_notify(nd, r);
614                         return 0;
615                 }
616
617                 r = sd_event_source_set_priority(nd->timeout,
618                                                  nd->event_priority);
619                 if (r < 0) {
620                         icmp6_nd_notify(nd, r);
621                         return 0;
622                 }
623
624                 r = sd_event_source_set_description(nd->timeout, "icmp6-timeout");
625                 if (r < 0) {
626                         icmp6_nd_notify(nd, r);
627                         return 0;
628                 }
629         }
630
631         return 0;
632 }
633
634 int sd_icmp6_nd_stop(sd_icmp6_nd *nd) {
635         assert_return(nd, -EINVAL);
636         assert_return(nd->event, -EINVAL);
637
638         log_icmp6_nd(client, "Stop ICMPv6");
639
640         icmp6_nd_init(nd);
641
642         nd->state = ICMP6_NEIGHBOR_DISCOVERY_IDLE;
643
644         return 0;
645 }
646
647 int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd) {
648         int r;
649
650         assert(nd);
651         assert(nd->event);
652
653         if (nd->state != ICMP6_NEIGHBOR_DISCOVERY_IDLE)
654                 return -EINVAL;
655
656         if (nd->index < 1)
657                 return -EINVAL;
658
659         r = dhcp_network_icmp6_bind_router_solicitation(nd->index);
660         if (r < 0)
661                 return r;
662
663         nd->fd = r;
664
665         r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN,
666                             icmp6_router_advertisment_recv, nd);
667         if (r < 0)
668                 goto error;
669
670         r = sd_event_source_set_priority(nd->recv, nd->event_priority);
671         if (r < 0)
672                 goto error;
673
674         r = sd_event_source_set_description(nd->recv, "icmp6-receive-message");
675         if (r < 0)
676                 goto error;
677
678         r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
679                               0, 0, icmp6_router_solicitation_timeout, nd);
680         if (r < 0)
681                 goto error;
682
683         r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
684         if (r < 0)
685                 goto error;
686
687         r = sd_event_source_set_description(nd->timeout, "icmp6-timeout");
688 error:
689         if (r < 0)
690                 icmp6_nd_init(nd);
691         else
692                 log_icmp6_nd(client, "Start Router Solicitation");
693
694         return r;
695 }