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