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