-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
/***
Copyright © 2014 Intel Corporation. All rights reserved.
***/
#include <arpa/inet.h>
#include <netinet/icmp6.h>
+#include <net/if_arp.h>
#include <linux/if.h>
#include "sd-ndisc.h"
#include "networkd-dhcp6.h"
#include "networkd-manager.h"
#include "networkd-ndisc.h"
-#include "networkd-sysctl.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
if (!link_ipv6ll_enabled(link))
return false;
- /* If unset use system default (enabled if local forwarding is disabled.
- * disabled if local forwarding is enabled).
- * If set, ignore or enforce RA independent of local forwarding state.
- */
- if (link->network->ipv6_accept_ra < 0)
+ assert(link->network->ipv6_accept_ra >= 0);
+ return link->network->ipv6_accept_ra;
+}
+
+void network_adjust_ipv6_accept_ra(Network *network) {
+ assert(network);
+
+ if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) {
+ if (network->ipv6_accept_ra > 0)
+ log_warning("%s: IPv6AcceptRA= is enabled but IPv6 link local addressing is disabled or not supported. "
+ "Disabling IPv6AcceptRA=.", network->filename);
+ network->ipv6_accept_ra = false;
+ }
+
+ if (network->ipv6_accept_ra < 0)
/* default to accept RA if ip_forward is disabled and ignore RA if ip_forward is enabled */
- return !link_ip_forward_enabled(link, AF_INET6);
- else if (link->network->ipv6_accept_ra > 0)
- /* accept RA even if ip_forward is enabled */
- return true;
- else
- /* ignore RA */
- return false;
+ network->ipv6_accept_ra = !FLAGS_SET(network->ip_forward, ADDRESS_FAMILY_IPV6);
+
+ /* When RouterAllowList=, PrefixAllowList= or RouteAllowList= are specified, then
+ * RouterDenyList=, PrefixDenyList= or RouteDenyList= are ignored, respectively. */
+ if (!set_isempty(network->ndisc_allow_listed_router))
+ network->ndisc_deny_listed_router = set_free_free(network->ndisc_deny_listed_router);
+ if (!set_isempty(network->ndisc_allow_listed_prefix))
+ network->ndisc_deny_listed_prefix = set_free_free(network->ndisc_deny_listed_prefix);
+ if (!set_isempty(network->ndisc_allow_listed_route_prefix))
+ network->ndisc_deny_listed_route_prefix = set_free_free(network->ndisc_deny_listed_route_prefix);
}
static int ndisc_remove_old_one(Link *link, const struct in6_addr *router, bool force);
break;
}
- if (IN6_IS_ADDR_UNSPECIFIED(&router)) {
+ if (in6_addr_is_null(&router)) {
_cleanup_free_ char *buf = NULL;
(void) in_addr_to_string(address->family, &address->in_addr, &buf);
NDiscDNSSL *dnssl;
NDiscRDNSS *rdnss;
int k, r = 0;
+ bool updated = false;
assert(link);
assert(router);
log_link_debug(link, "Removing old NDisc information obtained from %s.", strna(buf));
}
- link_dirty(link);
-
SET_FOREACH(na, link->ndisc_addresses)
if (na->marked && IN6_ARE_ADDR_EQUAL(&na->router, router)) {
k = address_remove(na->address, link, NULL);
SET_FOREACH(nr, link->ndisc_routes)
if (nr->marked && IN6_ARE_ADDR_EQUAL(&nr->router, router)) {
- k = route_remove(nr->route, link, NULL);
+ k = route_remove(nr->route, NULL, link, NULL);
if (k < 0)
r = k;
}
SET_FOREACH(rdnss, link->ndisc_rdnss)
- if (rdnss->marked && IN6_ARE_ADDR_EQUAL(&rdnss->router, router))
+ if (rdnss->marked && IN6_ARE_ADDR_EQUAL(&rdnss->router, router)) {
free(set_remove(link->ndisc_rdnss, rdnss));
+ updated = true;
+ }
SET_FOREACH(dnssl, link->ndisc_dnssl)
- if (dnssl->marked && IN6_ARE_ADDR_EQUAL(&dnssl->router, router))
+ if (dnssl->marked && IN6_ARE_ADDR_EQUAL(&dnssl->router, router)) {
free(set_remove(link->ndisc_dnssl, dnssl));
+ updated = true;
+ }
+
+ if (updated)
+ link_dirty(link);
return r;
}
r = route_configure(route, link, ndisc_route_handler, &ret);
if (r < 0)
return log_link_error_errno(link, r, "Failed to set NDisc route: %m");
+ if (r > 0)
+ link->ndisc_routes_configured = false;
link->ndisc_routes_messages++;
link_enter_failed(link);
return 1;
}
-
- r = link_set_routes(link);
- if (r < 0) {
- link_enter_failed(link);
- return 1;
- }
}
return 1;
assert(link);
assert(rt);
- r = address_configure(address, link, ndisc_address_handler, true, &ret);
+ r = address_configure(address, link, ndisc_address_handler, &ret);
if (r < 0)
return log_link_error_errno(link, r, "Failed to set NDisc SLAAC address: %m");
+ if (r > 0)
+ link->ndisc_addresses_configured = false;
link->ndisc_addresses_messages++;
union in_addr_union gateway;
uint16_t lifetime;
unsigned preference;
- uint32_t mtu;
+ uint32_t table, mtu;
usec_t time_now;
int r;
if (r < 0)
return log_link_error_errno(link, r, "Failed to get gateway address from RA: %m");
- if (address_exists(link, AF_INET6, &gateway)) {
+ if (link_has_ipv6_address(link, &gateway.in6) > 0) {
if (DEBUG_LOGGING) {
_cleanup_free_ char *buffer = NULL;
else if (r < 0)
return log_link_error_errno(link, r, "Failed to get default router MTU from RA: %m");
+ table = link_get_ipv6_accept_ra_route_table(link);
+
r = route_new(&route);
if (r < 0)
return log_oom();
route->family = AF_INET6;
- route->table = link_get_ipv6_accept_ra_route_table(link);
+ route->table = table;
route->priority = link->network->dhcp6_route_metric;
route->protocol = RTPROT_RA;
route->pref = preference;
+ route->gw_family = AF_INET6;
route->gw = gateway;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
route->mtu = mtu;
Route *route_gw;
HASHMAP_FOREACH(route_gw, link->network->routes_by_section) {
- if (!route_gw->gateway_from_dhcp)
+ if (!route_gw->gateway_from_dhcp_or_ra)
continue;
- if (route_gw->family != AF_INET6)
+ if (route_gw->gw_family != AF_INET6)
continue;
route_gw->gw = gateway;
+ if (!route_gw->table_set)
+ route_gw->table = table;
+ if (!route_gw->priority_set)
+ route_gw->priority = link->network->dhcp6_route_metric;
+ if (!route_gw->protocol_set)
+ route_gw->protocol = RTPROT_RA;
+ if (!route_gw->pref_set)
+ route->pref = preference;
+ route_gw->lifetime = time_now + lifetime * USEC_PER_SEC;
+ if (route_gw->mtu == 0)
+ route_gw->mtu = mtu;
r = ndisc_route_configure(route_gw, link, rt);
if (r < 0)
l = MAX(DIV_ROUND_UP(prefix_len, 8), 8);
siphash24_compress(prefix, l, &state);
siphash24_compress_string(link->ifname, &state);
- siphash24_compress(&link->mac, sizeof(struct ether_addr), &state);
+ /* Only last 8 bytes of IB MAC are stable */
+ if (link->iftype == ARPHRD_INFINIBAND)
+ siphash24_compress(&link->hw_addr.addr.infiniband[12], 8, &state);
+ else
+ siphash24_compress(link->hw_addr.addr.bytes, link->hw_addr.length, &state);
siphash24_compress(&dad_counter, sizeof(uint8_t), &state);
rid = htole64(siphash24_finalize(&state));
_cleanup_free_ struct in6_addr *new_address = NULL;
if (j->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_PREFIXSTABLE
- && (IN6_IS_ADDR_UNSPECIFIED(&j->prefix) || IN6_ARE_ADDR_EQUAL(&j->prefix, address))) {
+ && (in6_addr_is_null(&j->prefix) || IN6_ARE_ADDR_EQUAL(&j->prefix, address))) {
/* While this loop uses dad_counter and a retry limit as specified in RFC 7217, the loop
- does not actually attempt Duplicate Address Detection; the counter will be incremented
- only when the address generation algorithm produces an invalid address, and the loop
- may exit with an address which ends up being unusable due to duplication on the link.
- */
+ * does not actually attempt Duplicate Address Detection; the counter will be incremented
+ * only when the address generation algorithm produces an invalid address, and the loop
+ * may exit with an address which ends up being unusable due to duplication on the link. */
for (; j->dad_counter < DAD_CONFLICTS_IDGEN_RETRIES_RFC7217; j->dad_counter++) {
r = make_stableprivate_address(link, address, prefixlen, j->dad_counter, &new_address);
if (r < 0)
SET_FOREACH(a, addresses) {
Address *existing_address;
+ address->in_addr.in6 = *a;
+
/* see RFC4862 section 5.5.3.e */
- r = address_get(link, AF_INET6, (union in_addr_union *) a, prefixlen, &existing_address);
+ r = address_get(link, address, &existing_address);
if (r > 0) {
lifetime_remaining = existing_address->cinfo.tstamp / 100 + existing_address->cinfo.ifa_valid - time_now / USEC_PER_SEC;
if (lifetime_valid > NDISC_PREFIX_LFT_MIN || lifetime_valid > lifetime_remaining)
if (address->cinfo.ifa_valid == 0)
continue;
- address->in_addr.in6 = *a;
-
r = ndisc_address_configure(address, link, rt);
if (r < 0)
return log_link_error_errno(link, r, "Could not set SLAAC address: %m");
static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
_cleanup_(route_freep) Route *route = NULL;
- struct in6_addr gateway;
+ union in_addr_union gateway, dst;
uint32_t lifetime;
unsigned preference, prefixlen;
usec_t time_now;
if (lifetime == 0)
return 0;
- r = sd_ndisc_router_get_address(rt, &gateway);
+ r = sd_ndisc_router_route_get_address(rt, &dst.in6);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to get route address: %m");
+
+ if ((!set_isempty(link->network->ndisc_allow_listed_route_prefix) &&
+ !set_contains(link->network->ndisc_allow_listed_route_prefix, &dst.in6)) ||
+ set_contains(link->network->ndisc_deny_listed_route_prefix, &dst.in6)) {
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *buf = NULL;
+
+ (void) in_addr_to_string(AF_INET6, &dst, &buf);
+ if (!set_isempty(link->network->ndisc_allow_listed_route_prefix))
+ log_link_debug(link, "Route prefix '%s' is not in allow list, ignoring", strnull(buf));
+ else
+ log_link_debug(link, "Route prefix '%s' is in deny list, ignoring", strnull(buf));
+ }
+ return 0;
+ }
+
+ r = sd_ndisc_router_get_address(rt, &gateway.in6);
if (r < 0)
return log_link_error_errno(link, r, "Failed to get gateway address from RA: %m");
+ if (link_has_ipv6_address(link, &gateway.in6) > 0) {
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *buf = NULL;
+
+ (void) in_addr_to_string(AF_INET6, &gateway, &buf);
+ log_link_debug(link, "Advertised route gateway, %s, is local to the link, ignoring route", strnull(buf));
+ }
+ return 0;
+ }
+
r = sd_ndisc_router_route_get_prefixlen(rt, &prefixlen);
if (r < 0)
return log_link_error_errno(link, r, "Failed to get route prefix length: %m");
route->priority = link->network->dhcp6_route_metric;
route->protocol = RTPROT_RA;
route->pref = preference;
- route->gw.in6 = gateway;
+ route->gw = gateway;
+ route->gw_family = AF_INET6;
+ route->dst = dst;
route->dst_prefixlen = prefixlen;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
- r = sd_ndisc_router_route_get_address(rt, &route->dst.in6);
- if (r < 0)
- return log_link_error_errno(link, r, "Failed to get route address: %m");
-
r = ndisc_route_configure(route, link, rt);
if (r < 0)
return log_link_error_errno(link, r, "Could not set additional route: %m");
struct in6_addr router;
NDiscRDNSS *rdnss;
usec_t time_now;
+ bool updated = false;
int n, r;
assert(link);
if (r < 0)
return log_oom();
assert(r > 0);
+
+ updated = true;
}
+ if (updated)
+ link_dirty(link);
+
return 0;
}
uint32_t lifetime;
usec_t time_now;
NDiscDNSSL *dnssl;
+ bool updated = false;
char **j;
int r;
if (r < 0)
return log_oom();
assert(r > 0);
+
+ updated = true;
}
+ if (updated)
+ link_dirty(link);
+
return 0;
}
if (r < 0)
return log_link_error_errno(link, r, "Failed to get prefix address: %m");
- if (set_contains(link->network->ndisc_deny_listed_prefix, &a.in6)) {
+ if ((!set_isempty(link->network->ndisc_allow_listed_prefix) &&
+ !set_contains(link->network->ndisc_allow_listed_prefix, &a.in6)) ||
+ set_contains(link->network->ndisc_deny_listed_prefix, &a.in6)) {
if (DEBUG_LOGGING) {
_cleanup_free_ char *b = NULL;
(void) in_addr_to_string(AF_INET6, &a, &b);
- log_link_debug(link, "Prefix '%s' is deny-listed, ignoring", strna(b));
+ if (!set_isempty(link->network->ndisc_allow_listed_prefix))
+ log_link_debug(link, "Prefix '%s' is not in allow list, ignoring", strna(b));
+ else
+ log_link_debug(link, "Prefix '%s' is in deny list, ignoring", strna(b));
}
break;
}
}
static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
- struct in6_addr router;
+ union in_addr_union router;
uint64_t flags;
NDiscAddress *na;
NDiscRoute *nr;
assert(link->manager);
assert(rt);
- link->ndisc_addresses_configured = false;
- link->ndisc_routes_configured = false;
-
- link_dirty(link);
-
- r = sd_ndisc_router_get_address(rt, &router);
+ r = sd_ndisc_router_get_address(rt, &router.in6);
if (r < 0)
return log_link_error_errno(link, r, "Failed to get router address from RA: %m");
+ if ((!set_isempty(link->network->ndisc_allow_listed_router) &&
+ !set_contains(link->network->ndisc_allow_listed_router, &router.in6)) ||
+ set_contains(link->network->ndisc_deny_listed_router, &router.in6)) {
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *buf = NULL;
+
+ (void) in_addr_to_string(AF_INET6, &router, &buf);
+ if (!set_isempty(link->network->ndisc_allow_listed_router))
+ log_link_debug(link, "Router '%s' is not in allow list, ignoring", strna(buf));
+ else
+ log_link_debug(link, "Router '%s' is in deny list, ignoring", strna(buf));
+ }
+ return 0;
+ }
+
SET_FOREACH(na, link->ndisc_addresses)
- if (IN6_ARE_ADDR_EQUAL(&na->router, &router))
+ if (IN6_ARE_ADDR_EQUAL(&na->router, &router.in6))
na->marked = true;
SET_FOREACH(nr, link->ndisc_routes)
- if (IN6_ARE_ADDR_EQUAL(&nr->router, &router))
+ if (IN6_ARE_ADDR_EQUAL(&nr->router, &router.in6))
nr->marked = true;
r = sd_ndisc_router_get_flags(rt, &flags);
if (r < 0)
return log_link_error_errno(link, r, "Failed to get RA flags: %m");
- if ((flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER) && link->network->ipv6_accept_ra_start_dhcp6_client)) {
+ if ((flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER) &&
+ link->network->ipv6_accept_ra_start_dhcp6_client != IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO) ||
+ link->network->ipv6_accept_ra_start_dhcp6_client == IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS) {
- if (link->network->ipv6_accept_ra_start_dhcp6_client == IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS)
- r = dhcp6_request_address(link, false);
- else
+ if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))
/* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
r = dhcp6_request_address(link, !(flags & ND_RA_FLAG_MANAGED));
+ else
+ /* When IPv6AcceptRA.DHCPv6Client=always, start dhcp6 client in managed mode
+ * even if router does not have M or O flag. */
+ r = dhcp6_request_address(link, false);
if (r < 0 && r != -EBUSY)
return log_link_error_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m");
else
if (link->ndisc_addresses_messages == 0)
link->ndisc_addresses_configured = true;
- else {
+ else
log_link_debug(link, "Setting SLAAC addresses.");
- /* address_handler calls link_set_routes() and link_set_nexthop(). Before they are
- * called, the related flags must be cleared. Otherwise, the link becomes configured
- * state before routes are configured. */
- link->static_routes_configured = false;
- link->static_nexthops_configured = false;
- }
-
if (link->ndisc_routes_messages == 0)
link->ndisc_routes_configured = true;
else
assert(link);
- r = sd_ndisc_new(&link->ndisc);
- if (r < 0)
- return r;
+ if (!link_ipv6_accept_ra_enabled(link))
+ return 0;
- r = sd_ndisc_attach_event(link->ndisc, NULL, 0);
- if (r < 0)
- return r;
+ if (!link->ndisc) {
+ r = sd_ndisc_new(&link->ndisc);
+ if (r < 0)
+ return r;
- r = sd_ndisc_set_mac(link->ndisc, &link->mac);
+ r = sd_ndisc_attach_event(link->ndisc, link->manager->event, 0);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_ndisc_set_mac(link->ndisc, &link->hw_addr.addr.ether);
if (r < 0)
return r;
NDiscRDNSS *r;
NDiscDNSSL *d;
usec_t time_now;
- bool updated = false;
assert(link);
time_now = now(clock_boottime_or_monotonic());
SET_FOREACH(r, link->ndisc_rdnss)
- if (r->valid_until < time_now) {
+ if (r->valid_until < time_now)
free(set_remove(link->ndisc_rdnss, r));
- updated = true;
- }
SET_FOREACH(d, link->ndisc_dnssl)
- if (d->valid_until < time_now) {
+ if (d->valid_until < time_now)
free(set_remove(link->ndisc_dnssl, d));
- updated = true;
- }
-
- if (updated)
- link_dirty(link);
}
void ndisc_flush(Link *link) {
ipv6_token_compare_func,
free);
-int config_parse_ndisc_deny_listed_prefix(
+int config_parse_ndisc_address_filter(
const char *unit,
const char *filename,
unsigned line,
void *data,
void *userdata) {
- Network *network = data;
- const char *p;
+ Set **list = data;
int r;
assert(filename);
assert(data);
if (isempty(rvalue)) {
- network->ndisc_deny_listed_prefix = set_free_free(network->ndisc_deny_listed_prefix);
+ *list = set_free_free(*list);
return 0;
}
- for (p = rvalue;;) {
+ for (const char *p = rvalue;;) {
_cleanup_free_ char *n = NULL;
_cleanup_free_ struct in6_addr *a = NULL;
union in_addr_union ip;
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to parse NDisc deny-listed prefix, ignoring assignment: %s",
- rvalue);
+ "Failed to parse NDisc %s=, ignoring assignment: %s",
+ lvalue, rvalue);
return 0;
}
if (r == 0)
r = in_addr_from_string(AF_INET6, n, &ip);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
- "NDisc deny-listed prefix is invalid, ignoring assignment: %s", n);
+ "NDisc %s= entry is invalid, ignoring assignment: %s",
+ lvalue, n);
continue;
}
- if (set_contains(network->ndisc_deny_listed_prefix, &ip.in6))
- continue;
-
a = newdup(struct in6_addr, &ip.in6, 1);
if (!a)
return log_oom();
- r = set_ensure_consume(&network->ndisc_deny_listed_prefix, &in6_addr_hash_ops, TAKE_PTR(a));
+ r = set_ensure_consume(list, &in6_addr_hash_ops, TAKE_PTR(a));
if (r < 0)
return log_oom();
+ if (r == 0)
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "NDisc %s= entry is duplicated, ignoring assignment: %s",
+ lvalue, n);
}
}
token->prefix = buffer.in6;
}
- r = ordered_set_ensure_allocated(&network->ipv6_tokens, &ipv6_token_hash_ops);
- if (r < 0)
+ r = ordered_set_ensure_put(&network->ipv6_tokens, &ipv6_token_hash_ops, token);
+ if (r == -ENOMEM)
return log_oom();
-
- r = ordered_set_put(network->ipv6_tokens, token);
if (r == -EEXIST)
log_syntax(unit, LOG_DEBUG, filename, line, r,
"IPv6 token '%s' is duplicated, ignoring: %m", rvalue);