]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-icmp6-nd.c
sd-icmp6-nd: Add helper function to get the IPv6 link MTU
[thirdparty/systemd.git] / src / libsystemd-network / sd-icmp6-nd.c
CommitLineData
e3169126
PF
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>
09667885 21#include <netinet/ip6.h>
e3169126
PF
22#include <string.h>
23#include <stdbool.h>
24#include <netinet/in.h>
09667885 25#include <sys/ioctl.h>
e3169126
PF
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
37enum icmp6_nd_state {
38 ICMP6_NEIGHBOR_DISCOVERY_IDLE = 0,
39 ICMP6_ROUTER_SOLICITATION_SENT = 10,
40 ICMP6_ROUTER_ADVERTISMENT_LISTEN = 11,
41};
42
09667885
PF
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
5624c480
PF
47typedef struct ICMP6Prefix ICMP6Prefix;
48
49struct 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
e3169126
PF
59struct 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;
5624c480
PF
67 uint32_t mtu;
68 LIST_HEAD(ICMP6Prefix, prefixes);
e3169126
PF
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
79008bdd 77#define log_icmp6_nd(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "ICMPv6 CLIENT: " fmt, ##__VA_ARGS__)
e3169126 78
5624c480
PF
79static 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
90static 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
e3169126
PF
108static void icmp6_nd_notify(sd_icmp6_nd *nd, int event)
109{
110 if (nd->callback)
111 nd->callback(nd, event, nd->userdata);
112}
113
114int 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
124int 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
133int 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
eccaf899 139 zero(nd->mac_addr);
e3169126
PF
140
141 return 0;
142
143}
144
145int 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
164int 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
172sd_event *sd_icmp6_nd_get_event(sd_icmp6_nd *nd) {
173 assert(nd);
174
175 return nd->event;
176}
177
178sd_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
186static 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
196sd_icmp6_nd *sd_icmp6_nd_unref(sd_icmp6_nd *nd) {
f0c4b1c3 197 if (nd && REFCNT_DEC(nd->n_ref) == 0) {
5624c480 198 ICMP6Prefix *prefix, *p;
e3169126
PF
199
200 icmp6_nd_init(nd);
201 sd_icmp6_nd_detach_event(nd);
202
5624c480
PF
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
e3169126
PF
209 free(nd);
210 }
211
212 return NULL;
213}
214
215DEFINE_TRIVIAL_CLEANUP_FUNC(sd_icmp6_nd*, sd_icmp6_nd_unref);
216#define _cleanup_sd_icmp6_nd_free_ _cleanup_(sd_icmp6_nd_unrefp)
217
218int 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;
03de7ed9 230 nd->fd = -1;
e3169126 231
5624c480
PF
232 LIST_HEAD_INIT(nd->prefixes);
233
e3169126
PF
234 *ret = nd;
235 nd = NULL;
236
237 return 0;
238}
239
d14b5bc6
PF
240int 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
09667885
PF
252static 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) {
d14b5bc6
PF
271 struct nd_opt_mtu *opt_mtu;
272 uint32_t mtu;
09667885
PF
273
274 if (opt_hdr->nd_opt_len == 0)
275 return -ENOMSG;
276
277 switch (opt_hdr->nd_opt_type) {
d14b5bc6
PF
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;
09667885
PF
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
e3169126
PF
306static int icmp6_router_advertisment_recv(sd_event_source *s, int fd,
307 uint32_t revents, void *userdata)
308{
309 sd_icmp6_nd *nd = userdata;
09667885 310 int r, buflen = 0;
e3169126 311 ssize_t len;
09667885 312 _cleanup_free_ struct nd_router_advert *ra = NULL;
e3169126
PF
313 int event = ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE;
314
315 assert(s);
316 assert(nd);
317 assert(nd->event);
318
09667885
PF
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");
e3169126 330 return 0;
09667885 331 }
e3169126 332
09667885 333 if (ra->nd_ra_type != ND_ROUTER_ADVERT)
e3169126
PF
334 return 0;
335
09667885 336 if (ra->nd_ra_code != 0)
e3169126
PF
337 return 0;
338
339 nd->timeout = sd_event_source_unref(nd->timeout);
340
341 nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
342
09667885 343 if (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
e3169126
PF
344 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER;
345
09667885 346 if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
e3169126
PF
347 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED;
348
06b643e7 349 log_icmp6_nd(nd, "Received Router Advertisement flags %s/%s",
09667885
PF
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 }
e3169126
PF
361
362 icmp6_nd_notify(nd, event);
363
364 return 0;
365}
366
367static 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
fa94c34b 399 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
e3169126
PF
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
fa94c34b 407 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
e3169126
PF
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 }
9021bb9f 421
356779df 422 r = sd_event_source_set_description(nd->timeout, "icmp6-timeout");
9021bb9f
TG
423 if (r < 0) {
424 icmp6_nd_notify(nd, r);
425 return 0;
426 }
e3169126
PF
427 }
428
429 return 0;
430}
431
836cf090
PF
432int 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
e3169126
PF
445int 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
356779df 472 r = sd_event_source_set_description(nd->recv, "icmp6-receive-message");
9021bb9f
TG
473 if (r < 0)
474 goto error;
475
fa94c34b 476 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
e3169126
PF
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);
9021bb9f
TG
482 if (r < 0)
483 goto error;
e3169126 484
356779df 485 r = sd_event_source_set_description(nd->timeout, "icmp6-timeout");
e3169126
PF
486error:
487 if (r < 0)
488 icmp6_nd_init(nd);
489 else
490 log_icmp6_nd(client, "Start Router Solicitation");
491
492 return r;
493}