chiark / gitweb /
85b8ff93c57ff0596dfb8f488788379c7ca12198
[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 static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
241                           ssize_t len) {
242         void *opt;
243         struct nd_opt_hdr *opt_hdr;
244
245         assert_return(nd, -EINVAL);
246         assert_return(ra, -EINVAL);
247
248         len -= sizeof(*ra);
249         if (len < ICMP6_OPT_LEN_UNITS) {
250                 log_icmp6_nd(nd, "Router Advertisement below minimum length");
251
252                 return -ENOMSG;
253         }
254
255         opt = ra + 1;
256         opt_hdr = opt;
257
258         while (len != 0 && len >= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS) {
259
260                 if (opt_hdr->nd_opt_len == 0)
261                         return -ENOMSG;
262
263                 switch (opt_hdr->nd_opt_type) {
264
265                 }
266
267                 len -= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS;
268                 opt = (void *)((char *)opt +
269                         opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS);
270                 opt_hdr = opt;
271         }
272
273         if (len > 0)
274                 log_icmp6_nd(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
275
276         return 0;
277 }
278
279 static int icmp6_router_advertisment_recv(sd_event_source *s, int fd,
280                                           uint32_t revents, void *userdata)
281 {
282         sd_icmp6_nd *nd = userdata;
283         int r, buflen = 0;
284         ssize_t len;
285         _cleanup_free_ struct nd_router_advert *ra = NULL;
286         int event = ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE;
287
288         assert(s);
289         assert(nd);
290         assert(nd->event);
291
292         r = ioctl(fd, FIONREAD, &buflen);
293         if (r < 0 || buflen <= 0)
294                 buflen = ICMP6_ND_RECV_SIZE;
295
296         ra = malloc(buflen);
297         if (!ra)
298                 return -ENOMEM;
299
300         len = read(fd, ra, buflen);
301         if (len < 0) {
302                 log_icmp6_nd(nd, "Could not receive message from UDP socket: %m");
303                 return 0;
304         }
305
306         if (ra->nd_ra_type != ND_ROUTER_ADVERT)
307                 return 0;
308
309         if (ra->nd_ra_code != 0)
310                 return 0;
311
312         nd->timeout = sd_event_source_unref(nd->timeout);
313
314         nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
315
316         if (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
317                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER;
318
319         if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
320                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED;
321
322         log_icmp6_nd(nd, "Received Router Advertisement flags %s/%s",
323                      ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED? "MANAGED": "none",
324                      ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER? "OTHER": "none");
325
326         if (event != ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE) {
327                 r = icmp6_ra_parse(nd, ra, len);
328                 if (r < 0) {
329                         log_icmp6_nd(nd, "Could not parse Router Advertisement: %s",
330                                      strerror(-r));
331                         return 0;
332                 }
333         }
334
335         icmp6_nd_notify(nd, event);
336
337         return 0;
338 }
339
340 static int icmp6_router_solicitation_timeout(sd_event_source *s, uint64_t usec,
341                                              void *userdata)
342 {
343         sd_icmp6_nd *nd = userdata;
344         uint64_t time_now, next_timeout;
345         struct ether_addr unset = { };
346         struct ether_addr *addr = NULL;
347         int r;
348
349         assert(s);
350         assert(nd);
351         assert(nd->event);
352
353         nd->timeout = sd_event_source_unref(nd->timeout);
354
355         if (nd->nd_sent >= ICMP6_MAX_ROUTER_SOLICITATIONS) {
356                 icmp6_nd_notify(nd, ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT);
357                 nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
358         } else {
359                 if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr)))
360                         addr = &nd->mac_addr;
361
362                 r = dhcp_network_icmp6_send_router_solicitation(nd->fd, addr);
363                 if (r < 0)
364                         log_icmp6_nd(nd, "Error sending Router Solicitation");
365                 else {
366                         nd->state = ICMP6_ROUTER_SOLICITATION_SENT;
367                         log_icmp6_nd(nd, "Sent Router Solicitation");
368                 }
369
370                 nd->nd_sent++;
371
372                 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
373                 if (r < 0) {
374                         icmp6_nd_notify(nd, r);
375                         return 0;
376                 }
377
378                 next_timeout = time_now + ICMP6_ROUTER_SOLICITATION_INTERVAL;
379
380                 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
381                                       next_timeout, 0,
382                                       icmp6_router_solicitation_timeout, nd);
383                 if (r < 0) {
384                         icmp6_nd_notify(nd, r);
385                         return 0;
386                 }
387
388                 r = sd_event_source_set_priority(nd->timeout,
389                                                  nd->event_priority);
390                 if (r < 0) {
391                         icmp6_nd_notify(nd, r);
392                         return 0;
393                 }
394
395                 r = sd_event_source_set_description(nd->timeout, "icmp6-timeout");
396                 if (r < 0) {
397                         icmp6_nd_notify(nd, r);
398                         return 0;
399                 }
400         }
401
402         return 0;
403 }
404
405 int sd_icmp6_nd_stop(sd_icmp6_nd *nd) {
406         assert_return(nd, -EINVAL);
407         assert_return(nd->event, -EINVAL);
408
409         log_icmp6_nd(client, "Stop ICMPv6");
410
411         icmp6_nd_init(nd);
412
413         nd->state = ICMP6_NEIGHBOR_DISCOVERY_IDLE;
414
415         return 0;
416 }
417
418 int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd) {
419         int r;
420
421         assert(nd);
422         assert(nd->event);
423
424         if (nd->state != ICMP6_NEIGHBOR_DISCOVERY_IDLE)
425                 return -EINVAL;
426
427         if (nd->index < 1)
428                 return -EINVAL;
429
430         r = dhcp_network_icmp6_bind_router_solicitation(nd->index);
431         if (r < 0)
432                 return r;
433
434         nd->fd = r;
435
436         r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN,
437                             icmp6_router_advertisment_recv, nd);
438         if (r < 0)
439                 goto error;
440
441         r = sd_event_source_set_priority(nd->recv, nd->event_priority);
442         if (r < 0)
443                 goto error;
444
445         r = sd_event_source_set_description(nd->recv, "icmp6-receive-message");
446         if (r < 0)
447                 goto error;
448
449         r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
450                               0, 0, icmp6_router_solicitation_timeout, nd);
451         if (r < 0)
452                 goto error;
453
454         r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
455         if (r < 0)
456                 goto error;
457
458         r = sd_event_source_set_description(nd->timeout, "icmp6-timeout");
459 error:
460         if (r < 0)
461                 icmp6_nd_init(nd);
462         else
463                 log_icmp6_nd(client, "Start Router Solicitation");
464
465         return r;
466 }