1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 Copyright © 2014 Intel Corporation. All rights reserved.
6 #include <netinet/icmp6.h>
7 #include <netinet/in.h>
11 #include "alloc-util.h"
12 #include "ether-addr-util.h"
13 #include "event-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"
25 #include "socket-util.h"
26 #include "string-table.h"
27 #include "string-util.h"
29 #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
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",
38 DEFINE_STRING_TABLE_LOOKUP(ndisc_event
, sd_ndisc_event_t
);
40 static void ndisc_callback(sd_ndisc
*ndisc
, sd_ndisc_event_t event
, void *message
) {
42 assert(event
>= 0 && event
< _SD_NDISC_EVENT_MAX
);
45 return (void) log_ndisc(ndisc
, "Received '%s' event.", ndisc_event_to_string(event
));
47 log_ndisc(ndisc
, "Invoking callback for '%s' event.", ndisc_event_to_string(event
));
48 ndisc
->callback(ndisc
, event
, message
, ndisc
->userdata
);
51 int sd_ndisc_is_running(sd_ndisc
*nd
) {
55 return sd_event_source_get_enabled(nd
->recv_event_source
, NULL
) > 0;
58 int sd_ndisc_set_callback(
60 sd_ndisc_callback_t callback
,
63 assert_return(nd
, -EINVAL
);
65 nd
->callback
= callback
;
66 nd
->userdata
= userdata
;
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
);
76 nd
->ifindex
= ifindex
;
80 int sd_ndisc_set_ifname(sd_ndisc
*nd
, const char *ifname
) {
81 assert_return(nd
, -EINVAL
);
82 assert_return(ifname
, -EINVAL
);
84 if (!ifname_valid_full(ifname
, IFNAME_VALID_ALTERNATIVE
))
87 return free_and_strdup(&nd
->ifname
, ifname
);
90 int sd_ndisc_get_ifname(sd_ndisc
*nd
, const char **ret
) {
93 assert_return(nd
, -EINVAL
);
95 r
= get_ifname(nd
->ifindex
, &nd
->ifname
);
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
);
110 nd
->link_local_addr
= *addr
;
112 zero(nd
->link_local_addr
);
117 int sd_ndisc_set_mac(sd_ndisc
*nd
, const struct ether_addr
*mac_addr
) {
118 assert_return(nd
, -EINVAL
);
121 nd
->mac_addr
= *mac_addr
;
128 int sd_ndisc_attach_event(sd_ndisc
*nd
, sd_event
*event
, int64_t priority
) {
131 assert_return(nd
, -EINVAL
);
132 assert_return(!sd_ndisc_is_running(nd
), -EBUSY
);
133 assert_return(!nd
->event
, -EBUSY
);
136 nd
->event
= sd_event_ref(event
);
138 r
= sd_event_default(&nd
->event
);
143 nd
->event_priority
= priority
;
148 int sd_ndisc_detach_event(sd_ndisc
*nd
) {
150 assert_return(nd
, -EINVAL
);
151 assert_return(!sd_ndisc_is_running(nd
), -EBUSY
);
153 nd
->event
= sd_event_unref(nd
->event
);
157 sd_event
*sd_ndisc_get_event(sd_ndisc
*nd
) {
158 assert_return(nd
, NULL
);
163 static void ndisc_reset(sd_ndisc
*nd
) {
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
);
173 static sd_ndisc
*ndisc_free(sd_ndisc
*nd
) {
178 sd_event_source_unref(nd
->timeout_event_source
);
179 sd_event_source_unref(nd
->timeout_no_ra
);
180 sd_ndisc_detach_event(nd
);
186 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc
, sd_ndisc
, ndisc_free
);
188 int sd_ndisc_new(sd_ndisc
**ret
) {
189 _cleanup_(sd_ndisc_unrefp
) sd_ndisc
*nd
= NULL
;
191 assert_return(ret
, -EINVAL
);
193 nd
= new(sd_ndisc
, 1);
207 static int ndisc_handle_router(sd_ndisc
*nd
, ICMP6Packet
*packet
) {
208 _cleanup_(sd_ndisc_router_unrefp
) sd_ndisc_router
*rt
= NULL
;
214 rt
= ndisc_router_new(packet
);
218 r
= ndisc_router_parse(nd
, rt
);
222 (void) event_source_disable(nd
->timeout_event_source
);
223 (void) event_source_disable(nd
->timeout_no_ra
);
226 _cleanup_free_
char *s
= NULL
;
232 r
= sd_ndisc_router_get_sender_address(rt
, &a
);
236 r
= sd_ndisc_router_get_flags(rt
, &flags
);
240 r
= ndisc_router_flags_to_string(flags
, &s
);
244 r
= sd_ndisc_router_get_preference(rt
, &pref
);
248 r
= sd_ndisc_router_get_lifetime(rt
, &lifetime
);
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 */
256 ndisc_router_preference_to_string(pref
),
257 FORMAT_TIMESPAN(lifetime
, USEC_PER_SEC
));
260 ndisc_callback(nd
, SD_NDISC_EVENT_ROUTER
, rt
);
264 static int ndisc_handle_neighbor(sd_ndisc
*nd
, ICMP6Packet
*packet
) {
265 _cleanup_(sd_ndisc_neighbor_unrefp
) sd_ndisc_neighbor
*na
= NULL
;
271 na
= ndisc_neighbor_new(packet
);
275 r
= ndisc_neighbor_parse(nd
, na
);
282 r
= sd_ndisc_neighbor_get_sender_address(na
, &a
);
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));
293 ndisc_callback(nd
, SD_NDISC_EVENT_NEIGHBOR
, na
);
297 static int ndisc_handle_redirect(sd_ndisc
*nd
, ICMP6Packet
*packet
) {
298 _cleanup_(sd_ndisc_redirect_unrefp
) sd_ndisc_redirect
*rd
= NULL
;
304 rd
= ndisc_redirect_new(packet
);
308 r
= ndisc_redirect_parse(nd
, rd
);
313 struct in6_addr sender
, target
, dest
;
315 r
= sd_ndisc_redirect_get_sender_address(rd
, &sender
);
319 r
= sd_ndisc_redirect_get_target_address(rd
, &target
);
323 r
= sd_ndisc_redirect_get_destination_address(rd
, &dest
);
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
));
333 ndisc_callback(nd
, SD_NDISC_EVENT_REDIRECT
, rd
);
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
);
345 r
= icmp6_packet_receive(fd
, &packet
);
347 log_ndisc_errno(nd
, r
, "Failed to receive ICMPv6 packet, ignoring: %m");
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.");
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.");
363 r
= icmp6_packet_get_type(packet
);
365 log_ndisc_errno(nd
, r
, "Received an invalid ICMPv6 packet, ignoring: %m");
370 case ND_ROUTER_ADVERT
:
371 (void) ndisc_handle_router(nd
, packet
);
374 case ND_NEIGHBOR_ADVERT
:
375 (void) ndisc_handle_neighbor(nd
, packet
);
379 (void) ndisc_handle_redirect(nd
, packet
);
383 log_ndisc(nd
, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r
);
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
,
394 _cleanup_set_free_ Set
*options
= NULL
;
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
);
405 return ndisc_send(nd
->fd
, &IN6_ADDR_ALL_ROUTERS_MULTICAST
, &header
.nd_rs_hdr
, options
, USEC_INFINITY
);
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
;
414 static int ndisc_timeout(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
415 sd_ndisc
*nd
= ASSERT_PTR(userdata
);
422 assert_se(sd_event_now(nd
->event
, CLOCK_BOOTTIME
, &time_now
) >= 0);
424 if (!nd
->retransmit_time
)
425 nd
->retransmit_time
= ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL
);
427 if (nd
->retransmit_time
> NDISC_MAX_ROUTER_SOLICITATION_INTERVAL
/ 2)
428 nd
->retransmit_time
= ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL
);
430 nd
->retransmit_time
+= ndisc_timeout_compute_random(nd
->retransmit_time
);
433 r
= event_reset_time(nd
->event
, &nd
->timeout_event_source
,
435 time_now
+ nd
->retransmit_time
, 10 * USEC_PER_MSEC
,
437 nd
->event_priority
, "ndisc-timeout-no-ra", true);
441 r
= ndisc_send_router_solicitation(nd
);
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
));
446 log_ndisc(nd
, "Sent Router Solicitation, next solicitation in %s",
447 FORMAT_TIMESPAN(nd
->retransmit_time
, USEC_PER_SEC
));
452 (void) sd_ndisc_stop(nd
);
456 static int ndisc_timeout_no_ra(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
457 sd_ndisc
*nd
= ASSERT_PTR(userdata
);
461 log_ndisc(nd
, "No RA received before link confirmation timeout");
463 (void) event_source_disable(nd
->timeout_no_ra
);
464 ndisc_callback(nd
, SD_NDISC_EVENT_TIMEOUT
, NULL
);
469 int sd_ndisc_stop(sd_ndisc
*nd
) {
473 if (!sd_ndisc_is_running(nd
))
476 log_ndisc(nd
, "Stopping IPv6 Router Solicitation client");
482 static int ndisc_setup_recv_event(sd_ndisc
*nd
) {
487 assert(nd
->ifindex
> 0);
489 _cleanup_close_
int fd
= -EBADF
;
490 fd
= icmp6_bind(nd
->ifindex
, /* is_router = */ false);
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
);
499 r
= sd_event_source_set_priority(s
, nd
->event_priority
);
503 (void) sd_event_source_set_description(s
, "ndisc-receive-router-message");
505 nd
->fd
= TAKE_FD(fd
);
506 nd
->recv_event_source
= TAKE_PTR(s
);
510 static int ndisc_setup_timer(sd_ndisc
*nd
) {
516 r
= event_reset_time_relative(nd
->event
, &nd
->timeout_event_source
,
518 USEC_PER_SEC
/ 2, 1 * USEC_PER_SEC
, /* See RFC 8415 sec. 18.2.1 */
520 nd
->event_priority
, "ndisc-timeout", true);
524 r
= event_reset_time_relative(nd
->event
, &nd
->timeout_no_ra
,
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);
535 int sd_ndisc_start(sd_ndisc
*nd
) {
538 assert_return(nd
, -EINVAL
);
539 assert_return(nd
->event
, -EINVAL
);
540 assert_return(nd
->ifindex
> 0, -EINVAL
);
542 if (sd_ndisc_is_running(nd
))
545 r
= ndisc_setup_recv_event(nd
);
549 r
= ndisc_setup_timer(nd
);
553 log_ndisc(nd
, "Started IPv6 Router Solicitation client");