1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright (C) 2014 Intel Corporation. All rights reserved.
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 #include <netinet/icmp6.h>
22 #include <netinet/in.h>
26 #include "alloc-util.h"
28 #include "icmp6-util.h"
29 #include "in-addr-util.h"
30 #include "ndisc-internal.h"
31 #include "ndisc-router.h"
32 #include "random-util.h"
33 #include "socket-util.h"
34 #include "string-util.h"
37 #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
39 static void ndisc_callback(sd_ndisc
*ndisc
, sd_ndisc_event event
, sd_ndisc_router
*rt
) {
42 log_ndisc("Invoking callback for '%c'.", event
);
47 ndisc
->callback(ndisc
, event
, rt
, ndisc
->userdata
);
50 _public_
int sd_ndisc_set_callback(
52 sd_ndisc_callback_t callback
,
55 assert_return(nd
, -EINVAL
);
57 nd
->callback
= callback
;
58 nd
->userdata
= userdata
;
63 _public_
int sd_ndisc_set_ifindex(sd_ndisc
*nd
, int ifindex
) {
64 assert_return(nd
, -EINVAL
);
65 assert_return(ifindex
> 0, -EINVAL
);
66 assert_return(nd
->fd
< 0, -EBUSY
);
68 nd
->ifindex
= ifindex
;
72 _public_
int sd_ndisc_set_mac(sd_ndisc
*nd
, const struct ether_addr
*mac_addr
) {
73 assert_return(nd
, -EINVAL
);
76 nd
->mac_addr
= *mac_addr
;
83 _public_
int sd_ndisc_attach_event(sd_ndisc
*nd
, sd_event
*event
, int64_t priority
) {
86 assert_return(nd
, -EINVAL
);
87 assert_return(nd
->fd
< 0, -EBUSY
);
88 assert_return(!nd
->event
, -EBUSY
);
91 nd
->event
= sd_event_ref(event
);
93 r
= sd_event_default(&nd
->event
);
98 nd
->event_priority
= priority
;
103 _public_
int sd_ndisc_detach_event(sd_ndisc
*nd
) {
105 assert_return(nd
, -EINVAL
);
106 assert_return(nd
->fd
< 0, -EBUSY
);
108 nd
->event
= sd_event_unref(nd
->event
);
112 _public_ sd_event
*sd_ndisc_get_event(sd_ndisc
*nd
) {
113 assert_return(nd
, NULL
);
118 _public_ sd_ndisc
*sd_ndisc_ref(sd_ndisc
*nd
) {
123 assert(nd
->n_ref
> 0);
129 static int ndisc_reset(sd_ndisc
*nd
) {
132 nd
->timeout_event_source
= sd_event_source_unref(nd
->timeout_event_source
);
133 nd
->timeout_no_ra
= sd_event_source_unref(nd
->timeout_no_ra
);
134 nd
->retransmit_time
= 0;
135 nd
->recv_event_source
= sd_event_source_unref(nd
->recv_event_source
);
136 nd
->fd
= safe_close(nd
->fd
);
141 _public_ sd_ndisc
*sd_ndisc_unref(sd_ndisc
*nd
) {
146 assert(nd
->n_ref
> 0);
153 sd_ndisc_detach_event(nd
);
157 _public_
int sd_ndisc_new(sd_ndisc
**ret
) {
158 _cleanup_(sd_ndisc_unrefp
) sd_ndisc
*nd
= NULL
;
160 assert_return(ret
, -EINVAL
);
162 nd
= new0(sd_ndisc
, 1);
175 _public_
int sd_ndisc_get_mtu(sd_ndisc
*nd
, uint32_t *mtu
) {
176 assert_return(nd
, -EINVAL
);
177 assert_return(mtu
, -EINVAL
);
186 _public_
int sd_ndisc_get_hop_limit(sd_ndisc
*nd
, uint8_t *ret
) {
187 assert_return(nd
, -EINVAL
);
188 assert_return(ret
, -EINVAL
);
190 if (nd
->hop_limit
== 0)
193 *ret
= nd
->hop_limit
;
197 static int ndisc_handle_datagram(sd_ndisc
*nd
, sd_ndisc_router
*rt
) {
203 r
= ndisc_router_parse(rt
);
204 if (r
== -EBADMSG
) /* Bad packet */
209 /* Update global variables we keep */
212 if (rt
->hop_limit
> 0)
213 nd
->hop_limit
= rt
->hop_limit
;
215 log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16
" sec",
216 rt
->flags
& ND_RA_FLAG_MANAGED
? "MANAGED" : rt
->flags
& ND_RA_FLAG_OTHER
? "OTHER" : "none",
217 rt
->preference
== SD_NDISC_PREFERENCE_HIGH
? "high" : rt
->preference
== SD_NDISC_PREFERENCE_LOW
? "low" : "medium",
220 ndisc_callback(nd
, SD_NDISC_EVENT_ROUTER
, rt
);
224 static int ndisc_recv(sd_event_source
*s
, int fd
, uint32_t revents
, void *userdata
) {
225 _cleanup_(sd_ndisc_router_unrefp
) sd_ndisc_router
*rt
= NULL
;
226 sd_ndisc
*nd
= userdata
;
229 _cleanup_free_
char *addr
= NULL
;
235 buflen
= next_datagram_size_fd(fd
);
237 return log_ndisc_errno(buflen
, "Failed to determine datagram size to read: %m");
239 rt
= ndisc_router_new(buflen
);
243 r
= icmp6_receive(fd
, NDISC_ROUTER_RAW(rt
), rt
->raw_size
, &rt
->address
,
248 (void) in_addr_to_string(AF_INET6
, (union in_addr_union
*) &rt
->address
, &addr
);
249 log_ndisc("Received RA from non-link-local address %s. Ignoring", addr
);
253 log_ndisc("Received RA with invalid hop limit. Ignoring.");
257 log_ndisc("Received invalid source address from ICMPv6 socket.");
264 nd
->timeout_event_source
= sd_event_source_unref(nd
->timeout_event_source
);
266 return ndisc_handle_datagram(nd
, rt
);
269 static usec_t
ndisc_timeout_compute_random(usec_t val
) {
270 /* compute a time that is random within ±10% of the given value */
271 return val
- val
/ 10 +
272 (random_u64() % (2 * USEC_PER_SEC
)) * val
/ 10 / USEC_PER_SEC
;
275 static int ndisc_timeout(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
276 sd_ndisc
*nd
= userdata
;
279 char time_string
[FORMAT_TIMESPAN_MAX
];
285 assert_se(sd_event_now(nd
->event
, clock_boottime_or_monotonic(), &time_now
) >= 0);
287 nd
->timeout_event_source
= sd_event_source_unref(nd
->timeout_event_source
);
289 if (!nd
->retransmit_time
)
290 nd
->retransmit_time
= ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL
);
292 if (nd
->retransmit_time
> NDISC_MAX_ROUTER_SOLICITATION_INTERVAL
/ 2)
293 nd
->retransmit_time
= ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL
);
295 nd
->retransmit_time
+= ndisc_timeout_compute_random(nd
->retransmit_time
);
298 r
= sd_event_add_time(nd
->event
, &nd
->timeout_event_source
,
299 clock_boottime_or_monotonic(),
300 time_now
+ nd
->retransmit_time
,
301 10 * USEC_PER_MSEC
, ndisc_timeout
, nd
);
305 r
= sd_event_source_set_priority(nd
->timeout_event_source
, nd
->event_priority
);
309 (void) sd_event_source_set_description(nd
->timeout_event_source
, "ndisc-timeout-no-ra");
311 r
= sd_event_source_set_enabled(nd
->timeout_event_source
, SD_EVENT_ONESHOT
);
313 log_ndisc_errno(r
, "Error reenabling timer: %m");
317 r
= icmp6_send_router_solicitation(nd
->fd
, &nd
->mac_addr
);
319 log_ndisc_errno(r
, "Error sending Router Solicitation: %m");
323 log_ndisc("Sent Router Solicitation, next solicitation in %s",
324 format_timespan(time_string
, FORMAT_TIMESPAN_MAX
,
325 nd
->retransmit_time
, USEC_PER_SEC
));
334 static int ndisc_timeout_no_ra(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
335 sd_ndisc
*nd
= userdata
;
340 log_ndisc("No RA received before link confirmation timeout");
342 nd
->timeout_no_ra
= sd_event_source_unref(nd
->timeout_no_ra
);
343 ndisc_callback(nd
, SD_NDISC_EVENT_TIMEOUT
, NULL
);
348 _public_
int sd_ndisc_stop(sd_ndisc
*nd
) {
349 assert_return(nd
, -EINVAL
);
354 log_ndisc("Stopping IPv6 Router Solicitation client");
360 _public_
int sd_ndisc_start(sd_ndisc
*nd
) {
364 assert_return(nd
, -EINVAL
);
365 assert_return(nd
->event
, -EINVAL
);
366 assert_return(nd
->ifindex
> 0, -EINVAL
);
371 assert(!nd
->recv_event_source
);
372 assert(!nd
->timeout_event_source
);
374 r
= sd_event_now(nd
->event
, clock_boottime_or_monotonic(), &time_now
);
378 nd
->fd
= icmp6_bind_router_solicitation(nd
->ifindex
);
382 r
= sd_event_add_io(nd
->event
, &nd
->recv_event_source
, nd
->fd
, EPOLLIN
, ndisc_recv
, nd
);
386 r
= sd_event_source_set_priority(nd
->recv_event_source
, nd
->event_priority
);
390 (void) sd_event_source_set_description(nd
->recv_event_source
, "ndisc-receive-message");
392 r
= sd_event_add_time(nd
->event
, &nd
->timeout_event_source
, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout
, nd
);
396 r
= sd_event_source_set_priority(nd
->timeout_event_source
, nd
->event_priority
);
400 (void) sd_event_source_set_description(nd
->timeout_event_source
, "ndisc-timeout");
402 r
= sd_event_add_time(nd
->event
, &nd
->timeout_no_ra
,
403 clock_boottime_or_monotonic(),
404 time_now
+ NDISC_TIMEOUT_NO_RA_USEC
,
405 10 * USEC_PER_MSEC
, ndisc_timeout_no_ra
, nd
);
409 r
= sd_event_source_set_priority(nd
->timeout_no_ra
, nd
->event_priority
);
413 (void) sd_event_source_set_description(nd
->timeout_no_ra
, "ndisc-timeout-no-ra");
415 log_ndisc("Started IPv6 Router Solicitation client");