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"
24 #include "socket-util.h"
25 #include "string-table.h"
26 #include "string-util.h"
28 #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
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",
37 DEFINE_STRING_TABLE_LOOKUP(ndisc_event
, sd_ndisc_event_t
);
39 static void ndisc_callback(sd_ndisc
*ndisc
, sd_ndisc_event_t event
, void *message
) {
41 assert(event
>= 0 && event
< _SD_NDISC_EVENT_MAX
);
44 return (void) log_ndisc(ndisc
, "Received '%s' event.", ndisc_event_to_string(event
));
46 log_ndisc(ndisc
, "Invoking callback for '%s' event.", ndisc_event_to_string(event
));
47 ndisc
->callback(ndisc
, event
, message
, ndisc
->userdata
);
50 int sd_ndisc_is_running(sd_ndisc
*nd
) {
54 return sd_event_source_get_enabled(nd
->recv_event_source
, NULL
) > 0;
57 int sd_ndisc_set_callback(
59 sd_ndisc_callback_t callback
,
62 assert_return(nd
, -EINVAL
);
64 nd
->callback
= callback
;
65 nd
->userdata
= userdata
;
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
);
75 nd
->ifindex
= ifindex
;
79 int sd_ndisc_set_ifname(sd_ndisc
*nd
, const char *ifname
) {
80 assert_return(nd
, -EINVAL
);
81 assert_return(ifname
, -EINVAL
);
83 if (!ifname_valid_full(ifname
, IFNAME_VALID_ALTERNATIVE
))
86 return free_and_strdup(&nd
->ifname
, ifname
);
89 int sd_ndisc_get_ifname(sd_ndisc
*nd
, const char **ret
) {
92 assert_return(nd
, -EINVAL
);
94 r
= get_ifname(nd
->ifindex
, &nd
->ifname
);
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
);
109 nd
->link_local_addr
= *addr
;
111 zero(nd
->link_local_addr
);
116 int sd_ndisc_set_mac(sd_ndisc
*nd
, const struct ether_addr
*mac_addr
) {
117 assert_return(nd
, -EINVAL
);
120 nd
->mac_addr
= *mac_addr
;
127 int sd_ndisc_attach_event(sd_ndisc
*nd
, sd_event
*event
, int64_t priority
) {
130 assert_return(nd
, -EINVAL
);
131 assert_return(!sd_ndisc_is_running(nd
), -EBUSY
);
132 assert_return(!nd
->event
, -EBUSY
);
135 nd
->event
= sd_event_ref(event
);
137 r
= sd_event_default(&nd
->event
);
142 nd
->event_priority
= priority
;
147 int sd_ndisc_detach_event(sd_ndisc
*nd
) {
149 assert_return(nd
, -EINVAL
);
150 assert_return(!sd_ndisc_is_running(nd
), -EBUSY
);
152 nd
->event
= sd_event_unref(nd
->event
);
156 sd_event
*sd_ndisc_get_event(sd_ndisc
*nd
) {
157 assert_return(nd
, NULL
);
162 static void ndisc_reset(sd_ndisc
*nd
) {
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
);
172 static sd_ndisc
*ndisc_free(sd_ndisc
*nd
) {
177 sd_event_source_unref(nd
->timeout_event_source
);
178 sd_event_source_unref(nd
->timeout_no_ra
);
179 sd_ndisc_detach_event(nd
);
185 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc
, sd_ndisc
, ndisc_free
);
187 int sd_ndisc_new(sd_ndisc
**ret
) {
188 _cleanup_(sd_ndisc_unrefp
) sd_ndisc
*nd
= NULL
;
190 assert_return(ret
, -EINVAL
);
192 nd
= new(sd_ndisc
, 1);
206 static int ndisc_handle_router(sd_ndisc
*nd
, ICMP6Packet
*packet
) {
207 _cleanup_(sd_ndisc_router_unrefp
) sd_ndisc_router
*rt
= NULL
;
213 rt
= ndisc_router_new(packet
);
217 r
= ndisc_router_parse(nd
, rt
);
221 (void) event_source_disable(nd
->timeout_event_source
);
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
));
228 ndisc_callback(nd
, SD_NDISC_EVENT_ROUTER
, rt
);
232 static int ndisc_handle_neighbor(sd_ndisc
*nd
, ICMP6Packet
*packet
) {
233 _cleanup_(sd_ndisc_neighbor_unrefp
) sd_ndisc_neighbor
*na
= NULL
;
240 na
= ndisc_neighbor_new(packet
);
244 r
= ndisc_neighbor_parse(nd
, na
);
248 r
= sd_ndisc_neighbor_get_sender_address(na
, &a
);
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));
258 ndisc_callback(nd
, SD_NDISC_EVENT_NEIGHBOR
, na
);
262 static int ndisc_handle_redirect(sd_ndisc
*nd
, ICMP6Packet
*packet
) {
263 _cleanup_(sd_ndisc_redirect_unrefp
) sd_ndisc_redirect
*rd
= NULL
;
270 rd
= ndisc_redirect_new(packet
);
274 r
= ndisc_redirect_parse(nd
, rd
);
278 r
= sd_ndisc_redirect_get_sender_address(rd
, &a
);
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
));
287 ndisc_callback(nd
, SD_NDISC_EVENT_REDIRECT
, rd
);
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
);
299 r
= icmp6_packet_receive(fd
, &packet
);
301 log_ndisc_errno(nd
, r
, "Failed to receive ICMPv6 packet, ignoring: %m");
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.");
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.");
317 r
= icmp6_packet_get_type(packet
);
319 log_ndisc_errno(nd
, r
, "Received an invalid ICMPv6 packet, ignoring: %m");
324 case ND_ROUTER_ADVERT
:
325 (void) ndisc_handle_router(nd
, packet
);
328 case ND_NEIGHBOR_ADVERT
:
329 (void) ndisc_handle_neighbor(nd
, packet
);
333 (void) ndisc_handle_redirect(nd
, packet
);
337 log_ndisc(nd
, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r
);
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
,
348 static const struct nd_router_solicit header
= {
349 .nd_rs_type
= ND_ROUTER_SOLICIT
,
352 _cleanup_set_free_ Set
*options
= NULL
;
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
);
363 return ndisc_send(nd
->fd
, &dst
, &header
.nd_rs_hdr
, options
, USEC_INFINITY
);
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
;
372 static int ndisc_timeout(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
373 sd_ndisc
*nd
= ASSERT_PTR(userdata
);
380 assert_se(sd_event_now(nd
->event
, CLOCK_BOOTTIME
, &time_now
) >= 0);
382 if (!nd
->retransmit_time
)
383 nd
->retransmit_time
= ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL
);
385 if (nd
->retransmit_time
> NDISC_MAX_ROUTER_SOLICITATION_INTERVAL
/ 2)
386 nd
->retransmit_time
= ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL
);
388 nd
->retransmit_time
+= ndisc_timeout_compute_random(nd
->retransmit_time
);
391 r
= event_reset_time(nd
->event
, &nd
->timeout_event_source
,
393 time_now
+ nd
->retransmit_time
, 10 * USEC_PER_MSEC
,
395 nd
->event_priority
, "ndisc-timeout-no-ra", true);
399 r
= ndisc_send_router_solicitation(nd
);
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
));
404 log_ndisc(nd
, "Sent Router Solicitation, next solicitation in %s",
405 FORMAT_TIMESPAN(nd
->retransmit_time
, USEC_PER_SEC
));
410 (void) sd_ndisc_stop(nd
);
414 static int ndisc_timeout_no_ra(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
415 sd_ndisc
*nd
= ASSERT_PTR(userdata
);
419 log_ndisc(nd
, "No RA received before link confirmation timeout");
421 (void) event_source_disable(nd
->timeout_no_ra
);
422 ndisc_callback(nd
, SD_NDISC_EVENT_TIMEOUT
, NULL
);
427 int sd_ndisc_stop(sd_ndisc
*nd
) {
431 if (!sd_ndisc_is_running(nd
))
434 log_ndisc(nd
, "Stopping IPv6 Router Solicitation client");
440 static int ndisc_setup_recv_event(sd_ndisc
*nd
) {
445 assert(nd
->ifindex
> 0);
447 _cleanup_close_
int fd
= -EBADF
;
448 fd
= icmp6_bind(nd
->ifindex
, /* is_router = */ false);
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
);
457 r
= sd_event_source_set_priority(s
, nd
->event_priority
);
461 (void) sd_event_source_set_description(s
, "ndisc-receive-router-message");
463 nd
->fd
= TAKE_FD(fd
);
464 nd
->recv_event_source
= TAKE_PTR(s
);
468 static int ndisc_setup_timer(sd_ndisc
*nd
) {
474 r
= event_reset_time_relative(nd
->event
, &nd
->timeout_event_source
,
476 USEC_PER_SEC
/ 2, 1 * USEC_PER_SEC
, /* See RFC 8415 sec. 18.2.1 */
478 nd
->event_priority
, "ndisc-timeout", true);
482 r
= event_reset_time_relative(nd
->event
, &nd
->timeout_no_ra
,
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);
493 int sd_ndisc_start(sd_ndisc
*nd
) {
496 assert_return(nd
, -EINVAL
);
497 assert_return(nd
->event
, -EINVAL
);
498 assert_return(nd
->ifindex
> 0, -EINVAL
);
500 if (sd_ndisc_is_running(nd
))
503 r
= ndisc_setup_recv_event(nd
);
507 r
= ndisc_setup_timer(nd
);
511 log_ndisc(nd
, "Started IPv6 Router Solicitation client");