2 This file is part of systemd.
4 Copyright (C) 2014 Intel Corporation. All rights reserved.
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 #include <netinet/icmp6.h>
21 #include <netinet/in.h>
25 #include "alloc-util.h"
27 #include "icmp6-util.h"
28 #include "in-addr-util.h"
29 #include "ndisc-internal.h"
30 #include "ndisc-router.h"
31 #include "socket-util.h"
32 #include "string-util.h"
35 #define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
36 #define NDISC_MAX_ROUTER_SOLICITATIONS 3U
38 static void ndisc_callback(sd_ndisc
*ndisc
, sd_ndisc_event event
, sd_ndisc_router
*rt
) {
41 log_ndisc("Invoking callback for '%c'.", event
);
46 ndisc
->callback(ndisc
, event
, rt
, ndisc
->userdata
);
49 _public_
int sd_ndisc_set_callback(
51 sd_ndisc_callback_t callback
,
54 assert_return(nd
, -EINVAL
);
56 nd
->callback
= callback
;
57 nd
->userdata
= userdata
;
62 _public_
int sd_ndisc_set_ifindex(sd_ndisc
*nd
, int ifindex
) {
63 assert_return(nd
, -EINVAL
);
64 assert_return(ifindex
> 0, -EINVAL
);
65 assert_return(nd
->fd
< 0, -EBUSY
);
67 nd
->ifindex
= ifindex
;
71 _public_
int sd_ndisc_set_mac(sd_ndisc
*nd
, const struct ether_addr
*mac_addr
) {
72 assert_return(nd
, -EINVAL
);
75 nd
->mac_addr
= *mac_addr
;
82 _public_
int sd_ndisc_attach_event(sd_ndisc
*nd
, sd_event
*event
, int64_t priority
) {
85 assert_return(nd
, -EINVAL
);
86 assert_return(nd
->fd
< 0, -EBUSY
);
87 assert_return(!nd
->event
, -EBUSY
);
90 nd
->event
= sd_event_ref(event
);
92 r
= sd_event_default(&nd
->event
);
97 nd
->event_priority
= priority
;
102 _public_
int sd_ndisc_detach_event(sd_ndisc
*nd
) {
104 assert_return(nd
, -EINVAL
);
105 assert_return(nd
->fd
< 0, -EBUSY
);
107 nd
->event
= sd_event_unref(nd
->event
);
111 _public_ sd_event
*sd_ndisc_get_event(sd_ndisc
*nd
) {
112 assert_return(nd
, NULL
);
117 _public_ sd_ndisc
*sd_ndisc_ref(sd_ndisc
*nd
) {
122 assert(nd
->n_ref
> 0);
128 static int ndisc_reset(sd_ndisc
*nd
) {
131 nd
->timeout_event_source
= sd_event_source_unref(nd
->timeout_event_source
);
132 nd
->recv_event_source
= sd_event_source_unref(nd
->recv_event_source
);
133 nd
->fd
= safe_close(nd
->fd
);
139 _public_ sd_ndisc
*sd_ndisc_unref(sd_ndisc
*nd
) {
144 assert(nd
->n_ref
> 0);
151 sd_ndisc_detach_event(nd
);
155 _public_
int sd_ndisc_new(sd_ndisc
**ret
) {
156 _cleanup_(sd_ndisc_unrefp
) sd_ndisc
*nd
= NULL
;
158 assert_return(ret
, -EINVAL
);
160 nd
= new0(sd_ndisc
, 1);
173 _public_
int sd_ndisc_get_mtu(sd_ndisc
*nd
, uint32_t *mtu
) {
174 assert_return(nd
, -EINVAL
);
175 assert_return(mtu
, -EINVAL
);
184 _public_
int sd_ndisc_get_hop_limit(sd_ndisc
*nd
, uint8_t *ret
) {
185 assert_return(nd
, -EINVAL
);
186 assert_return(ret
, -EINVAL
);
188 if (nd
->hop_limit
== 0)
191 *ret
= nd
->hop_limit
;
195 static int ndisc_handle_datagram(sd_ndisc
*nd
, sd_ndisc_router
*rt
) {
201 r
= ndisc_router_parse(rt
);
202 if (r
== -EBADMSG
) /* Bad packet */
207 /* Update global variables we keep */
210 if (rt
->hop_limit
> 0)
211 nd
->hop_limit
= rt
->hop_limit
;
213 log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16
" sec",
214 rt
->flags
& ND_RA_FLAG_MANAGED
? "MANAGED" : rt
->flags
& ND_RA_FLAG_OTHER
? "OTHER" : "none",
215 rt
->preference
== SD_NDISC_PREFERENCE_HIGH
? "high" : rt
->preference
== SD_NDISC_PREFERENCE_LOW
? "low" : "medium",
218 ndisc_callback(nd
, SD_NDISC_EVENT_ROUTER
, rt
);
222 static int ndisc_recv(sd_event_source
*s
, int fd
, uint32_t revents
, void *userdata
) {
223 _cleanup_(sd_ndisc_router_unrefp
) sd_ndisc_router
*rt
= NULL
;
224 sd_ndisc
*nd
= userdata
;
227 _cleanup_free_
char *addr
= NULL
;
233 buflen
= next_datagram_size_fd(fd
);
235 return log_ndisc_errno(buflen
, "Failed to determine datagram size to read: %m");
237 rt
= ndisc_router_new(buflen
);
241 r
= icmp6_receive(fd
, NDISC_ROUTER_RAW(rt
), rt
->raw_size
, &rt
->address
,
246 (void) in_addr_to_string(AF_INET6
, (union in_addr_union
*) &rt
->address
, &addr
);
247 log_ndisc("Received RA from non-link-local address %s. Ignoring", addr
);
251 log_ndisc("Received RA with invalid hop limit. Ignoring.");
255 log_ndisc("Received invalid source address from ICMPv6 socket.");
262 nd
->timeout_event_source
= sd_event_source_unref(nd
->timeout_event_source
);
264 return ndisc_handle_datagram(nd
, rt
);
267 static int ndisc_timeout(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
268 sd_ndisc
*nd
= userdata
;
269 usec_t time_now
, next_timeout
;
276 if (nd
->nd_sent
>= NDISC_MAX_ROUTER_SOLICITATIONS
) {
277 nd
->timeout_event_source
= sd_event_source_unref(nd
->timeout_event_source
);
278 ndisc_callback(nd
, SD_NDISC_EVENT_TIMEOUT
, NULL
);
282 r
= icmp6_send_router_solicitation(nd
->fd
, &nd
->mac_addr
);
284 log_ndisc_errno(r
, "Error sending Router Solicitation: %m");
288 log_ndisc("Sent Router Solicitation");
291 assert_se(sd_event_now(nd
->event
, clock_boottime_or_monotonic(), &time_now
) >= 0);
292 next_timeout
= time_now
+ NDISC_ROUTER_SOLICITATION_INTERVAL
;
294 r
= sd_event_source_set_time(nd
->timeout_event_source
, next_timeout
);
296 log_ndisc_errno(r
, "Error updating timer: %m");
300 r
= sd_event_source_set_enabled(nd
->timeout_event_source
, SD_EVENT_ONESHOT
);
302 log_ndisc_errno(r
, "Error reenabling timer: %m");
313 _public_
int sd_ndisc_stop(sd_ndisc
*nd
) {
314 assert_return(nd
, -EINVAL
);
319 log_ndisc("Stopping IPv6 Router Solicitation client");
325 _public_
int sd_ndisc_start(sd_ndisc
*nd
) {
328 assert_return(nd
, -EINVAL
);
329 assert_return(nd
->event
, -EINVAL
);
330 assert_return(nd
->ifindex
> 0, -EINVAL
);
335 assert(!nd
->recv_event_source
);
336 assert(!nd
->timeout_event_source
);
338 nd
->fd
= icmp6_bind_router_solicitation(nd
->ifindex
);
342 r
= sd_event_add_io(nd
->event
, &nd
->recv_event_source
, nd
->fd
, EPOLLIN
, ndisc_recv
, nd
);
346 r
= sd_event_source_set_priority(nd
->recv_event_source
, nd
->event_priority
);
350 (void) sd_event_source_set_description(nd
->recv_event_source
, "ndisc-receive-message");
352 r
= sd_event_add_time(nd
->event
, &nd
->timeout_event_source
, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout
, nd
);
356 r
= sd_event_source_set_priority(nd
->timeout_event_source
, nd
->event_priority
);
360 (void) sd_event_source_set_description(nd
->timeout_event_source
, "ndisc-timeout");
362 log_ndisc("Started IPv6 Router Solicitation client");