]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-ndisc.c
ci: enable arm64 runner for build/unit jobs
[thirdparty/systemd.git] / src / libsystemd-network / sd-ndisc.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
e3169126 2/***
810adae9 3 Copyright © 2014 Intel Corporation. All rights reserved.
e3169126
PF
4***/
5
6#include <netinet/icmp6.h>
07630cea 7#include <netinet/in.h>
e3169126 8
07630cea
LP
9#include "sd-ndisc.h"
10
b5efdb8a 11#include "alloc-util.h"
349d51e7 12#include "ether-addr-util.h"
ff4b0321 13#include "event-util.h"
1e7a0e21 14#include "fd-util.h"
07630cea 15#include "icmp6-util.h"
9d96e6c3 16#include "in-addr-util.h"
0a970718 17#include "memory-util.h"
1e7a0e21 18#include "ndisc-internal.h"
696eb2b8 19#include "ndisc-neighbor-internal.h"
44e8cf30 20#include "ndisc-redirect-internal.h"
ca34b434 21#include "ndisc-router-internal.h"
61a9fa8f 22#include "network-common.h"
1bd6f895 23#include "random-util.h"
5cdf13c7 24#include "set.h"
940367a0 25#include "socket-util.h"
a2dcda32 26#include "string-table.h"
d7fa4380 27#include "string-util.h"
e3169126 28
1bd6f895 29#define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
e3169126 30
a2dcda32 31static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = {
696eb2b8
YW
32 [SD_NDISC_EVENT_TIMEOUT] = "timeout",
33 [SD_NDISC_EVENT_ROUTER] = "router",
34 [SD_NDISC_EVENT_NEIGHBOR] = "neighbor",
44e8cf30 35 [SD_NDISC_EVENT_REDIRECT] = "redirect",
a2dcda32
YW
36};
37
2324fd3a 38DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t);
a2dcda32 39
28eef158 40static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, void *message) {
1e7a0e21 41 assert(ndisc);
a2dcda32 42 assert(event >= 0 && event < _SD_NDISC_EVENT_MAX);
e3169126 43
35388783
YW
44 if (!ndisc->callback)
45 return (void) log_ndisc(ndisc, "Received '%s' event.", ndisc_event_to_string(event));
9d96e6c3 46
35388783 47 log_ndisc(ndisc, "Invoking callback for '%s' event.", ndisc_event_to_string(event));
28eef158 48 ndisc->callback(ndisc, event, message, ndisc->userdata);
5624c480
PF
49}
50
787e71e4
YW
51int 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
17347053 58int sd_ndisc_set_callback(
a1140666 59 sd_ndisc *nd,
a1140666
LP
60 sd_ndisc_callback_t callback,
61 void *userdata) {
62
63 assert_return(nd, -EINVAL);
e3169126
PF
64
65 nd->callback = callback;
66 nd->userdata = userdata;
67
68 return 0;
69}
70
17347053 71int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
2f8e7633
LP
72 assert_return(nd, -EINVAL);
73 assert_return(ifindex > 0, -EINVAL);
787e71e4 74 assert_return(!sd_ndisc_is_running(nd), -EBUSY);
e3169126 75
2f8e7633 76 nd->ifindex = ifindex;
e3169126
PF
77 return 0;
78}
79
61a9fa8f
YW
80int 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
5977b71f
YW
90int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret) {
91 int r;
92
93 assert_return(nd, -EINVAL);
61a9fa8f 94
5977b71f
YW
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;
61a9fa8f
YW
103}
104
25413fbf
YW
105int 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
17347053 117int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
a1140666 118 assert_return(nd, -EINVAL);
e3169126
PF
119
120 if (mac_addr)
1e7a0e21 121 nd->mac_addr = *mac_addr;
e3169126 122 else
eccaf899 123 zero(nd->mac_addr);
e3169126
PF
124
125 return 0;
e3169126
PF
126}
127
17347053 128int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
e3169126
PF
129 int r;
130
131 assert_return(nd, -EINVAL);
787e71e4 132 assert_return(!sd_ndisc_is_running(nd), -EBUSY);
e3169126
PF
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
17347053 148int sd_ndisc_detach_event(sd_ndisc *nd) {
1e7a0e21 149
e3169126 150 assert_return(nd, -EINVAL);
787e71e4 151 assert_return(!sd_ndisc_is_running(nd), -EBUSY);
e3169126
PF
152
153 nd->event = sd_event_unref(nd->event);
e3169126
PF
154 return 0;
155}
156
17347053 157sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
a1140666 158 assert_return(nd, NULL);
e3169126
PF
159
160 return nd->event;
161}
162
165ad41b 163static void ndisc_reset(sd_ndisc *nd) {
e3169126
PF
164 assert(nd);
165
ff4b0321
YW
166 (void) event_source_disable(nd->timeout_event_source);
167 (void) event_source_disable(nd->timeout_no_ra);
1bd6f895 168 nd->retransmit_time = 0;
eb2f7502 169 nd->recv_event_source = sd_event_source_disable_unref(nd->recv_event_source);
1e7a0e21 170 nd->fd = safe_close(nd->fd);
e3169126
PF
171}
172
8301aa0b
YW
173static sd_ndisc *ndisc_free(sd_ndisc *nd) {
174 assert(nd);
e3169126 175
5c4c338a 176 ndisc_reset(nd);
eb2f7502
YW
177
178 sd_event_source_unref(nd->timeout_event_source);
179 sd_event_source_unref(nd->timeout_no_ra);
4d7b83da 180 sd_ndisc_detach_event(nd);
eb2f7502 181
61a9fa8f 182 free(nd->ifname);
6b430fdb 183 return mfree(nd);
e3169126
PF
184}
185
8301aa0b
YW
186DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc, sd_ndisc, ndisc_free);
187
17347053 188int sd_ndisc_new(sd_ndisc **ret) {
4afd3348 189 _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
e3169126 190
a1140666 191 assert_return(ret, -EINVAL);
e3169126 192
144faa8e 193 nd = new(sd_ndisc, 1);
e3169126
PF
194 if (!nd)
195 return -ENOMEM;
196
144faa8e
YW
197 *nd = (sd_ndisc) {
198 .n_ref = 1,
254d1313 199 .fd = -EBADF,
144faa8e 200 };
e3169126 201
1cc6c93a 202 *ret = TAKE_PTR(nd);
e3169126
PF
203
204 return 0;
205}
206
c34cb1d6
YW
207static int ndisc_handle_router(sd_ndisc *nd, ICMP6Packet *packet) {
208 _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
f6e0ce66 209 int r;
d77bde34 210
f6e0ce66 211 assert(nd);
c34cb1d6
YW
212 assert(packet);
213
214 rt = ndisc_router_new(packet);
215 if (!rt)
216 return -ENOMEM;
d77bde34 217
35388783 218 r = ndisc_router_parse(nd, rt);
f6e0ce66 219 if (r < 0)
ab8a8a4e 220 return r;
09667885 221
e7cb8047 222 (void) event_source_disable(nd->timeout_event_source);
31db4c1b 223 (void) event_source_disable(nd->timeout_no_ra);
e7cb8047 224
238ed432
YW
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
a925620f 252 log_ndisc(nd, "Received Router Advertisement from %s: flags=0x%0*"PRIx64"(%s), preference=%s, lifetime=%s",
238ed432
YW
253 IN6_ADDR_TO_STRING(&a),
254 flags & UINT64_C(0x00ffffffffffff00) ? 14 : 2, flags, /* suppress too many zeros if no extension */
a925620f 255 s ?: "none",
238ed432
YW
256 ndisc_router_preference_to_string(pref),
257 FORMAT_TIMESPAN(lifetime, USEC_PER_SEC));
258 }
09667885 259
1e7a0e21 260 ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
09667885
PF
261 return 0;
262}
263
696eb2b8
YW
264static int ndisc_handle_neighbor(sd_ndisc *nd, ICMP6Packet *packet) {
265 _cleanup_(sd_ndisc_neighbor_unrefp) sd_ndisc_neighbor *na = NULL;
696eb2b8
YW
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
238ed432
YW
279 if (DEBUG_LOGGING) {
280 struct in6_addr a;
696eb2b8 281
238ed432
YW
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 }
696eb2b8
YW
292
293 ndisc_callback(nd, SD_NDISC_EVENT_NEIGHBOR, na);
294 return 0;
295}
296
44e8cf30
YW
297static int ndisc_handle_redirect(sd_ndisc *nd, ICMP6Packet *packet) {
298 _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *rd = NULL;
44e8cf30
YW
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
238ed432
YW
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;
44e8cf30 322
238ed432
YW
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 }
44e8cf30
YW
332
333 ndisc_callback(nd, SD_NDISC_EVENT_REDIRECT, rd);
334 return 0;
335}
336
1e7a0e21 337static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
c34cb1d6 338 _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
99534007 339 sd_ndisc *nd = ASSERT_PTR(userdata);
88d5a3db 340 int r;
e3169126
PF
341
342 assert(s);
e3169126
PF
343 assert(nd->event);
344
c34cb1d6
YW
345 r = icmp6_packet_receive(fd, &packet);
346 if (r < 0) {
347 log_ndisc_errno(nd, r, "Failed to receive ICMPv6 packet, ignoring: %m");
35388783
YW
348 return 0;
349 }
cddf4d81 350
4961f566
YW
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. */
c34cb1d6 353 if (in6_addr_is_null(&packet->sender_address)) {
189eedda
YW
354 log_ndisc(nd, "Received an ICMPv6 packet from null address, ignoring.");
355 return 0;
356 }
4961f566 357
25413fbf
YW
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
c34cb1d6
YW
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
696eb2b8
YW
374 case ND_NEIGHBOR_ADVERT:
375 (void) ndisc_handle_neighbor(nd, packet);
376 break;
377
44e8cf30
YW
378 case ND_REDIRECT:
379 (void) ndisc_handle_redirect(nd, packet);
380 break;
381
c34cb1d6
YW
382 default:
383 log_ndisc(nd, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r);
384 }
385
ab8a8a4e 386 return 0;
e3169126
PF
387}
388
349d51e7 389static int ndisc_send_router_solicitation(sd_ndisc *nd) {
349d51e7
YW
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)) {
ff944339 400 r = ndisc_option_set_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &nd->mac_addr);
349d51e7
YW
401 if (r < 0)
402 return r;
403 }
404
ac336e75 405 return ndisc_send(nd->fd, &IN6_ADDR_ALL_ROUTERS_MULTICAST, &header.nd_rs_hdr, options, USEC_INFINITY);
349d51e7
YW
406}
407
1bd6f895
PF
408static 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
1e7a0e21 414static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
99534007 415 sd_ndisc *nd = ASSERT_PTR(userdata);
1bd6f895 416 usec_t time_now;
e3169126
PF
417 int r;
418
419 assert(s);
e3169126
PF
420 assert(nd->event);
421
ba4e0427 422 assert_se(sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now) >= 0);
e3169126 423
1bd6f895
PF
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);
1e7a0e21 431 }
9021bb9f 432
ff4b0321 433 r = event_reset_time(nd->event, &nd->timeout_event_source,
ba4e0427 434 CLOCK_BOOTTIME,
ff4b0321
YW
435 time_now + nd->retransmit_time, 10 * USEC_PER_MSEC,
436 ndisc_timeout, nd,
437 nd->event_priority, "ndisc-timeout-no-ra", true);
1bd6f895
PF
438 if (r < 0)
439 goto fail;
440
349d51e7 441 r = ndisc_send_router_solicitation(nd);
852bf938
YW
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));
1bd6f895 448
e3169126 449 return 0;
b9e7b1cf
LP
450
451fail:
76f713df 452 (void) sd_ndisc_stop(nd);
b9e7b1cf 453 return 0;
e3169126
PF
454}
455
1bd6f895 456static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) {
99534007 457 sd_ndisc *nd = ASSERT_PTR(userdata);
1bd6f895
PF
458
459 assert(s);
1bd6f895 460
35388783 461 log_ndisc(nd, "No RA received before link confirmation timeout");
1bd6f895 462
ff4b0321 463 (void) event_source_disable(nd->timeout_no_ra);
1bd6f895
PF
464 ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
465
466 return 0;
467}
468
17347053 469int sd_ndisc_stop(sd_ndisc *nd) {
c8bae363
YW
470 if (!nd)
471 return 0;
836cf090 472
787e71e4 473 if (!sd_ndisc_is_running(nd))
c1c9b211
LP
474 return 0;
475
35388783 476 log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
836cf090 477
5c4c338a 478 ndisc_reset(nd);
1e7a0e21 479 return 1;
836cf090
PF
480}
481
6a27ca08 482static int ndisc_setup_recv_event(sd_ndisc *nd) {
e3169126
PF
483 int r;
484
6a27ca08
YW
485 assert(nd);
486 assert(nd->event);
487 assert(nd->ifindex > 0);
e3169126 488
6a27ca08
YW
489 _cleanup_close_ int fd = -EBADF;
490 fd = icmp6_bind(nd->ifindex, /* is_router = */ false);
491 if (fd < 0)
492 return fd;
e3169126 493
6a27ca08
YW
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;
e3169126 498
6a27ca08 499 r = sd_event_source_set_priority(s, nd->event_priority);
1bd6f895 500 if (r < 0)
6a27ca08
YW
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
510static int ndisc_setup_timer(sd_ndisc *nd) {
511 int r;
1bd6f895 512
6a27ca08
YW
513 assert(nd);
514 assert(nd->event);
1e7a0e21 515
6a27ca08
YW
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);
e3169126 521 if (r < 0)
6a27ca08 522 return r;
e3169126 523
6a27ca08
YW
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);
e3169126 529 if (r < 0)
6a27ca08 530 return r;
e3169126 531
6a27ca08
YW
532 return 0;
533}
9021bb9f 534
6a27ca08
YW
535int 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);
e3169126 546 if (r < 0)
5c4c338a 547 goto fail;
e3169126 548
6a27ca08 549 r = ndisc_setup_timer(nd);
9021bb9f 550 if (r < 0)
5c4c338a 551 goto fail;
e3169126 552
35388783 553 log_ndisc(nd, "Started IPv6 Router Solicitation client");
1e7a0e21 554 return 1;
e3169126 555
5c4c338a
LP
556fail:
557 ndisc_reset(nd);
e3169126
PF
558 return r;
559}