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/ip6.h>
24 #include <netinet/in.h>
25 #include <sys/ioctl.h>
27 #include "socket-util.h"
31 #include "dhcp6-internal.h"
32 #include "sd-icmp6-nd.h"
34 #define ICMP6_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC
35 #define ICMP6_MAX_ROUTER_SOLICITATIONS 3
38 ICMP6_NEIGHBOR_DISCOVERY_IDLE
= 0,
39 ICMP6_ROUTER_SOLICITATION_SENT
= 10,
40 ICMP6_ROUTER_ADVERTISMENT_LISTEN
= 11,
43 #define IP6_MIN_MTU (unsigned)1280
44 #define ICMP6_ND_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
45 #define ICMP6_OPT_LEN_UNITS 8
47 typedef struct ICMP6Prefix ICMP6Prefix
;
52 LIST_FIELDS(ICMP6Prefix
, prefixes
);
55 sd_event_source
*timeout_valid
;
62 enum icmp6_nd_state state
;
66 struct ether_addr mac_addr
;
68 LIST_HEAD(ICMP6Prefix
, prefixes
);
70 sd_event_source
*recv
;
71 sd_event_source
*timeout
;
73 sd_icmp6_nd_callback_t callback
;
77 #define log_icmp6_nd(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "ICMPv6 CLIENT: " fmt, ##__VA_ARGS__)
79 static ICMP6Prefix
*icmp6_prefix_unref(ICMP6Prefix
*prefix
) {
80 if (prefix
&& REFCNT_DEC(prefix
->n_ref
) <= 0) {
81 prefix
->timeout_valid
=
82 sd_event_source_unref(prefix
->timeout_valid
);
90 static int icmp6_prefix_new(ICMP6Prefix
**ret
) {
91 _cleanup_free_ ICMP6Prefix
*prefix
= NULL
;
95 prefix
= new0(ICMP6Prefix
, 1);
99 prefix
->n_ref
= REFCNT_INIT
;
100 LIST_INIT(prefixes
, prefix
);
108 static void icmp6_nd_notify(sd_icmp6_nd
*nd
, int event
)
111 nd
->callback(nd
, event
, nd
->userdata
);
114 int sd_icmp6_nd_set_callback(sd_icmp6_nd
*nd
, sd_icmp6_nd_callback_t callback
,
118 nd
->callback
= callback
;
119 nd
->userdata
= userdata
;
124 int sd_icmp6_nd_set_index(sd_icmp6_nd
*nd
, int interface_index
) {
126 assert(interface_index
>= -1);
128 nd
->index
= interface_index
;
133 int sd_icmp6_nd_set_mac(sd_icmp6_nd
*nd
, const struct ether_addr
*mac_addr
) {
137 memcpy(&nd
->mac_addr
, mac_addr
, sizeof(nd
->mac_addr
));
145 int sd_icmp6_nd_attach_event(sd_icmp6_nd
*nd
, sd_event
*event
, int priority
) {
148 assert_return(nd
, -EINVAL
);
149 assert_return(!nd
->event
, -EBUSY
);
152 nd
->event
= sd_event_ref(event
);
154 r
= sd_event_default(&nd
->event
);
159 nd
->event_priority
= priority
;
164 int sd_icmp6_nd_detach_event(sd_icmp6_nd
*nd
) {
165 assert_return(nd
, -EINVAL
);
167 nd
->event
= sd_event_unref(nd
->event
);
172 sd_event
*sd_icmp6_nd_get_event(sd_icmp6_nd
*nd
) {
178 sd_icmp6_nd
*sd_icmp6_nd_ref(sd_icmp6_nd
*nd
) {
181 assert_se(REFCNT_INC(nd
->n_ref
) >= 2);
186 static int icmp6_nd_init(sd_icmp6_nd
*nd
) {
189 nd
->recv
= sd_event_source_unref(nd
->recv
);
190 nd
->fd
= asynchronous_close(nd
->fd
);
191 nd
->timeout
= sd_event_source_unref(nd
->timeout
);
196 sd_icmp6_nd
*sd_icmp6_nd_unref(sd_icmp6_nd
*nd
) {
197 if (nd
&& REFCNT_DEC(nd
->n_ref
) == 0) {
198 ICMP6Prefix
*prefix
, *p
;
201 sd_icmp6_nd_detach_event(nd
);
203 LIST_FOREACH_SAFE(prefixes
, prefix
, p
, nd
->prefixes
) {
204 LIST_REMOVE(prefixes
, nd
->prefixes
, prefix
);
206 prefix
= icmp6_prefix_unref(prefix
);
215 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_icmp6_nd
*, sd_icmp6_nd_unref
);
216 #define _cleanup_sd_icmp6_nd_free_ _cleanup_(sd_icmp6_nd_unrefp)
218 int sd_icmp6_nd_new(sd_icmp6_nd
**ret
) {
219 _cleanup_sd_icmp6_nd_free_ sd_icmp6_nd
*nd
= NULL
;
223 nd
= new0(sd_icmp6_nd
, 1);
227 nd
->n_ref
= REFCNT_INIT
;
232 LIST_HEAD_INIT(nd
->prefixes
);
240 int sd_icmp6_ra_get_mtu(sd_icmp6_nd
*nd
, uint32_t *mtu
) {
241 assert_return(nd
, -EINVAL
);
242 assert_return(mtu
, -EINVAL
);
252 static int icmp6_ra_parse(sd_icmp6_nd
*nd
, struct nd_router_advert
*ra
,
255 struct nd_opt_hdr
*opt_hdr
;
257 assert_return(nd
, -EINVAL
);
258 assert_return(ra
, -EINVAL
);
261 if (len
< ICMP6_OPT_LEN_UNITS
) {
262 log_icmp6_nd(nd
, "Router Advertisement below minimum length");
270 while (len
!= 0 && len
>= opt_hdr
->nd_opt_len
* ICMP6_OPT_LEN_UNITS
) {
271 struct nd_opt_mtu
*opt_mtu
;
274 if (opt_hdr
->nd_opt_len
== 0)
277 switch (opt_hdr
->nd_opt_type
) {
281 mtu
= be32toh(opt_mtu
->nd_opt_mtu_mtu
);
283 if (mtu
!= nd
->mtu
) {
284 nd
->mtu
= MAX(mtu
, IP6_MIN_MTU
);
286 log_icmp6_nd(nd
, "Router Advertisement link MTU %d using %d",
294 len
-= opt_hdr
->nd_opt_len
* ICMP6_OPT_LEN_UNITS
;
295 opt
= (void *)((char *)opt
+
296 opt_hdr
->nd_opt_len
* ICMP6_OPT_LEN_UNITS
);
301 log_icmp6_nd(nd
, "Router Advertisement contains %zd bytes of trailing garbage", len
);
306 static int icmp6_router_advertisment_recv(sd_event_source
*s
, int fd
,
307 uint32_t revents
, void *userdata
)
309 sd_icmp6_nd
*nd
= userdata
;
312 _cleanup_free_
struct nd_router_advert
*ra
= NULL
;
313 int event
= ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE
;
319 r
= ioctl(fd
, FIONREAD
, &buflen
);
320 if (r
< 0 || buflen
<= 0)
321 buflen
= ICMP6_ND_RECV_SIZE
;
327 len
= read(fd
, ra
, buflen
);
329 log_icmp6_nd(nd
, "Could not receive message from UDP socket: %m");
333 if (ra
->nd_ra_type
!= ND_ROUTER_ADVERT
)
336 if (ra
->nd_ra_code
!= 0)
339 nd
->timeout
= sd_event_source_unref(nd
->timeout
);
341 nd
->state
= ICMP6_ROUTER_ADVERTISMENT_LISTEN
;
343 if (ra
->nd_ra_flags_reserved
& ND_RA_FLAG_OTHER
)
344 event
= ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER
;
346 if (ra
->nd_ra_flags_reserved
& ND_RA_FLAG_MANAGED
)
347 event
= ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED
;
349 log_icmp6_nd(nd
, "Received Router Advertisement flags %s/%s",
350 ra
->nd_ra_flags_reserved
& ND_RA_FLAG_MANAGED
? "MANAGED": "none",
351 ra
->nd_ra_flags_reserved
& ND_RA_FLAG_OTHER
? "OTHER": "none");
353 if (event
!= ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE
) {
354 r
= icmp6_ra_parse(nd
, ra
, len
);
356 log_icmp6_nd(nd
, "Could not parse Router Advertisement: %s",
362 icmp6_nd_notify(nd
, event
);
367 static int icmp6_router_solicitation_timeout(sd_event_source
*s
, uint64_t usec
,
370 sd_icmp6_nd
*nd
= userdata
;
371 uint64_t time_now
, next_timeout
;
372 struct ether_addr unset
= { };
373 struct ether_addr
*addr
= NULL
;
380 nd
->timeout
= sd_event_source_unref(nd
->timeout
);
382 if (nd
->nd_sent
>= ICMP6_MAX_ROUTER_SOLICITATIONS
) {
383 icmp6_nd_notify(nd
, ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT
);
384 nd
->state
= ICMP6_ROUTER_ADVERTISMENT_LISTEN
;
386 if (memcmp(&nd
->mac_addr
, &unset
, sizeof(struct ether_addr
)))
387 addr
= &nd
->mac_addr
;
389 r
= dhcp_network_icmp6_send_router_solicitation(nd
->fd
, addr
);
391 log_icmp6_nd(nd
, "Error sending Router Solicitation");
393 nd
->state
= ICMP6_ROUTER_SOLICITATION_SENT
;
394 log_icmp6_nd(nd
, "Sent Router Solicitation");
399 r
= sd_event_now(nd
->event
, clock_boottime_or_monotonic(), &time_now
);
401 icmp6_nd_notify(nd
, r
);
405 next_timeout
= time_now
+ ICMP6_ROUTER_SOLICITATION_INTERVAL
;
407 r
= sd_event_add_time(nd
->event
, &nd
->timeout
, clock_boottime_or_monotonic(),
409 icmp6_router_solicitation_timeout
, nd
);
411 icmp6_nd_notify(nd
, r
);
415 r
= sd_event_source_set_priority(nd
->timeout
,
418 icmp6_nd_notify(nd
, r
);
422 r
= sd_event_source_set_description(nd
->timeout
, "icmp6-timeout");
424 icmp6_nd_notify(nd
, r
);
432 int sd_icmp6_nd_stop(sd_icmp6_nd
*nd
) {
433 assert_return(nd
, -EINVAL
);
434 assert_return(nd
->event
, -EINVAL
);
436 log_icmp6_nd(client
, "Stop ICMPv6");
440 nd
->state
= ICMP6_NEIGHBOR_DISCOVERY_IDLE
;
445 int sd_icmp6_router_solicitation_start(sd_icmp6_nd
*nd
) {
451 if (nd
->state
!= ICMP6_NEIGHBOR_DISCOVERY_IDLE
)
457 r
= dhcp_network_icmp6_bind_router_solicitation(nd
->index
);
463 r
= sd_event_add_io(nd
->event
, &nd
->recv
, nd
->fd
, EPOLLIN
,
464 icmp6_router_advertisment_recv
, nd
);
468 r
= sd_event_source_set_priority(nd
->recv
, nd
->event_priority
);
472 r
= sd_event_source_set_description(nd
->recv
, "icmp6-receive-message");
476 r
= sd_event_add_time(nd
->event
, &nd
->timeout
, clock_boottime_or_monotonic(),
477 0, 0, icmp6_router_solicitation_timeout
, nd
);
481 r
= sd_event_source_set_priority(nd
->timeout
, nd
->event_priority
);
485 r
= sd_event_source_set_description(nd
->timeout
, "icmp6-timeout");
490 log_icmp6_nd(client
, "Start Router Solicitation");