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 <arpa/inet.h>
26 #include "networkd-ndisc.h"
27 #include "networkd-route.h"
29 #define NDISC_DNSSL_MAX 64U
30 #define NDISC_RDNSS_MAX 64U
31 #define NDISC_PREFIX_LFT_MIN 7200U
33 static int ndisc_netlink_handler(sd_netlink
*rtnl
, sd_netlink_message
*m
, void *userdata
) {
34 _cleanup_link_unref_ Link
*link
= userdata
;
38 assert(link
->ndisc_messages
> 0);
40 link
->ndisc_messages
--;
42 r
= sd_netlink_message_get_errno(m
);
43 if (r
< 0 && r
!= -EEXIST
)
44 log_link_error_errno(link
, r
, "Could not set NDisc route or address: %m");
46 if (link
->ndisc_messages
== 0) {
47 link
->ndisc_configured
= true;
48 link_check_ready(link
);
54 static void ndisc_router_process_default(Link
*link
, sd_ndisc_router
*rt
) {
55 _cleanup_route_free_ Route
*route
= NULL
;
56 struct in6_addr gateway
;
68 r
= sd_ndisc_router_get_lifetime(rt
, &lifetime
);
70 log_link_warning_errno(link
, r
, "Failed to get gateway address from RA: %m");
73 if (lifetime
== 0) /* not a default router */
76 r
= sd_ndisc_router_get_address(rt
, &gateway
);
78 log_link_warning_errno(link
, r
, "Failed to get gateway address from RA: %m");
82 SET_FOREACH(address
, link
->addresses
, i
) {
83 if (!memcmp(&gateway
, &address
->in_addr
.in6
,
84 sizeof(address
->in_addr
.in6
))) {
85 char buffer
[INET6_ADDRSTRLEN
];
87 log_link_debug(link
, "No NDisc route added, gateway %s matches local address",
89 &address
->in_addr
.in6
,
90 buffer
, sizeof(buffer
)));
95 SET_FOREACH(address
, link
->addresses_foreign
, i
) {
96 if (!memcmp(&gateway
, &address
->in_addr
.in6
,
97 sizeof(address
->in_addr
.in6
))) {
98 char buffer
[INET6_ADDRSTRLEN
];
100 log_link_debug(link
, "No NDisc route added, gateway %s matches local address",
102 &address
->in_addr
.in6
,
103 buffer
, sizeof(buffer
)));
108 r
= sd_ndisc_router_get_preference(rt
, &preference
);
110 log_link_warning_errno(link
, r
, "Failed to get default router preference from RA: %m");
114 r
= sd_ndisc_router_get_timestamp(rt
, clock_boottime_or_monotonic(), &time_now
);
116 log_link_warning_errno(link
, r
, "Failed to get RA timestamp: %m");
120 r
= sd_ndisc_router_get_mtu(rt
, &mtu
);
124 log_link_warning_errno(link
, r
, "Failed to get default router MTU from RA: %m");
128 r
= route_new(&route
);
130 log_link_error_errno(link
, r
, "Could not allocate route: %m");
134 route
->family
= AF_INET6
;
135 route
->table
= link
->network
->ipv6_accept_ra_route_table
;
136 route
->priority
= link
->network
->dhcp_route_metric
;
137 route
->protocol
= RTPROT_RA
;
138 route
->pref
= preference
;
139 route
->gw
.in6
= gateway
;
140 route
->lifetime
= time_now
+ lifetime
* USEC_PER_SEC
;
143 r
= route_configure(route
, link
, ndisc_netlink_handler
);
145 log_link_warning_errno(link
, r
, "Could not set default route: %m");
146 link_enter_failed(link
);
150 link
->ndisc_messages
++;
153 static void ndisc_router_process_autonomous_prefix(Link
*link
, sd_ndisc_router
*rt
) {
154 _cleanup_address_free_ Address
*address
= NULL
;
155 Address
*existing_address
;
156 uint32_t lifetime_valid
, lifetime_preferred
, lifetime_remaining
;
164 r
= sd_ndisc_router_get_timestamp(rt
, clock_boottime_or_monotonic(), &time_now
);
166 log_link_warning_errno(link
, r
, "Failed to get RA timestamp: %m");
170 r
= sd_ndisc_router_prefix_get_prefixlen(rt
, &prefixlen
);
172 log_link_error_errno(link
, r
, "Failed to get prefix length: %m");
176 r
= sd_ndisc_router_prefix_get_valid_lifetime(rt
, &lifetime_valid
);
178 log_link_error_errno(link
, r
, "Failed to get prefix valid lifetime: %m");
182 r
= sd_ndisc_router_prefix_get_preferred_lifetime(rt
, &lifetime_preferred
);
184 log_link_error_errno(link
, r
, "Failed to get prefix preferred lifetime: %m");
188 /* The preferred lifetime is never greater than the valid lifetime */
189 if (lifetime_preferred
> lifetime_valid
)
192 r
= address_new(&address
);
194 log_link_error_errno(link
, r
, "Could not allocate address: %m");
198 address
->family
= AF_INET6
;
199 r
= sd_ndisc_router_prefix_get_address(rt
, &address
->in_addr
.in6
);
201 log_link_error_errno(link
, r
, "Failed to get prefix address: %m");
205 if (in_addr_is_null(AF_INET6
, (const union in_addr_union
*) &link
->network
->ipv6_token
) == 0)
206 memcpy(((char *)&address
->in_addr
.in6
) + 8, ((char *)&link
->network
->ipv6_token
) + 8, 8);
208 /* see RFC4291 section 2.5.1 */
209 address
->in_addr
.in6
.s6_addr
[8] = link
->mac
.ether_addr_octet
[0];
210 address
->in_addr
.in6
.s6_addr
[8] ^= 1 << 1;
211 address
->in_addr
.in6
.s6_addr
[9] = link
->mac
.ether_addr_octet
[1];
212 address
->in_addr
.in6
.s6_addr
[10] = link
->mac
.ether_addr_octet
[2];
213 address
->in_addr
.in6
.s6_addr
[11] = 0xff;
214 address
->in_addr
.in6
.s6_addr
[12] = 0xfe;
215 address
->in_addr
.in6
.s6_addr
[13] = link
->mac
.ether_addr_octet
[3];
216 address
->in_addr
.in6
.s6_addr
[14] = link
->mac
.ether_addr_octet
[4];
217 address
->in_addr
.in6
.s6_addr
[15] = link
->mac
.ether_addr_octet
[5];
219 address
->prefixlen
= prefixlen
;
220 address
->flags
= IFA_F_NOPREFIXROUTE
|IFA_F_MANAGETEMPADDR
;
221 address
->cinfo
.ifa_prefered
= lifetime_preferred
;
223 /* see RFC4862 section 5.5.3.e */
224 r
= address_get(link
, address
->family
, &address
->in_addr
, address
->prefixlen
, &existing_address
);
226 lifetime_remaining
= existing_address
->cinfo
.tstamp
/ 100 + existing_address
->cinfo
.ifa_valid
- time_now
/ USEC_PER_SEC
;
227 if (lifetime_valid
> NDISC_PREFIX_LFT_MIN
|| lifetime_valid
> lifetime_remaining
)
228 address
->cinfo
.ifa_valid
= lifetime_valid
;
229 else if (lifetime_remaining
<= NDISC_PREFIX_LFT_MIN
)
230 address
->cinfo
.ifa_valid
= lifetime_remaining
;
232 address
->cinfo
.ifa_valid
= NDISC_PREFIX_LFT_MIN
;
233 } else if (lifetime_valid
> 0)
234 address
->cinfo
.ifa_valid
= lifetime_valid
;
236 return; /* see RFC4862 section 5.5.3.d */
238 if (address
->cinfo
.ifa_valid
== 0)
241 r
= address_configure(address
, link
, ndisc_netlink_handler
, true);
243 log_link_warning_errno(link
, r
, "Could not set SLAAC address: %m");
244 link_enter_failed(link
);
248 link
->ndisc_messages
++;
251 static void ndisc_router_process_onlink_prefix(Link
*link
, sd_ndisc_router
*rt
) {
252 _cleanup_route_free_ Route
*route
= NULL
;
261 r
= sd_ndisc_router_get_timestamp(rt
, clock_boottime_or_monotonic(), &time_now
);
263 log_link_warning_errno(link
, r
, "Failed to get RA timestamp: %m");
267 r
= sd_ndisc_router_prefix_get_prefixlen(rt
, &prefixlen
);
269 log_link_error_errno(link
, r
, "Failed to get prefix length: %m");
273 r
= sd_ndisc_router_prefix_get_valid_lifetime(rt
, &lifetime
);
275 log_link_error_errno(link
, r
, "Failed to get prefix lifetime: %m");
279 r
= route_new(&route
);
281 log_link_error_errno(link
, r
, "Could not allocate route: %m");
285 route
->family
= AF_INET6
;
286 route
->table
= link
->network
->ipv6_accept_ra_route_table
;
287 route
->priority
= link
->network
->dhcp_route_metric
;
288 route
->protocol
= RTPROT_RA
;
289 route
->flags
= RTM_F_PREFIX
;
290 route
->dst_prefixlen
= prefixlen
;
291 route
->lifetime
= time_now
+ lifetime
* USEC_PER_SEC
;
293 r
= sd_ndisc_router_prefix_get_address(rt
, &route
->dst
.in6
);
295 log_link_error_errno(link
, r
, "Failed to get prefix address: %m");
299 r
= route_configure(route
, link
, ndisc_netlink_handler
);
301 log_link_warning_errno(link
, r
, "Could not set prefix route: %m");
302 link_enter_failed(link
);
306 link
->ndisc_messages
++;
309 static void ndisc_router_process_route(Link
*link
, sd_ndisc_router
*rt
) {
310 _cleanup_route_free_ Route
*route
= NULL
;
311 struct in6_addr gateway
;
313 unsigned preference
, prefixlen
;
319 r
= sd_ndisc_router_route_get_lifetime(rt
, &lifetime
);
321 log_link_warning_errno(link
, r
, "Failed to get gateway address from RA: %m");
327 r
= sd_ndisc_router_get_address(rt
, &gateway
);
329 log_link_warning_errno(link
, r
, "Failed to get gateway address from RA: %m");
333 r
= sd_ndisc_router_route_get_prefixlen(rt
, &prefixlen
);
335 log_link_warning_errno(link
, r
, "Failed to get route prefix length: %m");
339 r
= sd_ndisc_router_route_get_preference(rt
, &preference
);
341 log_link_warning_errno(link
, r
, "Failed to get default router preference from RA: %m");
345 r
= sd_ndisc_router_get_timestamp(rt
, clock_boottime_or_monotonic(), &time_now
);
347 log_link_warning_errno(link
, r
, "Failed to get RA timestamp: %m");
351 r
= route_new(&route
);
353 log_link_error_errno(link
, r
, "Could not allocate route: %m");
357 route
->family
= AF_INET6
;
358 route
->table
= link
->network
->ipv6_accept_ra_route_table
;
359 route
->protocol
= RTPROT_RA
;
360 route
->pref
= preference
;
361 route
->gw
.in6
= gateway
;
362 route
->dst_prefixlen
= prefixlen
;
363 route
->lifetime
= time_now
+ lifetime
* USEC_PER_SEC
;
365 r
= sd_ndisc_router_route_get_address(rt
, &route
->dst
.in6
);
367 log_link_error_errno(link
, r
, "Failed to get route address: %m");
371 r
= route_configure(route
, link
, ndisc_netlink_handler
);
373 log_link_warning_errno(link
, r
, "Could not set additional route: %m");
374 link_enter_failed(link
);
378 link
->ndisc_messages
++;
381 static void ndisc_rdnss_hash_func(const void *p
, struct siphash
*state
) {
382 const NDiscRDNSS
*x
= p
;
384 siphash24_compress(&x
->address
, sizeof(x
->address
), state
);
387 static int ndisc_rdnss_compare_func(const void *_a
, const void *_b
) {
388 const NDiscRDNSS
*a
= _a
, *b
= _b
;
390 return memcmp(&a
->address
, &b
->address
, sizeof(a
->address
));
393 static const struct hash_ops ndisc_rdnss_hash_ops
= {
394 .hash
= ndisc_rdnss_hash_func
,
395 .compare
= ndisc_rdnss_compare_func
398 static void ndisc_router_process_rdnss(Link
*link
, sd_ndisc_router
*rt
) {
400 const struct in6_addr
*a
;
407 r
= sd_ndisc_router_get_timestamp(rt
, clock_boottime_or_monotonic(), &time_now
);
409 log_link_warning_errno(link
, r
, "Failed to get RA timestamp: %m");
413 r
= sd_ndisc_router_rdnss_get_lifetime(rt
, &lifetime
);
415 log_link_warning_errno(link
, r
, "Failed to get RDNSS lifetime: %m");
419 n
= sd_ndisc_router_rdnss_get_addresses(rt
, &a
);
421 log_link_warning_errno(link
, n
, "Failed to get RDNSS addresses: %m");
425 for (i
= 0; i
< n
; i
++) {
431 (void) set_remove(link
->ndisc_rdnss
, &d
);
436 x
= set_get(link
->ndisc_rdnss
, &d
);
438 x
->valid_until
= time_now
+ lifetime
* USEC_PER_SEC
;
444 if (set_size(link
->ndisc_rdnss
) >= NDISC_RDNSS_MAX
) {
445 log_link_warning(link
, "Too many RDNSS records per link, ignoring.");
449 r
= set_ensure_allocated(&link
->ndisc_rdnss
, &ndisc_rdnss_hash_ops
);
455 x
= new0(NDiscRDNSS
, 1);
462 x
->valid_until
= time_now
+ lifetime
* USEC_PER_SEC
;
464 r
= set_put(link
->ndisc_rdnss
, x
);
476 static void ndisc_dnssl_hash_func(const void *p
, struct siphash
*state
) {
477 const NDiscDNSSL
*x
= p
;
479 siphash24_compress(NDISC_DNSSL_DOMAIN(x
), strlen(NDISC_DNSSL_DOMAIN(x
)), state
);
482 static int ndisc_dnssl_compare_func(const void *_a
, const void *_b
) {
483 const NDiscDNSSL
*a
= _a
, *b
= _b
;
485 return strcmp(NDISC_DNSSL_DOMAIN(a
), NDISC_DNSSL_DOMAIN(b
));
488 static const struct hash_ops ndisc_dnssl_hash_ops
= {
489 .hash
= ndisc_dnssl_hash_func
,
490 .compare
= ndisc_dnssl_compare_func
493 static void ndisc_router_process_dnssl(Link
*link
, sd_ndisc_router
*rt
) {
494 _cleanup_strv_free_
char **l
= NULL
;
503 r
= sd_ndisc_router_get_timestamp(rt
, clock_boottime_or_monotonic(), &time_now
);
505 log_link_warning_errno(link
, r
, "Failed to get RA timestamp: %m");
509 r
= sd_ndisc_router_dnssl_get_lifetime(rt
, &lifetime
);
511 log_link_warning_errno(link
, r
, "Failed to get RDNSS lifetime: %m");
515 r
= sd_ndisc_router_dnssl_get_domains(rt
, &l
);
517 log_link_warning_errno(link
, r
, "Failed to get RDNSS addresses: %m");
522 _cleanup_free_ NDiscDNSSL
*s
;
525 s
= malloc0(ALIGN(sizeof(NDiscDNSSL
)) + strlen(*i
) + 1);
531 strcpy(NDISC_DNSSL_DOMAIN(s
), *i
);
534 (void) set_remove(link
->ndisc_dnssl
, s
);
539 x
= set_get(link
->ndisc_dnssl
, s
);
541 x
->valid_until
= time_now
+ lifetime
* USEC_PER_SEC
;
547 if (set_size(link
->ndisc_dnssl
) >= NDISC_DNSSL_MAX
) {
548 log_link_warning(link
, "Too many DNSSL records per link, ignoring.");
552 r
= set_ensure_allocated(&link
->ndisc_dnssl
, &ndisc_dnssl_hash_ops
);
558 s
->valid_until
= time_now
+ lifetime
* USEC_PER_SEC
;
560 r
= set_put(link
->ndisc_dnssl
, s
);
572 static void ndisc_router_process_options(Link
*link
, sd_ndisc_router
*rt
) {
578 r
= sd_ndisc_router_option_rewind(rt
);
583 log_link_warning_errno(link
, r
, "Failed to iterate through options: %m");
586 if (r
== 0) /* EOF */
589 r
= sd_ndisc_router_option_get_type(rt
, &type
);
591 log_link_warning_errno(link
, r
, "Failed to get RA option type: %m");
597 case SD_NDISC_OPTION_PREFIX_INFORMATION
: {
600 r
= sd_ndisc_router_prefix_get_flags(rt
, &flags
);
602 log_link_warning_errno(link
, r
, "Failed to get RA prefix flags: %m");
606 if (flags
& ND_OPT_PI_FLAG_ONLINK
)
607 ndisc_router_process_onlink_prefix(link
, rt
);
608 if (flags
& ND_OPT_PI_FLAG_AUTO
)
609 ndisc_router_process_autonomous_prefix(link
, rt
);
614 case SD_NDISC_OPTION_ROUTE_INFORMATION
:
615 ndisc_router_process_route(link
, rt
);
618 case SD_NDISC_OPTION_RDNSS
:
619 if (link
->network
->ipv6_accept_ra_use_dns
)
620 ndisc_router_process_rdnss(link
, rt
);
623 case SD_NDISC_OPTION_DNSSL
:
624 if (link
->network
->ipv6_accept_ra_use_dns
)
625 ndisc_router_process_dnssl(link
, rt
);
629 r
= sd_ndisc_router_option_next(rt
);
633 static void ndisc_router_handler(Link
*link
, sd_ndisc_router
*rt
) {
638 assert(link
->network
);
639 assert(link
->manager
);
642 r
= sd_ndisc_router_get_flags(rt
, &flags
);
644 log_link_warning_errno(link
, r
, "Failed to get RA flags: %m");
648 if (flags
& (ND_RA_FLAG_MANAGED
| ND_RA_FLAG_OTHER
)) {
649 /* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
650 r
= dhcp6_request_address(link
, !(flags
& ND_RA_FLAG_MANAGED
));
651 if (r
< 0 && r
!= -EBUSY
)
652 log_link_warning_errno(link
, r
, "Could not acquire DHCPv6 lease on NDisc request: %m");
654 log_link_debug(link
, "Acquiring DHCPv6 lease on NDisc request");
657 ndisc_router_process_default(link
, rt
);
658 ndisc_router_process_options(link
, rt
);
661 static void ndisc_handler(sd_ndisc
*nd
, sd_ndisc_event event
, sd_ndisc_router
*rt
, void *userdata
) {
662 Link
*link
= userdata
;
666 if (IN_SET(link
->state
, LINK_STATE_FAILED
, LINK_STATE_LINGER
))
671 case SD_NDISC_EVENT_ROUTER
:
672 ndisc_router_handler(link
, rt
);
675 case SD_NDISC_EVENT_TIMEOUT
:
676 link
->ndisc_configured
= true;
677 link_check_ready(link
);
681 log_link_warning(link
, "IPv6 Neighbor Discovery unknown event: %d", event
);
685 int ndisc_configure(Link
*link
) {
690 r
= sd_ndisc_new(&link
->ndisc
);
694 r
= sd_ndisc_attach_event(link
->ndisc
, NULL
, 0);
698 r
= sd_ndisc_set_mac(link
->ndisc
, &link
->mac
);
702 r
= sd_ndisc_set_ifindex(link
->ndisc
, link
->ifindex
);
706 r
= sd_ndisc_set_callback(link
->ndisc
, ndisc_handler
, link
);
713 void ndisc_vacuum(Link
*link
) {
721 /* Removes all RDNSS and DNSSL entries whose validity time has passed */
723 time_now
= now(clock_boottime_or_monotonic());
725 SET_FOREACH(r
, link
->ndisc_rdnss
, i
)
726 if (r
->valid_until
< time_now
) {
727 free(set_remove(link
->ndisc_rdnss
, r
));
731 SET_FOREACH(d
, link
->ndisc_dnssl
, i
)
732 if (d
->valid_until
< time_now
) {
733 free(set_remove(link
->ndisc_dnssl
, d
));
738 void ndisc_flush(Link
*link
) {
741 /* Removes all RDNSS and DNSSL entries, without exception */
743 link
->ndisc_rdnss
= set_free_free(link
->ndisc_rdnss
);
744 link
->ndisc_dnssl
= set_free_free(link
->ndisc_dnssl
);