]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-ndisc.c
libsystemd-network: make constant addresses type-safe
[thirdparty/systemd.git] / src / libsystemd-network / sd-ndisc.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /***
3 Copyright © 2014 Intel Corporation. All rights reserved.
4 ***/
5
6 #include <netinet/icmp6.h>
7 #include <netinet/in.h>
8
9 #include "sd-ndisc.h"
10
11 #include "alloc-util.h"
12 #include "ether-addr-util.h"
13 #include "event-util.h"
14 #include "fd-util.h"
15 #include "icmp6-util.h"
16 #include "in-addr-util.h"
17 #include "memory-util.h"
18 #include "ndisc-internal.h"
19 #include "ndisc-neighbor-internal.h"
20 #include "ndisc-redirect-internal.h"
21 #include "ndisc-router-internal.h"
22 #include "network-common.h"
23 #include "random-util.h"
24 #include "socket-util.h"
25 #include "string-table.h"
26 #include "string-util.h"
27
28 #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
29
30 static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = {
31 [SD_NDISC_EVENT_TIMEOUT] = "timeout",
32 [SD_NDISC_EVENT_ROUTER] = "router",
33 [SD_NDISC_EVENT_NEIGHBOR] = "neighbor",
34 [SD_NDISC_EVENT_REDIRECT] = "redirect",
35 };
36
37 DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t);
38
39 static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, void *message) {
40 assert(ndisc);
41 assert(event >= 0 && event < _SD_NDISC_EVENT_MAX);
42
43 if (!ndisc->callback)
44 return (void) log_ndisc(ndisc, "Received '%s' event.", ndisc_event_to_string(event));
45
46 log_ndisc(ndisc, "Invoking callback for '%s' event.", ndisc_event_to_string(event));
47 ndisc->callback(ndisc, event, message, ndisc->userdata);
48 }
49
50 int sd_ndisc_is_running(sd_ndisc *nd) {
51 if (!nd)
52 return false;
53
54 return sd_event_source_get_enabled(nd->recv_event_source, NULL) > 0;
55 }
56
57 int sd_ndisc_set_callback(
58 sd_ndisc *nd,
59 sd_ndisc_callback_t callback,
60 void *userdata) {
61
62 assert_return(nd, -EINVAL);
63
64 nd->callback = callback;
65 nd->userdata = userdata;
66
67 return 0;
68 }
69
70 int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
71 assert_return(nd, -EINVAL);
72 assert_return(ifindex > 0, -EINVAL);
73 assert_return(!sd_ndisc_is_running(nd), -EBUSY);
74
75 nd->ifindex = ifindex;
76 return 0;
77 }
78
79 int sd_ndisc_set_ifname(sd_ndisc *nd, const char *ifname) {
80 assert_return(nd, -EINVAL);
81 assert_return(ifname, -EINVAL);
82
83 if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
84 return -EINVAL;
85
86 return free_and_strdup(&nd->ifname, ifname);
87 }
88
89 int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret) {
90 int r;
91
92 assert_return(nd, -EINVAL);
93
94 r = get_ifname(nd->ifindex, &nd->ifname);
95 if (r < 0)
96 return r;
97
98 if (ret)
99 *ret = nd->ifname;
100
101 return 0;
102 }
103
104 int sd_ndisc_set_link_local_address(sd_ndisc *nd, const struct in6_addr *addr) {
105 assert_return(nd, -EINVAL);
106 assert_return(!addr || in6_addr_is_link_local(addr), -EINVAL);
107
108 if (addr)
109 nd->link_local_addr = *addr;
110 else
111 zero(nd->link_local_addr);
112
113 return 0;
114 }
115
116 int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
117 assert_return(nd, -EINVAL);
118
119 if (mac_addr)
120 nd->mac_addr = *mac_addr;
121 else
122 zero(nd->mac_addr);
123
124 return 0;
125 }
126
127 int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
128 int r;
129
130 assert_return(nd, -EINVAL);
131 assert_return(!sd_ndisc_is_running(nd), -EBUSY);
132 assert_return(!nd->event, -EBUSY);
133
134 if (event)
135 nd->event = sd_event_ref(event);
136 else {
137 r = sd_event_default(&nd->event);
138 if (r < 0)
139 return 0;
140 }
141
142 nd->event_priority = priority;
143
144 return 0;
145 }
146
147 int sd_ndisc_detach_event(sd_ndisc *nd) {
148
149 assert_return(nd, -EINVAL);
150 assert_return(!sd_ndisc_is_running(nd), -EBUSY);
151
152 nd->event = sd_event_unref(nd->event);
153 return 0;
154 }
155
156 sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
157 assert_return(nd, NULL);
158
159 return nd->event;
160 }
161
162 static void ndisc_reset(sd_ndisc *nd) {
163 assert(nd);
164
165 (void) event_source_disable(nd->timeout_event_source);
166 (void) event_source_disable(nd->timeout_no_ra);
167 nd->retransmit_time = 0;
168 nd->recv_event_source = sd_event_source_disable_unref(nd->recv_event_source);
169 nd->fd = safe_close(nd->fd);
170 }
171
172 static sd_ndisc *ndisc_free(sd_ndisc *nd) {
173 assert(nd);
174
175 ndisc_reset(nd);
176
177 sd_event_source_unref(nd->timeout_event_source);
178 sd_event_source_unref(nd->timeout_no_ra);
179 sd_ndisc_detach_event(nd);
180
181 free(nd->ifname);
182 return mfree(nd);
183 }
184
185 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc, sd_ndisc, ndisc_free);
186
187 int sd_ndisc_new(sd_ndisc **ret) {
188 _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
189
190 assert_return(ret, -EINVAL);
191
192 nd = new(sd_ndisc, 1);
193 if (!nd)
194 return -ENOMEM;
195
196 *nd = (sd_ndisc) {
197 .n_ref = 1,
198 .fd = -EBADF,
199 };
200
201 *ret = TAKE_PTR(nd);
202
203 return 0;
204 }
205
206 static int ndisc_handle_router(sd_ndisc *nd, ICMP6Packet *packet) {
207 _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
208 int r;
209
210 assert(nd);
211 assert(packet);
212
213 rt = ndisc_router_new(packet);
214 if (!rt)
215 return -ENOMEM;
216
217 r = ndisc_router_parse(nd, rt);
218 if (r < 0)
219 return r;
220
221 (void) event_source_disable(nd->timeout_event_source);
222
223 log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %s",
224 rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none",
225 rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium",
226 FORMAT_TIMESPAN(rt->lifetime_usec, USEC_PER_SEC));
227
228 ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
229 return 0;
230 }
231
232 static int ndisc_handle_neighbor(sd_ndisc *nd, ICMP6Packet *packet) {
233 _cleanup_(sd_ndisc_neighbor_unrefp) sd_ndisc_neighbor *na = NULL;
234 struct in6_addr a;
235 int r;
236
237 assert(nd);
238 assert(packet);
239
240 na = ndisc_neighbor_new(packet);
241 if (!na)
242 return -ENOMEM;
243
244 r = ndisc_neighbor_parse(nd, na);
245 if (r < 0)
246 return r;
247
248 r = sd_ndisc_neighbor_get_sender_address(na, &a);
249 if (r < 0)
250 return r;
251
252 log_ndisc(nd, "Received Neighbor Advertisement from %s: Router=%s, Solicited=%s, Override=%s",
253 IN6_ADDR_TO_STRING(&a),
254 yes_no(sd_ndisc_neighbor_is_router(na) > 0),
255 yes_no(sd_ndisc_neighbor_is_solicited(na) > 0),
256 yes_no(sd_ndisc_neighbor_is_override(na) > 0));
257
258 ndisc_callback(nd, SD_NDISC_EVENT_NEIGHBOR, na);
259 return 0;
260 }
261
262 static int ndisc_handle_redirect(sd_ndisc *nd, ICMP6Packet *packet) {
263 _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *rd = NULL;
264 struct in6_addr a;
265 int r;
266
267 assert(nd);
268 assert(packet);
269
270 rd = ndisc_redirect_new(packet);
271 if (!rd)
272 return -ENOMEM;
273
274 r = ndisc_redirect_parse(nd, rd);
275 if (r < 0)
276 return r;
277
278 r = sd_ndisc_redirect_get_sender_address(rd, &a);
279 if (r < 0)
280 return r;
281
282 log_ndisc(nd, "Received Redirect message from %s: Target=%s, Destination=%s",
283 IN6_ADDR_TO_STRING(&a),
284 IN6_ADDR_TO_STRING(&rd->target_address),
285 IN6_ADDR_TO_STRING(&rd->destination_address));
286
287 ndisc_callback(nd, SD_NDISC_EVENT_REDIRECT, rd);
288 return 0;
289 }
290
291 static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
292 _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
293 sd_ndisc *nd = ASSERT_PTR(userdata);
294 int r;
295
296 assert(s);
297 assert(nd->event);
298
299 r = icmp6_packet_receive(fd, &packet);
300 if (r < 0) {
301 log_ndisc_errno(nd, r, "Failed to receive ICMPv6 packet, ignoring: %m");
302 return 0;
303 }
304
305 /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states
306 * that hosts MUST discard messages with the null source address. */
307 if (in6_addr_is_null(&packet->sender_address)) {
308 log_ndisc(nd, "Received an ICMPv6 packet from null address, ignoring.");
309 return 0;
310 }
311
312 if (in6_addr_equal(&packet->sender_address, &nd->link_local_addr)) {
313 log_ndisc(nd, "Received an ICMPv6 packet sent by the same interface, ignoring.");
314 return 0;
315 }
316
317 r = icmp6_packet_get_type(packet);
318 if (r < 0) {
319 log_ndisc_errno(nd, r, "Received an invalid ICMPv6 packet, ignoring: %m");
320 return 0;
321 }
322
323 switch (r) {
324 case ND_ROUTER_ADVERT:
325 (void) ndisc_handle_router(nd, packet);
326 break;
327
328 case ND_NEIGHBOR_ADVERT:
329 (void) ndisc_handle_neighbor(nd, packet);
330 break;
331
332 case ND_REDIRECT:
333 (void) ndisc_handle_redirect(nd, packet);
334 break;
335
336 default:
337 log_ndisc(nd, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r);
338 }
339
340 return 0;
341 }
342
343 static int ndisc_send_router_solicitation(sd_ndisc *nd) {
344 static const struct sockaddr_in6 dst = {
345 .sin6_family = AF_INET6,
346 .sin6_addr = IN6_ADDR_ALL_ROUTERS_MULTICAST,
347 };
348 static const struct nd_router_solicit header = {
349 .nd_rs_type = ND_ROUTER_SOLICIT,
350 };
351
352 _cleanup_set_free_ Set *options = NULL;
353 int r;
354
355 assert(nd);
356
357 if (!ether_addr_is_null(&nd->mac_addr)) {
358 r = ndisc_option_set_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &nd->mac_addr);
359 if (r < 0)
360 return r;
361 }
362
363 return ndisc_send(nd->fd, &dst, &header.nd_rs_hdr, options, USEC_INFINITY);
364 }
365
366 static usec_t ndisc_timeout_compute_random(usec_t val) {
367 /* compute a time that is random within ±10% of the given value */
368 return val - val / 10 +
369 (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
370 }
371
372 static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
373 sd_ndisc *nd = ASSERT_PTR(userdata);
374 usec_t time_now;
375 int r;
376
377 assert(s);
378 assert(nd->event);
379
380 assert_se(sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now) >= 0);
381
382 if (!nd->retransmit_time)
383 nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL);
384 else {
385 if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2)
386 nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL);
387 else
388 nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time);
389 }
390
391 r = event_reset_time(nd->event, &nd->timeout_event_source,
392 CLOCK_BOOTTIME,
393 time_now + nd->retransmit_time, 10 * USEC_PER_MSEC,
394 ndisc_timeout, nd,
395 nd->event_priority, "ndisc-timeout-no-ra", true);
396 if (r < 0)
397 goto fail;
398
399 r = ndisc_send_router_solicitation(nd);
400 if (r < 0)
401 log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m",
402 FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
403 else
404 log_ndisc(nd, "Sent Router Solicitation, next solicitation in %s",
405 FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
406
407 return 0;
408
409 fail:
410 (void) sd_ndisc_stop(nd);
411 return 0;
412 }
413
414 static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) {
415 sd_ndisc *nd = ASSERT_PTR(userdata);
416
417 assert(s);
418
419 log_ndisc(nd, "No RA received before link confirmation timeout");
420
421 (void) event_source_disable(nd->timeout_no_ra);
422 ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
423
424 return 0;
425 }
426
427 int sd_ndisc_stop(sd_ndisc *nd) {
428 if (!nd)
429 return 0;
430
431 if (!sd_ndisc_is_running(nd))
432 return 0;
433
434 log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
435
436 ndisc_reset(nd);
437 return 1;
438 }
439
440 static int ndisc_setup_recv_event(sd_ndisc *nd) {
441 int r;
442
443 assert(nd);
444 assert(nd->event);
445 assert(nd->ifindex > 0);
446
447 _cleanup_close_ int fd = -EBADF;
448 fd = icmp6_bind(nd->ifindex, /* is_router = */ false);
449 if (fd < 0)
450 return fd;
451
452 _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
453 r = sd_event_add_io(nd->event, &s, fd, EPOLLIN, ndisc_recv, nd);
454 if (r < 0)
455 return r;
456
457 r = sd_event_source_set_priority(s, nd->event_priority);
458 if (r < 0)
459 return r;
460
461 (void) sd_event_source_set_description(s, "ndisc-receive-router-message");
462
463 nd->fd = TAKE_FD(fd);
464 nd->recv_event_source = TAKE_PTR(s);
465 return 1;
466 }
467
468 static int ndisc_setup_timer(sd_ndisc *nd) {
469 int r;
470
471 assert(nd);
472 assert(nd->event);
473
474 r = event_reset_time_relative(nd->event, &nd->timeout_event_source,
475 CLOCK_BOOTTIME,
476 USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */
477 ndisc_timeout, nd,
478 nd->event_priority, "ndisc-timeout", true);
479 if (r < 0)
480 return r;
481
482 r = event_reset_time_relative(nd->event, &nd->timeout_no_ra,
483 CLOCK_BOOTTIME,
484 NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC,
485 ndisc_timeout_no_ra, nd,
486 nd->event_priority, "ndisc-timeout-no-ra", true);
487 if (r < 0)
488 return r;
489
490 return 0;
491 }
492
493 int sd_ndisc_start(sd_ndisc *nd) {
494 int r;
495
496 assert_return(nd, -EINVAL);
497 assert_return(nd->event, -EINVAL);
498 assert_return(nd->ifindex > 0, -EINVAL);
499
500 if (sd_ndisc_is_running(nd))
501 return 0;
502
503 r = ndisc_setup_recv_event(nd);
504 if (r < 0)
505 goto fail;
506
507 r = ndisc_setup_timer(nd);
508 if (r < 0)
509 goto fail;
510
511 log_ndisc(nd, "Started IPv6 Router Solicitation client");
512 return 1;
513
514 fail:
515 ndisc_reset(nd);
516 return r;
517 }