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 "event-util.h"
14 #include "icmp6-util.h"
15 #include "in-addr-util.h"
16 #include "memory-util.h"
17 #include "ndisc-internal.h"
18 #include "ndisc-router-internal.h"
19 #include "network-common.h"
20 #include "random-util.h"
21 #include "socket-util.h"
22 #include "string-table.h"
23 #include "string-util.h"
25 #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
27 static const char * const ndisc_event_table
[_SD_NDISC_EVENT_MAX
] = {
28 [SD_NDISC_EVENT_TIMEOUT
] = "timeout",
29 [SD_NDISC_EVENT_ROUTER
] = "router",
32 DEFINE_STRING_TABLE_LOOKUP(ndisc_event
, sd_ndisc_event_t
);
34 static void ndisc_callback(sd_ndisc
*ndisc
, sd_ndisc_event_t event
, sd_ndisc_router
*rt
) {
36 assert(event
>= 0 && event
< _SD_NDISC_EVENT_MAX
);
39 return (void) log_ndisc(ndisc
, "Received '%s' event.", ndisc_event_to_string(event
));
41 log_ndisc(ndisc
, "Invoking callback for '%s' event.", ndisc_event_to_string(event
));
42 ndisc
->callback(ndisc
, event
, rt
, ndisc
->userdata
);
45 int sd_ndisc_is_running(sd_ndisc
*nd
) {
49 return sd_event_source_get_enabled(nd
->recv_event_source
, NULL
) > 0;
52 int sd_ndisc_set_callback(
54 sd_ndisc_callback_t callback
,
57 assert_return(nd
, -EINVAL
);
59 nd
->callback
= callback
;
60 nd
->userdata
= userdata
;
65 int sd_ndisc_set_ifindex(sd_ndisc
*nd
, int ifindex
) {
66 assert_return(nd
, -EINVAL
);
67 assert_return(ifindex
> 0, -EINVAL
);
68 assert_return(!sd_ndisc_is_running(nd
), -EBUSY
);
70 nd
->ifindex
= ifindex
;
74 int sd_ndisc_set_ifname(sd_ndisc
*nd
, const char *ifname
) {
75 assert_return(nd
, -EINVAL
);
76 assert_return(ifname
, -EINVAL
);
78 if (!ifname_valid_full(ifname
, IFNAME_VALID_ALTERNATIVE
))
81 return free_and_strdup(&nd
->ifname
, ifname
);
84 int sd_ndisc_get_ifname(sd_ndisc
*nd
, const char **ret
) {
87 assert_return(nd
, -EINVAL
);
89 r
= get_ifname(nd
->ifindex
, &nd
->ifname
);
99 int sd_ndisc_set_mac(sd_ndisc
*nd
, const struct ether_addr
*mac_addr
) {
100 assert_return(nd
, -EINVAL
);
103 nd
->mac_addr
= *mac_addr
;
110 int sd_ndisc_attach_event(sd_ndisc
*nd
, sd_event
*event
, int64_t priority
) {
113 assert_return(nd
, -EINVAL
);
114 assert_return(!sd_ndisc_is_running(nd
), -EBUSY
);
115 assert_return(!nd
->event
, -EBUSY
);
118 nd
->event
= sd_event_ref(event
);
120 r
= sd_event_default(&nd
->event
);
125 nd
->event_priority
= priority
;
130 int sd_ndisc_detach_event(sd_ndisc
*nd
) {
132 assert_return(nd
, -EINVAL
);
133 assert_return(!sd_ndisc_is_running(nd
), -EBUSY
);
135 nd
->event
= sd_event_unref(nd
->event
);
139 sd_event
*sd_ndisc_get_event(sd_ndisc
*nd
) {
140 assert_return(nd
, NULL
);
145 static void ndisc_reset(sd_ndisc
*nd
) {
148 (void) event_source_disable(nd
->timeout_event_source
);
149 (void) event_source_disable(nd
->timeout_no_ra
);
150 nd
->retransmit_time
= 0;
151 nd
->recv_event_source
= sd_event_source_disable_unref(nd
->recv_event_source
);
152 nd
->fd
= safe_close(nd
->fd
);
155 static sd_ndisc
*ndisc_free(sd_ndisc
*nd
) {
160 sd_event_source_unref(nd
->timeout_event_source
);
161 sd_event_source_unref(nd
->timeout_no_ra
);
162 sd_ndisc_detach_event(nd
);
168 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc
, sd_ndisc
, ndisc_free
);
170 int sd_ndisc_new(sd_ndisc
**ret
) {
171 _cleanup_(sd_ndisc_unrefp
) sd_ndisc
*nd
= NULL
;
173 assert_return(ret
, -EINVAL
);
175 nd
= new(sd_ndisc
, 1);
189 static int ndisc_handle_datagram(sd_ndisc
*nd
, sd_ndisc_router
*rt
) {
195 r
= ndisc_router_parse(nd
, rt
);
199 (void) event_source_disable(nd
->timeout_event_source
);
201 log_ndisc(nd
, "Received Router Advertisement: flags %s preference %s lifetime %s",
202 rt
->flags
& ND_RA_FLAG_MANAGED
? "MANAGED" : rt
->flags
& ND_RA_FLAG_OTHER
? "OTHER" : "none",
203 rt
->preference
== SD_NDISC_PREFERENCE_HIGH
? "high" : rt
->preference
== SD_NDISC_PREFERENCE_LOW
? "low" : "medium",
204 FORMAT_TIMESPAN(rt
->lifetime_usec
, USEC_PER_SEC
));
206 ndisc_callback(nd
, SD_NDISC_EVENT_ROUTER
, rt
);
210 static int ndisc_recv(sd_event_source
*s
, int fd
, uint32_t revents
, void *userdata
) {
211 _cleanup_(sd_ndisc_router_unrefp
) sd_ndisc_router
*rt
= NULL
;
212 sd_ndisc
*nd
= ASSERT_PTR(userdata
);
219 buflen
= next_datagram_size_fd(fd
);
220 if (ERRNO_IS_NEG_TRANSIENT(buflen
) || ERRNO_IS_NEG_DISCONNECT(buflen
))
223 log_ndisc_errno(nd
, buflen
, "Failed to determine datagram size to read, ignoring: %m");
227 rt
= ndisc_router_new(buflen
);
231 r
= icmp6_receive(fd
, NDISC_ROUTER_RAW(rt
), rt
->raw_size
, &rt
->address
, &rt
->timestamp
);
232 if (ERRNO_IS_NEG_TRANSIENT(r
) || ERRNO_IS_NEG_DISCONNECT(r
))
237 log_ndisc(nd
, "Received RA from neither link-local nor null address. Ignoring.");
241 log_ndisc(nd
, "Received RA with invalid hop limit. Ignoring.");
245 log_ndisc(nd
, "Received invalid source address from ICMPv6 socket. Ignoring.");
249 log_ndisc_errno(nd
, r
, "Unexpected error while reading from ICMPv6, ignoring: %m");
253 /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states
254 * that hosts MUST discard messages with the null source address. */
255 if (in6_addr_is_null(&rt
->address
)) {
256 log_ndisc(nd
, "Received an ICMPv6 packet from null address, ignoring.");
260 (void) ndisc_handle_datagram(nd
, rt
);
264 static usec_t
ndisc_timeout_compute_random(usec_t val
) {
265 /* compute a time that is random within ±10% of the given value */
266 return val
- val
/ 10 +
267 (random_u64() % (2 * USEC_PER_SEC
)) * val
/ 10 / USEC_PER_SEC
;
270 static int ndisc_timeout(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
271 sd_ndisc
*nd
= ASSERT_PTR(userdata
);
278 assert_se(sd_event_now(nd
->event
, CLOCK_BOOTTIME
, &time_now
) >= 0);
280 if (!nd
->retransmit_time
)
281 nd
->retransmit_time
= ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL
);
283 if (nd
->retransmit_time
> NDISC_MAX_ROUTER_SOLICITATION_INTERVAL
/ 2)
284 nd
->retransmit_time
= ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL
);
286 nd
->retransmit_time
+= ndisc_timeout_compute_random(nd
->retransmit_time
);
289 r
= event_reset_time(nd
->event
, &nd
->timeout_event_source
,
291 time_now
+ nd
->retransmit_time
, 10 * USEC_PER_MSEC
,
293 nd
->event_priority
, "ndisc-timeout-no-ra", true);
297 r
= icmp6_send_router_solicitation(nd
->fd
, &nd
->mac_addr
);
299 log_ndisc_errno(nd
, r
, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m",
300 FORMAT_TIMESPAN(nd
->retransmit_time
, USEC_PER_SEC
));
302 log_ndisc(nd
, "Sent Router Solicitation, next solicitation in %s",
303 FORMAT_TIMESPAN(nd
->retransmit_time
, USEC_PER_SEC
));
308 (void) sd_ndisc_stop(nd
);
312 static int ndisc_timeout_no_ra(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
313 sd_ndisc
*nd
= ASSERT_PTR(userdata
);
317 log_ndisc(nd
, "No RA received before link confirmation timeout");
319 (void) event_source_disable(nd
->timeout_no_ra
);
320 ndisc_callback(nd
, SD_NDISC_EVENT_TIMEOUT
, NULL
);
325 int sd_ndisc_stop(sd_ndisc
*nd
) {
329 if (!sd_ndisc_is_running(nd
))
332 log_ndisc(nd
, "Stopping IPv6 Router Solicitation client");
338 int sd_ndisc_start(sd_ndisc
*nd
) {
342 assert_return(nd
, -EINVAL
);
343 assert_return(nd
->event
, -EINVAL
);
344 assert_return(nd
->ifindex
> 0, -EINVAL
);
346 if (sd_ndisc_is_running(nd
))
349 assert(!nd
->recv_event_source
);
351 r
= sd_event_now(nd
->event
, CLOCK_BOOTTIME
, &time_now
);
355 nd
->fd
= icmp6_bind(nd
->ifindex
, /* is_router = */ false);
359 r
= sd_event_add_io(nd
->event
, &nd
->recv_event_source
, nd
->fd
, EPOLLIN
, ndisc_recv
, nd
);
363 r
= sd_event_source_set_priority(nd
->recv_event_source
, nd
->event_priority
);
367 (void) sd_event_source_set_description(nd
->recv_event_source
, "ndisc-receive-message");
369 r
= event_reset_time(nd
->event
, &nd
->timeout_event_source
,
371 time_now
+ USEC_PER_SEC
/ 2, 1 * USEC_PER_SEC
, /* See RFC 8415 sec. 18.2.1 */
373 nd
->event_priority
, "ndisc-timeout", true);
377 r
= event_reset_time(nd
->event
, &nd
->timeout_no_ra
,
379 time_now
+ NDISC_TIMEOUT_NO_RA_USEC
, 10 * USEC_PER_MSEC
,
380 ndisc_timeout_no_ra
, nd
,
381 nd
->event_priority
, "ndisc-timeout-no-ra", true);
385 log_ndisc(nd
, "Started IPv6 Router Solicitation client");