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.h"
19 #include "random-util.h"
20 #include "socket-util.h"
21 #include "string-table.h"
22 #include "string-util.h"
24 #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
26 static const char * const ndisc_event_table
[_SD_NDISC_EVENT_MAX
] = {
27 [SD_NDISC_EVENT_TIMEOUT
] = "timeout",
28 [SD_NDISC_EVENT_ROUTER
] = "router",
31 DEFINE_STRING_TABLE_LOOKUP(ndisc_event
, sd_ndisc_event
);
33 static void ndisc_callback(sd_ndisc
*ndisc
, sd_ndisc_event event
, sd_ndisc_router
*rt
) {
35 assert(event
>= 0 && event
< _SD_NDISC_EVENT_MAX
);
37 if (!ndisc
->callback
) {
38 log_ndisc("Received '%s' event.", ndisc_event_to_string(event
));
42 log_ndisc("Invoking callback for '%s' event.", ndisc_event_to_string(event
));
43 ndisc
->callback(ndisc
, event
, rt
, ndisc
->userdata
);
46 _public_
int sd_ndisc_set_callback(
48 sd_ndisc_callback_t callback
,
51 assert_return(nd
, -EINVAL
);
53 nd
->callback
= callback
;
54 nd
->userdata
= userdata
;
59 _public_
int sd_ndisc_set_ifindex(sd_ndisc
*nd
, int ifindex
) {
60 assert_return(nd
, -EINVAL
);
61 assert_return(ifindex
> 0, -EINVAL
);
62 assert_return(nd
->fd
< 0, -EBUSY
);
64 nd
->ifindex
= ifindex
;
68 _public_
int sd_ndisc_set_mac(sd_ndisc
*nd
, const struct ether_addr
*mac_addr
) {
69 assert_return(nd
, -EINVAL
);
72 nd
->mac_addr
= *mac_addr
;
79 _public_
int sd_ndisc_attach_event(sd_ndisc
*nd
, sd_event
*event
, int64_t priority
) {
82 assert_return(nd
, -EINVAL
);
83 assert_return(nd
->fd
< 0, -EBUSY
);
84 assert_return(!nd
->event
, -EBUSY
);
87 nd
->event
= sd_event_ref(event
);
89 r
= sd_event_default(&nd
->event
);
94 nd
->event_priority
= priority
;
99 _public_
int sd_ndisc_detach_event(sd_ndisc
*nd
) {
101 assert_return(nd
, -EINVAL
);
102 assert_return(nd
->fd
< 0, -EBUSY
);
104 nd
->event
= sd_event_unref(nd
->event
);
108 _public_ sd_event
*sd_ndisc_get_event(sd_ndisc
*nd
) {
109 assert_return(nd
, NULL
);
114 static void ndisc_reset(sd_ndisc
*nd
) {
117 (void) event_source_disable(nd
->timeout_event_source
);
118 (void) event_source_disable(nd
->timeout_no_ra
);
119 nd
->retransmit_time
= 0;
120 nd
->recv_event_source
= sd_event_source_unref(nd
->recv_event_source
);
121 nd
->fd
= safe_close(nd
->fd
);
124 static sd_ndisc
*ndisc_free(sd_ndisc
*nd
) {
127 nd
->timeout_event_source
= sd_event_source_unref(nd
->timeout_event_source
);
128 nd
->timeout_no_ra
= sd_event_source_unref(nd
->timeout_no_ra
);
131 sd_ndisc_detach_event(nd
);
135 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc
, sd_ndisc
, ndisc_free
);
137 _public_
int sd_ndisc_new(sd_ndisc
**ret
) {
138 _cleanup_(sd_ndisc_unrefp
) sd_ndisc
*nd
= NULL
;
140 assert_return(ret
, -EINVAL
);
142 nd
= new(sd_ndisc
, 1);
156 _public_
int sd_ndisc_get_mtu(sd_ndisc
*nd
, uint32_t *mtu
) {
157 assert_return(nd
, -EINVAL
);
158 assert_return(mtu
, -EINVAL
);
167 _public_
int sd_ndisc_get_hop_limit(sd_ndisc
*nd
, uint8_t *ret
) {
168 assert_return(nd
, -EINVAL
);
169 assert_return(ret
, -EINVAL
);
171 if (nd
->hop_limit
== 0)
174 *ret
= nd
->hop_limit
;
178 static int ndisc_handle_datagram(sd_ndisc
*nd
, sd_ndisc_router
*rt
) {
184 r
= ndisc_router_parse(rt
);
185 if (r
== -EBADMSG
) /* Bad packet */
190 /* Update global variables we keep */
193 if (rt
->hop_limit
> 0)
194 nd
->hop_limit
= rt
->hop_limit
;
196 log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16
" sec",
197 rt
->flags
& ND_RA_FLAG_MANAGED
? "MANAGED" : rt
->flags
& ND_RA_FLAG_OTHER
? "OTHER" : "none",
198 rt
->preference
== SD_NDISC_PREFERENCE_HIGH
? "high" : rt
->preference
== SD_NDISC_PREFERENCE_LOW
? "low" : "medium",
201 ndisc_callback(nd
, SD_NDISC_EVENT_ROUTER
, rt
);
205 static int ndisc_recv(sd_event_source
*s
, int fd
, uint32_t revents
, void *userdata
) {
206 _cleanup_(sd_ndisc_router_unrefp
) sd_ndisc_router
*rt
= NULL
;
207 sd_ndisc
*nd
= userdata
;
210 _cleanup_free_
char *addr
= NULL
;
216 buflen
= next_datagram_size_fd(fd
);
218 return log_ndisc_errno(buflen
, "Failed to determine datagram size to read: %m");
220 rt
= ndisc_router_new(buflen
);
224 r
= icmp6_receive(fd
, NDISC_ROUTER_RAW(rt
), rt
->raw_size
, &rt
->address
,
229 (void) in_addr_to_string(AF_INET6
, (union in_addr_union
*) &rt
->address
, &addr
);
230 log_ndisc("Received RA from non-link-local address %s. Ignoring", addr
);
234 log_ndisc("Received RA with invalid hop limit. Ignoring.");
238 log_ndisc("Received invalid source address from ICMPv6 socket. Ignoring.");
241 case -EAGAIN
: /* ignore spurious wakeups */
245 log_ndisc_errno(r
, "Unexpected error while reading from ICMPv6, ignoring: %m");
252 (void) event_source_disable(nd
->timeout_event_source
);
254 return ndisc_handle_datagram(nd
, rt
);
257 static usec_t
ndisc_timeout_compute_random(usec_t val
) {
258 /* compute a time that is random within ±10% of the given value */
259 return val
- val
/ 10 +
260 (random_u64() % (2 * USEC_PER_SEC
)) * val
/ 10 / USEC_PER_SEC
;
263 static int ndisc_timeout(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
264 char time_string
[FORMAT_TIMESPAN_MAX
];
265 sd_ndisc
*nd
= userdata
;
273 assert_se(sd_event_now(nd
->event
, clock_boottime_or_monotonic(), &time_now
) >= 0);
275 if (!nd
->retransmit_time
)
276 nd
->retransmit_time
= ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL
);
278 if (nd
->retransmit_time
> NDISC_MAX_ROUTER_SOLICITATION_INTERVAL
/ 2)
279 nd
->retransmit_time
= ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL
);
281 nd
->retransmit_time
+= ndisc_timeout_compute_random(nd
->retransmit_time
);
284 r
= event_reset_time(nd
->event
, &nd
->timeout_event_source
,
285 clock_boottime_or_monotonic(),
286 time_now
+ nd
->retransmit_time
, 10 * USEC_PER_MSEC
,
288 nd
->event_priority
, "ndisc-timeout-no-ra", true);
292 r
= icmp6_send_router_solicitation(nd
->fd
, &nd
->mac_addr
);
294 log_ndisc_errno(r
, "Error sending Router Solicitation: %m");
298 log_ndisc("Sent Router Solicitation, next solicitation in %s",
299 format_timespan(time_string
, FORMAT_TIMESPAN_MAX
,
300 nd
->retransmit_time
, USEC_PER_SEC
));
305 (void) sd_ndisc_stop(nd
);
309 static int ndisc_timeout_no_ra(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
310 sd_ndisc
*nd
= userdata
;
315 log_ndisc("No RA received before link confirmation timeout");
317 (void) event_source_disable(nd
->timeout_no_ra
);
318 ndisc_callback(nd
, SD_NDISC_EVENT_TIMEOUT
, NULL
);
323 _public_
int sd_ndisc_stop(sd_ndisc
*nd
) {
330 log_ndisc("Stopping IPv6 Router Solicitation client");
336 _public_
int sd_ndisc_start(sd_ndisc
*nd
) {
340 assert_return(nd
, -EINVAL
);
341 assert_return(nd
->event
, -EINVAL
);
342 assert_return(nd
->ifindex
> 0, -EINVAL
);
347 assert(!nd
->recv_event_source
);
349 r
= sd_event_now(nd
->event
, clock_boottime_or_monotonic(), &time_now
);
353 nd
->fd
= icmp6_bind_router_solicitation(nd
->ifindex
);
357 r
= sd_event_add_io(nd
->event
, &nd
->recv_event_source
, nd
->fd
, EPOLLIN
, ndisc_recv
, nd
);
361 r
= sd_event_source_set_priority(nd
->recv_event_source
, nd
->event_priority
);
365 (void) sd_event_source_set_description(nd
->recv_event_source
, "ndisc-receive-message");
367 r
= event_reset_time(nd
->event
, &nd
->timeout_event_source
,
368 clock_boottime_or_monotonic(),
369 time_now
+ USEC_PER_SEC
/ 2, 1 * USEC_PER_SEC
, /* See RFC 8415 sec. 18.2.1 */
371 nd
->event_priority
, "ndisc-timeout", true);
375 r
= event_reset_time(nd
->event
, &nd
->timeout_no_ra
,
376 clock_boottime_or_monotonic(),
377 time_now
+ NDISC_TIMEOUT_NO_RA_USEC
, 10 * USEC_PER_MSEC
,
378 ndisc_timeout_no_ra
, nd
,
379 nd
->event_priority
, "ndisc-timeout-no-ra", true);
383 log_ndisc("Started IPv6 Router Solicitation client");