summaryrefslogtreecommitdiffstats
path: root/src/network/networkd-ndisc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/networkd-ndisc.c')
-rw-r--r--src/network/networkd-ndisc.c1347
1 files changed, 1156 insertions, 191 deletions
diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c
index 840ccb1..7cafe1f 100644
--- a/src/network/networkd-ndisc.c
+++ b/src/network/networkd-ndisc.c
@@ -12,6 +12,7 @@
#include "event-util.h"
#include "missing_network.h"
+#include "ndisc-router-internal.h"
#include "networkd-address-generation.h"
#include "networkd-address.h"
#include "networkd-dhcp6.h"
@@ -20,6 +21,7 @@
#include "networkd-queue.h"
#include "networkd-route.h"
#include "networkd-state-file.h"
+#include "networkd-sysctl.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
@@ -34,7 +36,9 @@
* Not sure if the threshold is high enough. Let's adjust later if not. */
#define NDISC_PREF64_MAX 64U
-bool link_ipv6_accept_ra_enabled(Link *link) {
+static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t timestamp_usec);
+
+bool link_ndisc_enabled(Link *link) {
assert(link);
if (!socket_ipv6_is_supported())
@@ -52,24 +56,33 @@ bool link_ipv6_accept_ra_enabled(Link *link) {
if (!link_may_have_ipv6ll(link, /* check_multicast = */ true))
return false;
- assert(link->network->ipv6_accept_ra >= 0);
- return link->network->ipv6_accept_ra;
+ /* Honor explicitly specified value. */
+ if (link->network->ndisc >= 0)
+ return link->network->ndisc;
+
+ /* Disable if RADV is enabled. */
+ if (link_radv_enabled(link))
+ return false;
+
+ /* Accept RAs if IPv6 forwarding is disabled, and ignore RAs if IPv6 forwarding is enabled. */
+ int t = link_get_ip_forwarding(link, AF_INET6);
+ if (t >= 0)
+ return !t;
+
+ /* Otherwise, defaults to true. */
+ return true;
}
-void network_adjust_ipv6_accept_ra(Network *network) {
+void network_adjust_ndisc(Network *network) {
assert(network);
if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) {
- if (network->ipv6_accept_ra > 0)
+ if (network->ndisc > 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;
+ network->ndisc = false;
}
- if (network->ipv6_accept_ra < 0)
- /* default to accept RA if ip_forward is disabled and ignore RA if ip_forward is enabled */
- 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))
@@ -139,7 +152,7 @@ static int ndisc_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request
assert(link);
- r = route_configure_handler_internal(rtnl, m, link, "Could not set NDisc route");
+ r = route_configure_handler_internal(rtnl, m, link, route, "Could not set NDisc route");
if (r <= 0)
return r;
@@ -159,66 +172,84 @@ static void ndisc_set_route_priority(Link *link, Route *route) {
switch (route->pref) {
case SD_NDISC_PREFERENCE_LOW:
- route->priority = link->network->ipv6_accept_ra_route_metric_low;
+ route->priority = link->network->ndisc_route_metric_low;
break;
case SD_NDISC_PREFERENCE_MEDIUM:
- route->priority = link->network->ipv6_accept_ra_route_metric_medium;
+ route->priority = link->network->ndisc_route_metric_medium;
break;
case SD_NDISC_PREFERENCE_HIGH:
- route->priority = link->network->ipv6_accept_ra_route_metric_high;
+ route->priority = link->network->ndisc_route_metric_high;
break;
default:
assert_not_reached();
}
}
-static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) {
- _cleanup_(route_freep) Route *route = in;
- struct in6_addr router;
- uint8_t hop_limit = 0;
- uint32_t mtu = 0;
- bool is_new;
+static int ndisc_request_route(Route *route, Link *link) {
int r;
assert(route);
assert(link);
+ assert(link->manager);
assert(link->network);
- assert(rt);
- r = sd_ndisc_router_get_address(rt, &router);
+ route->source = NETWORK_CONFIG_SOURCE_NDISC;
+
+ if (!route->table_set)
+ route->table = link_get_ndisc_route_table(link);
+
+ r = route_metric_set(&route->metric, RTAX_QUICKACK, link->network->ndisc_quickack);
if (r < 0)
return r;
- if (link->network->ipv6_accept_ra_use_mtu) {
- r = sd_ndisc_router_get_mtu(rt, &mtu);
- if (r < 0 && r != -ENODATA)
- return log_link_warning_errno(link, r, "Failed to get default router MTU from RA: %m");
- }
+ r = route_adjust_nexthops(route, link);
+ if (r < 0)
+ return r;
+
+ uint8_t pref, pref_original = route->pref;
+ FOREACH_ARGUMENT(pref, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH) {
+ Route *existing;
+ Request *req;
+
+ /* If the preference is specified by the user config (that is, for semi-static routes),
+ * rather than RA, then only search conflicting routes that have the same preference. */
+ if (route->pref_set && pref != pref_original)
+ continue;
- if (link->network->ipv6_accept_ra_use_hop_limit) {
- r = sd_ndisc_router_get_hop_limit(rt, &hop_limit);
- if (r < 0 && r != -ENODATA)
- return log_link_warning_errno(link, r, "Failed to get default router hop limit from RA: %m");
+ route->pref = pref;
+ ndisc_set_route_priority(link, route);
+
+ /* Note, here do not call route_remove_and_cancel() with 'route' directly, otherwise
+ * existing route(s) may be removed needlessly. */
+
+ if (route_get(link->manager, route, &existing) >= 0) {
+ /* Found an existing route that may conflict with this route. */
+ if (!route_can_update(existing, route)) {
+ log_link_debug(link, "Found an existing route that conflicts with new route based on a received RA, removing.");
+ r = route_remove_and_cancel(existing, link->manager);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ if (route_get_request(link->manager, route, &req) >= 0) {
+ existing = ASSERT_PTR(req->userdata);
+ if (!route_can_update(existing, route)) {
+ log_link_debug(link, "Found a pending route request that conflicts with new request based on a received RA, cancelling.");
+ r = route_remove_and_cancel(existing, link->manager);
+ if (r < 0)
+ return r;
+ }
+ }
}
- route->source = NETWORK_CONFIG_SOURCE_NDISC;
- route->provider.in6 = router;
- if (!route->table_set)
- route->table = link_get_ipv6_accept_ra_route_table(link);
+ /* The preference (and priority) may be changed in the above loop. Restore it. */
+ route->pref = pref_original;
ndisc_set_route_priority(link, route);
- if (!route->protocol_set)
- route->protocol = RTPROT_RA;
- if (route->quickack < 0)
- route->quickack = link->network->ipv6_accept_ra_quickack;
- if (route->mtu == 0)
- route->mtu = mtu;
- if (route->hop_limit == 0)
- route->hop_limit = hop_limit;
- is_new = route_get(NULL, link, route, NULL) < 0;
+ bool is_new = route_get(link->manager, route, NULL) < 0;
- r = link_request_route(link, TAKE_PTR(route), true, &link->ndisc_messages,
- ndisc_route_handler, NULL);
+ r = link_request_route(link, route, &link->ndisc_messages, ndisc_route_handler);
if (r < 0)
return r;
if (r > 0 && is_new)
@@ -227,6 +258,56 @@ static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) {
return 0;
}
+static int ndisc_request_router_route(Route *route, Link *link, sd_ndisc_router *rt) {
+ int r;
+
+ assert(route);
+ assert(link);
+ assert(rt);
+
+ r = sd_ndisc_router_get_sender_address(rt, &route->provider.in6);
+ if (r < 0)
+ return r;
+
+ if (!route->protocol_set)
+ route->protocol = RTPROT_RA;
+
+ return ndisc_request_route(route, link);
+}
+
+static int ndisc_remove_route(Route *route, Link *link) {
+ int r;
+
+ assert(route);
+ assert(link);
+ assert(link->manager);
+
+ ndisc_set_route_priority(link, route);
+
+ if (!route->table_set)
+ route->table = link_get_ndisc_route_table(link);
+
+ r = route_adjust_nexthops(route, link);
+ if (r < 0)
+ return r;
+
+ if (route->pref_set) {
+ ndisc_set_route_priority(link, route);
+ return route_remove_and_cancel(route, link->manager);
+ }
+
+ uint8_t pref;
+ FOREACH_ARGUMENT(pref, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH) {
+ route->pref = pref;
+ ndisc_set_route_priority(link, route);
+ r = route_remove_and_cancel(route, link->manager);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
static int ndisc_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Address *address) {
int r;
@@ -243,28 +324,39 @@ static int ndisc_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Reques
return 1;
}
-static int ndisc_request_address(Address *in, Link *link, sd_ndisc_router *rt) {
- _cleanup_(address_freep) Address *address = in;
- struct in6_addr router;
+static int ndisc_request_address(Address *address, Link *link) {
bool is_new;
int r;
assert(address);
assert(link);
- assert(rt);
-
- r = sd_ndisc_router_get_address(rt, &router);
- if (r < 0)
- return r;
address->source = NETWORK_CONFIG_SOURCE_NDISC;
- address->provider.in6 = router;
r = free_and_strdup_warn(&address->netlabel, link->network->ndisc_netlabel);
if (r < 0)
return r;
- is_new = address_get(link, address, NULL) < 0;
+ Address *existing;
+ if (address_get_harder(link, address, &existing) < 0)
+ is_new = true;
+ else if (address_can_update(existing, address))
+ is_new = false;
+ else if (existing->source == NETWORK_CONFIG_SOURCE_DHCP6) {
+ /* SLAAC address is preferred over DHCPv6 address. */
+ log_link_debug(link, "Conflicting DHCPv6 address %s exists, removing.",
+ IN_ADDR_PREFIX_TO_STRING(existing->family, &existing->in_addr, existing->prefixlen));
+ r = address_remove(existing, link);
+ if (r < 0)
+ return r;
+
+ is_new = true;
+ } else {
+ /* Conflicting static address is configured?? */
+ log_link_debug(link, "Conflicting address %s exists, ignoring request.",
+ IN_ADDR_PREFIX_TO_STRING(existing->family, &existing->in_addr, existing->prefixlen));
+ return 0;
+ }
r = link_request_address(link, address, &link->ndisc_messages,
ndisc_address_handler, NULL);
@@ -276,17 +368,397 @@ static int ndisc_request_address(Address *in, Link *link, sd_ndisc_router *rt) {
return 0;
}
+int ndisc_reconfigure_address(Address *address, Link *link) {
+ int r;
+
+ assert(address);
+ assert(address->source == NETWORK_CONFIG_SOURCE_NDISC);
+ assert(link);
+
+ r = regenerate_address(address, link);
+ if (r <= 0)
+ return r;
+
+ r = ndisc_request_address(address, link);
+ if (r < 0)
+ return r;
+
+ if (!link->ndisc_configured)
+ link_set_state(link, LINK_STATE_CONFIGURING);
+
+ link_check_ready(link);
+ return 0;
+}
+
+static int ndisc_redirect_route_new(sd_ndisc_redirect *rd, Route **ret) {
+ _cleanup_(route_unrefp) Route *route = NULL;
+ struct in6_addr gateway, destination;
+ int r;
+
+ assert(rd);
+ assert(ret);
+
+ r = sd_ndisc_redirect_get_target_address(rd, &gateway);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_redirect_get_destination_address(rd, &destination);
+ if (r < 0)
+ return r;
+
+ r = route_new(&route);
+ if (r < 0)
+ return r;
+
+ route->family = AF_INET6;
+ if (!in6_addr_equal(&gateway, &destination)) {
+ route->nexthop.gw.in6 = gateway;
+ route->nexthop.family = AF_INET6;
+ }
+ route->dst.in6 = destination;
+ route->dst_prefixlen = 128;
+ route->protocol = RTPROT_REDIRECT;
+
+ r = sd_ndisc_redirect_get_sender_address(rd, &route->provider.in6);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(route);
+ return 0;
+}
+
+static int ndisc_remove_redirect_route(Link *link, sd_ndisc_redirect *rd) {
+ _cleanup_(route_unrefp) Route *route = NULL;
+ int r;
+
+ assert(link);
+ assert(rd);
+
+ r = ndisc_redirect_route_new(rd, &route);
+ if (r < 0)
+ return r;
+
+ return ndisc_remove_route(route, link);
+}
+
+static void ndisc_redirect_hash_func(const sd_ndisc_redirect *x, struct siphash *state) {
+ struct in6_addr dest = {};
+
+ assert(x);
+ assert(state);
+
+ (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) x, &dest);
+
+ siphash24_compress_typesafe(dest, state);
+}
+
+static int ndisc_redirect_compare_func(const sd_ndisc_redirect *x, const sd_ndisc_redirect *y) {
+ struct in6_addr dest_x = {}, dest_y = {};
+
+ assert(x);
+ assert(y);
+
+ (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) x, &dest_x);
+ (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) y, &dest_y);
+
+ return memcmp(&dest_x, &dest_y, sizeof(dest_x));
+}
+
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+ ndisc_redirect_hash_ops,
+ sd_ndisc_redirect,
+ ndisc_redirect_hash_func,
+ ndisc_redirect_compare_func,
+ sd_ndisc_redirect_unref);
+
+static int ndisc_redirect_equal(sd_ndisc_redirect *x, sd_ndisc_redirect *y) {
+ struct in6_addr a, b;
+ int r;
+
+ assert(x);
+ assert(y);
+
+ r = sd_ndisc_redirect_get_destination_address(x, &a);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_redirect_get_destination_address(y, &b);
+ if (r < 0)
+ return r;
+
+ if (!in6_addr_equal(&a, &b))
+ return false;
+
+ r = sd_ndisc_redirect_get_target_address(x, &a);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_redirect_get_target_address(y, &b);
+ if (r < 0)
+ return r;
+
+ return in6_addr_equal(&a, &b);
+}
+
+static int ndisc_redirect_drop_conflict(Link *link, sd_ndisc_redirect *rd) {
+ _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *existing = NULL;
+ int r;
+
+ assert(link);
+ assert(rd);
+
+ existing = set_remove(link->ndisc_redirects, rd);
+ if (!existing)
+ return 0;
+
+ r = ndisc_redirect_equal(rd, existing);
+ if (r != 0)
+ return r;
+
+ return ndisc_remove_redirect_route(link, existing);
+}
+
+static int ndisc_redirect_verify_sender(Link *link, sd_ndisc_redirect *rd) {
+ int r;
+
+ assert(link);
+ assert(rd);
+
+ /* RFC 4861 section 8.1
+ * The IP source address of the Redirect is the same as the current first-hop router for the specified
+ * ICMP Destination Address. */
+
+ struct in6_addr sender;
+ r = sd_ndisc_redirect_get_sender_address(rd, &sender);
+ if (r < 0)
+ return r;
+
+ /* We will reuse the sender's router lifetime as the lifetime of the redirect route. Hence, if we
+ * have not remembered an RA from the sender, refuse the Redirect message. */
+ sd_ndisc_router *router = hashmap_get(link->ndisc_routers_by_sender, &sender);
+ if (!router)
+ return false;
+
+ sd_ndisc_redirect *existing = set_get(link->ndisc_redirects, rd);
+ if (existing) {
+ struct in6_addr target, dest;
+
+ /* If we have received Redirect message for the host, the sender must be the previous target. */
+
+ r = sd_ndisc_redirect_get_target_address(existing, &target);
+ if (r < 0)
+ return r;
+
+ if (in6_addr_equal(&sender, &target))
+ return true;
+
+ /* If the existing redirect route is on-link, that is, the destination and target address are
+ * equivalent, then also accept Redirect message from the current default router. This is not
+ * mentioned by the RFC, but without this, we cannot update on-link redirect route. */
+ r = sd_ndisc_redirect_get_destination_address(existing, &dest);
+ if (r < 0)
+ return r;
+
+ if (!in6_addr_equal(&dest, &target))
+ return false;
+ }
+
+ /* Check if the sender is one of the known router with highest priority. */
+ uint8_t preference;
+ r = sd_ndisc_router_get_preference(router, &preference);
+ if (r < 0)
+ return r;
+
+ if (preference == SD_NDISC_PREFERENCE_HIGH)
+ return true;
+
+ sd_ndisc_router *rt;
+ HASHMAP_FOREACH(rt, link->ndisc_routers_by_sender) {
+ if (rt == router)
+ continue;
+
+ uint8_t pref;
+ if (sd_ndisc_router_get_preference(rt, &pref) < 0)
+ continue;
+
+ if (pref == SD_NDISC_PREFERENCE_HIGH ||
+ (pref == SD_NDISC_PREFERENCE_MEDIUM && preference == SD_NDISC_PREFERENCE_LOW))
+ return false;
+ }
+
+ return true;
+}
+
+static int ndisc_redirect_handler(Link *link, sd_ndisc_redirect *rd) {
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(rd);
+
+ if (!link->network->ndisc_use_redirect)
+ return 0;
+
+ usec_t now_usec;
+ r = sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec);
+ if (r < 0)
+ return r;
+
+ r = ndisc_drop_outdated(link, /* router = */ NULL, now_usec);
+ if (r < 0)
+ return r;
+
+ r = ndisc_redirect_verify_sender(link, rd);
+ if (r <= 0)
+ return r;
+
+ /* First, drop conflicting redirect route, if exists. */
+ r = ndisc_redirect_drop_conflict(link, rd);
+ if (r < 0)
+ return r;
+
+ /* Then, remember the received message. */
+ r = set_ensure_put(&link->ndisc_redirects, &ndisc_redirect_hash_ops, rd);
+ if (r < 0)
+ return r;
+
+ sd_ndisc_redirect_ref(rd);
+
+ /* Finally, request the corresponding route. */
+ _cleanup_(route_unrefp) Route *route = NULL;
+ r = ndisc_redirect_route_new(rd, &route);
+ if (r < 0)
+ return r;
+
+ sd_ndisc_router *rt = hashmap_get(link->ndisc_routers_by_sender, &route->provider.in6);
+ if (!rt)
+ return -EADDRNOTAVAIL;
+
+ r = sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &route->lifetime_usec);
+ if (r < 0)
+ return r;
+
+ return ndisc_request_route(route, link);
+}
+
+static int ndisc_drop_redirect(Link *link, const struct in6_addr *router) {
+ int r, ret = 0;
+
+ assert(link);
+
+ sd_ndisc_redirect *rd;
+ SET_FOREACH(rd, link->ndisc_redirects) {
+ if (router) {
+ struct in6_addr a;
+
+ if (!(sd_ndisc_redirect_get_sender_address(rd, &a) >= 0 && in6_addr_equal(&a, router)) &&
+ !(sd_ndisc_redirect_get_target_address(rd, &a) >= 0 && in6_addr_equal(&a, router)))
+ continue;
+ }
+
+ r = ndisc_remove_redirect_route(link, rd);
+ if (r < 0)
+ RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove redirect route, ignoring: %m"));
+
+ sd_ndisc_redirect_unref(set_remove(link->ndisc_redirects, rd));
+ }
+
+ return ret;
+}
+
+static int ndisc_update_redirect_sender(Link *link, const struct in6_addr *original_address, const struct in6_addr *current_address) {
+ int r;
+
+ assert(link);
+ assert(original_address);
+ assert(current_address);
+
+ sd_ndisc_redirect *rd;
+ SET_FOREACH(rd, link->ndisc_redirects) {
+ struct in6_addr sender;
+
+ r = sd_ndisc_redirect_get_sender_address(rd, &sender);
+ if (r < 0)
+ return r;
+
+ if (!in6_addr_equal(&sender, original_address))
+ continue;
+
+ r = sd_ndisc_redirect_set_sender_address(rd, current_address);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int ndisc_router_drop_default(Link *link, sd_ndisc_router *rt) {
+ _cleanup_(route_unrefp) Route *route = NULL;
+ struct in6_addr gateway;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(rt);
+
+ r = sd_ndisc_router_get_sender_address(rt, &gateway);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get router address from RA: %m");
+
+ r = route_new(&route);
+ if (r < 0)
+ return log_oom();
+
+ route->family = AF_INET6;
+ route->nexthop.family = AF_INET6;
+ route->nexthop.gw.in6 = gateway;
+
+ r = ndisc_remove_route(route, link);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to remove the default gateway configured by RA: %m");
+
+ Route *route_gw;
+ HASHMAP_FOREACH(route_gw, link->network->routes_by_section) {
+ _cleanup_(route_unrefp) Route *tmp = NULL;
+
+ if (!route_gw->gateway_from_dhcp_or_ra)
+ continue;
+
+ if (route_gw->nexthop.family != AF_INET6)
+ continue;
+
+ r = route_dup(route_gw, NULL, &tmp);
+ if (r < 0)
+ return r;
+
+ tmp->nexthop.gw.in6 = gateway;
+
+ r = ndisc_remove_route(tmp, link);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not remove semi-static gateway: %m");
+ }
+
+ return 0;
+}
+
static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
usec_t lifetime_usec;
struct in6_addr gateway;
- unsigned preference;
+ uint8_t preference;
int r;
assert(link);
assert(link->network);
assert(rt);
- if (!link->network->ipv6_accept_ra_use_gateway &&
+ /* If the router lifetime is zero, the router should not be used as the default gateway. */
+ r = sd_ndisc_router_get_lifetime(rt, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return ndisc_router_drop_default(link, rt);
+
+ if (!link->network->ndisc_use_gateway &&
hashmap_isempty(link->network->routes_by_section))
return 0;
@@ -294,23 +766,16 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get gateway lifetime from RA: %m");
- r = sd_ndisc_router_get_address(rt, &gateway);
+ r = sd_ndisc_router_get_sender_address(rt, &gateway);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
- if (link_get_ipv6_address(link, &gateway, 0, NULL) >= 0) {
- if (DEBUG_LOGGING)
- log_link_debug(link, "No NDisc route added, gateway %s matches local address",
- IN6_ADDR_TO_STRING(&gateway));
- return 0;
- }
-
r = sd_ndisc_router_get_preference(rt, &preference);
if (r < 0)
- return log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
+ return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m");
- if (link->network->ipv6_accept_ra_use_gateway) {
- _cleanup_(route_freep) Route *route = NULL;
+ if (link->network->ndisc_use_gateway) {
+ _cleanup_(route_unrefp) Route *route = NULL;
r = route_new(&route);
if (r < 0)
@@ -318,35 +783,35 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
route->family = AF_INET6;
route->pref = preference;
- route->gw_family = AF_INET6;
- route->gw.in6 = gateway;
+ route->nexthop.family = AF_INET6;
+ route->nexthop.gw.in6 = gateway;
route->lifetime_usec = lifetime_usec;
- r = ndisc_request_route(TAKE_PTR(route), link, rt);
+ r = ndisc_request_router_route(route, link, rt);
if (r < 0)
return log_link_warning_errno(link, r, "Could not request default route: %m");
}
Route *route_gw;
HASHMAP_FOREACH(route_gw, link->network->routes_by_section) {
- _cleanup_(route_freep) Route *route = NULL;
+ _cleanup_(route_unrefp) Route *route = NULL;
if (!route_gw->gateway_from_dhcp_or_ra)
continue;
- if (route_gw->gw_family != AF_INET6)
+ if (route_gw->nexthop.family != AF_INET6)
continue;
- r = route_dup(route_gw, &route);
+ r = route_dup(route_gw, NULL, &route);
if (r < 0)
return r;
- route->gw.in6 = gateway;
+ route->nexthop.gw.in6 = gateway;
if (!route->pref_set)
route->pref = preference;
route->lifetime_usec = lifetime_usec;
- r = ndisc_request_route(TAKE_PTR(route), link, rt);
+ r = ndisc_request_router_route(route, link, rt);
if (r < 0)
return log_link_warning_errno(link, r, "Could not request gateway: %m");
}
@@ -354,54 +819,310 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
return 0;
}
-static int ndisc_router_process_icmp6_ratelimit(Link *link, sd_ndisc_router *rt) {
- usec_t icmp6_ratelimit, msec;
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ ndisc_router_hash_ops,
+ struct in6_addr,
+ in6_addr_hash_func,
+ in6_addr_compare_func,
+ sd_ndisc_router,
+ sd_ndisc_router_unref);
+
+static int ndisc_update_router_address(Link *link, const struct in6_addr *original_address, const struct in6_addr *current_address) {
+ _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
+ int r;
+
+ assert(link);
+ assert(original_address);
+ assert(current_address);
+
+ rt = hashmap_remove(link->ndisc_routers_by_sender, original_address);
+ if (!rt)
+ return 0;
+
+ /* If we already received an RA from the new address, then forget the RA from the old address. */
+ if (hashmap_contains(link->ndisc_routers_by_sender, current_address))
+ return 0;
+
+ /* Otherwise, update the sender address of the previously received RA. */
+ r = sd_ndisc_router_set_sender_address(rt, current_address);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(link->ndisc_routers_by_sender, &rt->packet->sender_address, rt);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(rt);
+ return 0;
+}
+
+static int ndisc_drop_router_one(Link *link, sd_ndisc_router *rt, usec_t timestamp_usec) {
+ usec_t lifetime_usec;
+ int r;
+
+ assert(link);
+ assert(rt);
+ assert(rt->packet);
+
+ r = sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec);
+ if (r < 0)
+ return r;
+
+ if (lifetime_usec > timestamp_usec)
+ return 0;
+
+ r = ndisc_drop_redirect(link, &rt->packet->sender_address);
+
+ sd_ndisc_router_unref(hashmap_remove(link->ndisc_routers_by_sender, &rt->packet->sender_address));
+
+ return r;
+}
+
+static int ndisc_drop_routers(Link *link, const struct in6_addr *router, usec_t timestamp_usec) {
+ sd_ndisc_router *rt;
+ int ret = 0;
+
+ assert(link);
+
+ if (router) {
+ rt = hashmap_get(link->ndisc_routers_by_sender, router);
+ if (!rt)
+ return 0;
+
+ return ndisc_drop_router_one(link, rt, timestamp_usec);
+ }
+
+ HASHMAP_FOREACH_KEY(rt, router, link->ndisc_routers_by_sender)
+ RET_GATHER(ret, ndisc_drop_router_one(link, rt, timestamp_usec));
+
+ return ret;
+}
+
+static int ndisc_remember_router(Link *link, sd_ndisc_router *rt) {
+ int r;
+
+ assert(link);
+ assert(rt);
+ assert(rt->packet);
+
+ sd_ndisc_router_unref(hashmap_remove(link->ndisc_routers_by_sender, &rt->packet->sender_address));
+
+ /* Remember RAs with non-zero lifetime. */
+ r = sd_ndisc_router_get_lifetime(rt, NULL);
+ if (r <= 0)
+ return r;
+
+ r = hashmap_ensure_put(&link->ndisc_routers_by_sender, &ndisc_router_hash_ops, &rt->packet->sender_address, rt);
+ if (r < 0)
+ return r;
+
+ sd_ndisc_router_ref(rt);
+ return 0;
+}
+
+static int ndisc_router_process_reachable_time(Link *link, sd_ndisc_router *rt) {
+ usec_t reachable_time, msec;
int r;
assert(link);
assert(link->network);
assert(rt);
- if (!link->network->ipv6_accept_ra_use_icmp6_ratelimit)
+ if (!link->network->ndisc_use_reachable_time)
+ return 0;
+
+ r = sd_ndisc_router_get_reachable_time(rt, &reachable_time);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get reachable time from RA: %m");
+
+ /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4) */
+ if (!timestamp_is_set(reachable_time))
+ return 0;
+
+ msec = DIV_ROUND_UP(reachable_time, USEC_PER_MSEC);
+ if (msec <= 0 || msec > UINT32_MAX) {
+ log_link_debug(link, "Failed to get reachable time from RA - out of range (%"PRIu64"), ignoring", msec);
+ return 0;
+ }
+
+ /* Set the reachable time for Neighbor Solicitations. */
+ r = sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "base_reachable_time_ms", (uint32_t) msec);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Failed to apply neighbor reachable time (%"PRIu64"), ignoring: %m", msec);
+
+ return 0;
+}
+
+static int ndisc_router_process_retransmission_time(Link *link, sd_ndisc_router *rt) {
+ usec_t retrans_time, msec;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(rt);
+
+ if (!link->network->ndisc_use_retransmission_time)
+ return 0;
+
+ r = sd_ndisc_router_get_retransmission_time(rt, &retrans_time);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get retransmission time from RA: %m");
+
+ /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4) */
+ if (!timestamp_is_set(retrans_time))
return 0;
- r = sd_ndisc_router_get_icmp6_ratelimit(rt, &icmp6_ratelimit);
- if (r < 0) {
- log_link_debug(link, "Failed to get ICMP6 ratelimit from RA, ignoring: %m");
+ msec = DIV_ROUND_UP(retrans_time, USEC_PER_MSEC);
+ if (msec <= 0 || msec > UINT32_MAX) {
+ log_link_debug(link, "Failed to get retransmission time from RA - out of range (%"PRIu64"), ignoring", msec);
return 0;
}
- /* We do not allow 0 here. */
- if (!timestamp_is_set(icmp6_ratelimit))
+ /* Set the retransmission time for Neighbor Solicitations. */
+ r = sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", (uint32_t) msec);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Failed to apply neighbor retransmission time (%"PRIu64"), ignoring: %m", msec);
+
+ return 0;
+}
+
+static int ndisc_router_process_hop_limit(Link *link, sd_ndisc_router *rt) {
+ uint8_t hop_limit;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(rt);
+
+ if (!link->network->ndisc_use_hop_limit)
+ return 0;
+
+ r = sd_ndisc_router_get_hop_limit(rt, &hop_limit);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get hop limit from RA: %m");
+
+ /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4):
+ *
+ * A Router Advertisement field (e.g., Cur Hop Limit, Reachable Time, and Retrans Timer) may contain
+ * a value denoting that it is unspecified. In such cases, the parameter should be ignored and the
+ * host should continue using whatever value it is already using. In particular, a host MUST NOT
+ * interpret the unspecified value as meaning change back to the default value that was in use before
+ * the first Router Advertisement was received.
+ *
+ * If the received Cur Hop Limit value is non-zero, the host SHOULD set
+ * its CurHopLimit variable to the received value.*/
+ if (hop_limit <= 0)
+ return 0;
+
+ r = sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "hop_limit", (uint32_t) hop_limit);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Failed to apply hop_limit (%u), ignoring: %m", hop_limit);
+
+ return 0;
+}
+
+static int ndisc_router_process_mtu(Link *link, sd_ndisc_router *rt) {
+ uint32_t mtu;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(rt);
+
+ if (!link->network->ndisc_use_mtu)
return 0;
- msec = DIV_ROUND_UP(icmp6_ratelimit, USEC_PER_MSEC);
- if (msec <= 0 || msec > INT_MAX)
+ r = sd_ndisc_router_get_mtu(rt, &mtu);
+ if (r == -ENODATA)
return 0;
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get MTU from RA: %m");
+
+ link->ndisc_mtu = mtu;
- /* Limit the maximal rates for sending ICMPv6 packets. 0 to disable any limiting, otherwise the
- * minimal space between responses in milliseconds. Default: 1000. */
- r = sysctl_write_ip_property_int(AF_INET6, NULL, "icmp/ratelimit", (int) msec);
+ r = link_set_ipv6_mtu(link, LOG_DEBUG);
if (r < 0)
- log_link_warning_errno(link, r, "Failed to apply ICMP6 ratelimit, ignoring: %m");
+ log_link_warning_errno(link, r, "Failed to apply IPv6 MTU (%"PRIu32"), ignoring: %m", mtu);
return 0;
}
+static int ndisc_address_set_lifetime(Address *address, Link *link, sd_ndisc_router *rt) {
+ Address *existing;
+ usec_t t;
+ int r;
+
+ assert(address);
+ assert(link);
+ assert(rt);
+
+ /* This is mostly based on RFC 4862 section 5.5.3 (e). However, the definition of 'RemainingLifetime'
+ * is ambiguous, and there is no clear explanation when the address is not assigned yet. If we assume
+ * that 'RemainingLifetime' is zero in that case, then IPv6 Core Conformance test [v6LC.3.2.5 Part C]
+ * fails. So, in such case, we skip the conditions about 'RemainingLifetime'. */
+
+ r = sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_BOOTTIME, &address->lifetime_valid_usec);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(rt, CLOCK_BOOTTIME, &address->lifetime_preferred_usec);
+ if (r < 0)
+ return r;
+
+ /* RFC 4862 section 5.5.3 (e)
+ * 1. If the received Valid Lifetime is greater than 2 hours or greater than RemainingLifetime,
+ * set the valid lifetime of the corresponding address to the advertised Valid Lifetime. */
+ r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &t);
+ if (r < 0)
+ return r;
+
+ if (t > 2 * USEC_PER_HOUR)
+ return 0;
+
+ if (address_get(link, address, &existing) < 0 || existing->source != NETWORK_CONFIG_SOURCE_NDISC)
+ return 0;
+
+ if (address->lifetime_valid_usec > existing->lifetime_valid_usec)
+ return 0;
+
+ /* 2. If RemainingLifetime is less than or equal to 2 hours, ignore the Prefix Information option
+ * with regards to the valid lifetime, unless the Router Advertisement from which this option was
+ * obtained has been authenticated (e.g., via Secure Neighbor Discovery [RFC3971]). If the Router
+ * Advertisement was authenticated, the valid lifetime of the corresponding address should be set
+ * to the Valid Lifetime in the received option.
+ *
+ * Currently, authentication is not supported. So check the lifetime of the existing address. */
+ r = sd_ndisc_router_get_timestamp(rt, CLOCK_BOOTTIME, &t);
+ if (r < 0)
+ return r;
+
+ if (existing->lifetime_valid_usec <= usec_add(t, 2 * USEC_PER_HOUR)) {
+ address->lifetime_valid_usec = existing->lifetime_valid_usec;
+ return 0;
+ }
+
+ /* 3. Otherwise, reset the valid lifetime of the corresponding address to 2 hours. */
+ address->lifetime_valid_usec = usec_add(t, 2 * USEC_PER_HOUR);
+ return 0;
+}
+
static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) {
usec_t lifetime_valid_usec, lifetime_preferred_usec;
- _cleanup_set_free_ Set *addresses = NULL;
- struct in6_addr prefix, *a;
- unsigned prefixlen;
+ struct in6_addr prefix, router;
+ uint8_t prefixlen;
int r;
assert(link);
assert(link->network);
assert(rt);
- if (!link->network->ipv6_accept_ra_use_autonomous_prefix)
+ if (!link->network->ndisc_use_autonomous_prefix)
return 0;
+ r = sd_ndisc_router_get_sender_address(rt, &router);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get router address: %m");
+
r = sd_ndisc_router_prefix_get_address(rt, &prefix);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get prefix address: %m");
@@ -417,37 +1138,48 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r
return 0;
}
- r = sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_valid_usec);
+ r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid_usec);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get prefix valid lifetime: %m");
- r = sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_preferred_usec);
+ r = sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred_usec);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get prefix preferred lifetime: %m");
- /* The preferred lifetime is never greater than the valid lifetime */
+ /* RFC 4862 section 5.5.3 (c)
+ * If the preferred lifetime is greater than the valid lifetime, silently ignore the Prefix
+ * Information option. */
if (lifetime_preferred_usec > lifetime_valid_usec)
return 0;
- r = ndisc_generate_addresses(link, &prefix, prefixlen, &addresses);
+ _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL;
+ r = ndisc_generate_addresses(link, &prefix, prefixlen, &tokens_by_address);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to generate SLAAC addresses: %m");
- SET_FOREACH(a, addresses) {
- _cleanup_(address_freep) Address *address = NULL;
+ IPv6Token *token;
+ struct in6_addr *a;
+ HASHMAP_FOREACH_KEY(token, a, tokens_by_address) {
+ _cleanup_(address_unrefp) Address *address = NULL;
r = address_new(&address);
if (r < 0)
return log_oom();
+ address->provider.in6 = router;
address->family = AF_INET6;
address->in_addr.in6 = *a;
address->prefixlen = prefixlen;
address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR;
- address->lifetime_valid_usec = lifetime_valid_usec;
- address->lifetime_preferred_usec = lifetime_preferred_usec;
+ address->token = ipv6_token_ref(token);
- r = ndisc_request_address(TAKE_PTR(address), link, rt);
+ r = ndisc_address_set_lifetime(address, link, rt);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to set lifetime of SLAAC address: %m");
+
+ assert(address->lifetime_preferred_usec <= address->lifetime_valid_usec);
+
+ r = ndisc_request_address(address, link);
if (r < 0)
return log_link_warning_errno(link, r, "Could not request SLAAC address: %m");
}
@@ -456,8 +1188,8 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r
}
static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
- _cleanup_(route_freep) Route *route = NULL;
- unsigned prefixlen, preference;
+ _cleanup_(route_unrefp) Route *route = NULL;
+ uint8_t prefixlen, preference;
usec_t lifetime_usec;
struct in6_addr prefix;
int r;
@@ -466,7 +1198,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
assert(link->network);
assert(rt);
- if (!link->network->ipv6_accept_ra_use_onlink_prefix)
+ if (!link->network->ndisc_use_onlink_prefix)
return 0;
r = sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec);
@@ -484,7 +1216,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
/* Prefix Information option does not have preference, hence we use the 'main' preference here */
r = sd_ndisc_router_get_preference(rt, &preference);
if (r < 0)
- log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
+ return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m");
r = route_new(&route);
if (r < 0)
@@ -496,17 +1228,30 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
route->pref = preference;
route->lifetime_usec = lifetime_usec;
- r = ndisc_request_route(TAKE_PTR(route), link, rt);
- if (r < 0)
- return log_link_warning_errno(link, r, "Could not request prefix route: %m");
+ /* RFC 4861 section 6.3.4:
+ * - If the prefix is not already present in the Prefix List, and the Prefix Information option's
+ * Valid Lifetime field is non-zero, create a new entry for the prefix and initialize its
+ * invalidation timer to the Valid Lifetime value in the Prefix Information option.
+ *
+ * - If the prefix is already present in the host's Prefix List as the result of a previously
+ * received advertisement, reset its invalidation timer to the Valid Lifetime value in the Prefix
+ * Information option. If the new Lifetime value is zero, time-out the prefix immediately. */
+ if (lifetime_usec == 0) {
+ r = ndisc_remove_route(route, link);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to remove prefix route: %m");
+ } else {
+ r = ndisc_request_router_route(route, link, rt);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to request prefix route: %m");
+ }
return 0;
}
static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) {
- unsigned prefixlen;
+ uint8_t flags, prefixlen;
struct in6_addr a;
- uint8_t flags;
int r;
assert(link);
@@ -521,7 +1266,7 @@ static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) {
* A router SHOULD NOT send a prefix option for the link-local prefix and a host SHOULD ignore such
* a prefix option. */
if (in6_addr_is_link_local(&a)) {
- log_link_debug(link, "Received link-local prefix, ignoring autonomous prefix.");
+ log_link_debug(link, "Received link-local prefix, ignoring prefix.");
return 0;
}
@@ -558,15 +1303,15 @@ static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) {
}
static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
- _cleanup_(route_freep) Route *route = NULL;
- unsigned preference, prefixlen;
+ _cleanup_(route_unrefp) Route *route = NULL;
+ uint8_t preference, prefixlen;
struct in6_addr gateway, dst;
usec_t lifetime_usec;
int r;
assert(link);
- if (!link->network->ipv6_accept_ra_use_route_prefix)
+ if (!link->network->ndisc_use_route_prefix)
return 0;
r = sd_ndisc_router_route_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec);
@@ -581,11 +1326,6 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get route prefix length: %m");
- if (in6_addr_is_null(&dst) && prefixlen == 0) {
- log_link_debug(link, "Route prefix is ::/0, ignoring");
- return 0;
- }
-
if (in6_prefix_is_filtered(&dst, prefixlen,
link->network->ndisc_allow_listed_route_prefix,
link->network->ndisc_deny_listed_route_prefix)) {
@@ -598,7 +1338,7 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
return 0;
}
- r = sd_ndisc_router_get_address(rt, &gateway);
+ r = sd_ndisc_router_get_sender_address(rt, &gateway);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
@@ -610,12 +1350,12 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
}
r = sd_ndisc_router_route_get_preference(rt, &preference);
- if (r == -ENOTSUP) {
+ if (r == -EOPNOTSUPP) {
log_link_debug_errno(link, r, "Received route prefix with unsupported preference, ignoring: %m");
return 0;
}
if (r < 0)
- return log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
+ return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m");
r = route_new(&route);
if (r < 0)
@@ -623,21 +1363,27 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
route->family = AF_INET6;
route->pref = preference;
- route->gw.in6 = gateway;
- route->gw_family = AF_INET6;
+ route->nexthop.gw.in6 = gateway;
+ route->nexthop.family = AF_INET6;
route->dst.in6 = dst;
route->dst_prefixlen = prefixlen;
route->lifetime_usec = lifetime_usec;
- r = ndisc_request_route(TAKE_PTR(route), link, rt);
- if (r < 0)
- return log_link_warning_errno(link, r, "Could not request additional route: %m");
+ if (lifetime_usec != 0) {
+ r = ndisc_request_router_route(route, link, rt);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not request additional route: %m");
+ } else {
+ r = ndisc_remove_route(route, link);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not remove additional route with zero lifetime: %m");
+ }
return 0;
}
static void ndisc_rdnss_hash_func(const NDiscRDNSS *x, struct siphash *state) {
- siphash24_compress(&x->address, sizeof(x->address), state);
+ siphash24_compress_typesafe(x->address, state);
}
static int ndisc_rdnss_compare_func(const NDiscRDNSS *a, const NDiscRDNSS *b) {
@@ -662,10 +1408,10 @@ static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) {
assert(link->network);
assert(rt);
- if (!link->network->ipv6_accept_ra_use_dns)
+ if (!link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC))
return 0;
- r = sd_ndisc_router_get_address(rt, &router);
+ r = sd_ndisc_router_get_sender_address(rt, &router);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get router address from RA: %m");
@@ -744,7 +1490,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
free);
static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) {
- _cleanup_strv_free_ char **l = NULL;
+ char **l;
usec_t lifetime_usec;
struct in6_addr router;
bool updated = false, logged_about_too_many = false;
@@ -754,10 +1500,10 @@ static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) {
assert(link->network);
assert(rt);
- if (link->network->ipv6_accept_ra_use_domains == DHCP_USE_DOMAINS_NO)
+ if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) <= 0)
return 0;
- r = sd_ndisc_router_get_address(rt, &router);
+ r = sd_ndisc_router_get_sender_address(rt, &router);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get router address from RA: %m");
@@ -848,21 +1594,20 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) {
_cleanup_(ndisc_captive_portal_freep) NDiscCaptivePortal *new_entry = NULL;
_cleanup_free_ char *captive_portal = NULL;
+ const char *uri;
usec_t lifetime_usec;
NDiscCaptivePortal *exist;
struct in6_addr router;
- const char *uri;
- size_t len;
int r;
assert(link);
assert(link->network);
assert(rt);
- if (!link->network->ipv6_accept_ra_use_captive_portal)
+ if (!link->network->ndisc_use_captive_portal)
return 0;
- r = sd_ndisc_router_get_address(rt, &router);
+ r = sd_ndisc_router_get_sender_address(rt, &router);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get router address from RA: %m");
@@ -873,31 +1618,25 @@ static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt)
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get lifetime of RA message: %m");
- r = sd_ndisc_router_captive_portal_get_uri(rt, &uri, &len);
+ r = sd_ndisc_router_get_captive_portal(rt, &uri);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get captive portal from RA: %m");
- if (len == 0)
- return log_link_warning_errno(link, SYNTHETIC_ERRNO(EBADMSG), "Received empty captive portal, ignoring.");
-
- r = make_cstring(uri, len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &captive_portal);
- if (r < 0)
- return log_link_warning_errno(link, r, "Failed to convert captive portal URI: %m");
-
- if (!in_charset(captive_portal, URI_VALID))
- return log_link_warning_errno(link, SYNTHETIC_ERRNO(EBADMSG), "Received invalid captive portal, ignoring.");
+ captive_portal = strdup(uri);
+ if (!captive_portal)
+ return log_oom();
if (lifetime_usec == 0) {
/* Drop the portal with zero lifetime. */
ndisc_captive_portal_free(set_remove(link->ndisc_captive_portals,
- &(NDiscCaptivePortal) {
+ &(const NDiscCaptivePortal) {
.captive_portal = captive_portal,
}));
return 0;
}
exist = set_get(link->ndisc_captive_portals,
- &(NDiscCaptivePortal) {
+ &(const NDiscCaptivePortal) {
.captive_portal = captive_portal,
});
if (exist) {
@@ -943,8 +1682,8 @@ static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt)
static void ndisc_pref64_hash_func(const NDiscPREF64 *x, struct siphash *state) {
assert(x);
- siphash24_compress(&x->prefix_len, sizeof(x->prefix_len), state);
- siphash24_compress(&x->prefix, sizeof(x->prefix), state);
+ siphash24_compress_typesafe(x->prefix_len, state);
+ siphash24_compress_typesafe(x->prefix, state);
}
static int ndisc_pref64_compare_func(const NDiscPREF64 *a, const NDiscPREF64 *b) {
@@ -971,7 +1710,7 @@ static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) {
_cleanup_free_ NDiscPREF64 *new_entry = NULL;
usec_t lifetime_usec;
struct in6_addr a, router;
- unsigned prefix_len;
+ uint8_t prefix_len;
NDiscPREF64 *exist;
int r;
@@ -979,10 +1718,10 @@ static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) {
assert(link->network);
assert(rt);
- if (!link->network->ipv6_accept_ra_use_pref64)
+ if (!link->network->ndisc_use_pref64)
return 0;
- r = sd_ndisc_router_get_address(rt, &router);
+ r = sd_ndisc_router_get_sender_address(rt, &router);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get router address from RA: %m");
@@ -1102,7 +1841,7 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
}
}
-static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) {
+static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t timestamp_usec) {
bool updated = false;
NDiscDNSSL *dnssl;
NDiscRDNSS *rdnss;
@@ -1110,9 +1849,10 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) {
NDiscPREF64 *p64;
Address *address;
Route *route;
- int r = 0, k;
+ int r, ret = 0;
assert(link);
+ assert(link->manager);
/* If an address or friends is already assigned, but not valid anymore, then refuse to update it,
* and let's immediately remove it.
@@ -1120,58 +1860,86 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) {
* valid lifetimes to improve the reaction of SLAAC to renumbering events.
* See draft-ietf-6man-slaac-renum-02, section 4.2. */
- SET_FOREACH(route, link->routes) {
+ r = ndisc_drop_routers(link, router, timestamp_usec);
+ if (r < 0)
+ RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to drop outdated default router, ignoring: %m"));
+
+ SET_FOREACH(route, link->manager->routes) {
if (route->source != NETWORK_CONFIG_SOURCE_NDISC)
continue;
- if (route->lifetime_usec >= timestamp_usec)
+ if (route->nexthop.ifindex != link->ifindex)
+ continue;
+
+ if (route->protocol == RTPROT_REDIRECT)
+ continue; /* redirect route will be dropped by ndisc_drop_redirect(). */
+
+ if (route->lifetime_usec > timestamp_usec)
continue; /* the route is still valid */
- k = route_remove_and_drop(route);
- if (k < 0)
- r = log_link_warning_errno(link, k, "Failed to remove outdated SLAAC route, ignoring: %m");
+ if (router && !in6_addr_equal(&route->provider.in6, router))
+ continue;
+
+ r = route_remove_and_cancel(route, link->manager);
+ if (r < 0)
+ RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC route, ignoring: %m"));
}
SET_FOREACH(address, link->addresses) {
if (address->source != NETWORK_CONFIG_SOURCE_NDISC)
continue;
- if (address->lifetime_valid_usec >= timestamp_usec)
+ if (address->lifetime_valid_usec > timestamp_usec)
continue; /* the address is still valid */
- k = address_remove_and_drop(address);
- if (k < 0)
- r = log_link_warning_errno(link, k, "Failed to remove outdated SLAAC address, ignoring: %m");
+ if (router && !in6_addr_equal(&address->provider.in6, router))
+ continue;
+
+ r = address_remove_and_cancel(address, link);
+ if (r < 0)
+ RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC address, ignoring: %m"));
}
SET_FOREACH(rdnss, link->ndisc_rdnss) {
- if (rdnss->lifetime_usec >= timestamp_usec)
+ if (rdnss->lifetime_usec > timestamp_usec)
continue; /* the DNS server is still valid */
+ if (router && !in6_addr_equal(&rdnss->router, router))
+ continue;
+
free(set_remove(link->ndisc_rdnss, rdnss));
updated = true;
}
SET_FOREACH(dnssl, link->ndisc_dnssl) {
- if (dnssl->lifetime_usec >= timestamp_usec)
+ if (dnssl->lifetime_usec > timestamp_usec)
continue; /* the DNS domain is still valid */
+ if (router && !in6_addr_equal(&dnssl->router, router))
+ continue;
+
free(set_remove(link->ndisc_dnssl, dnssl));
updated = true;
}
SET_FOREACH(cp, link->ndisc_captive_portals) {
- if (cp->lifetime_usec >= timestamp_usec)
+ if (cp->lifetime_usec > timestamp_usec)
continue; /* the captive portal is still valid */
+ if (router && !in6_addr_equal(&cp->router, router))
+ continue;
+
ndisc_captive_portal_free(set_remove(link->ndisc_captive_portals, cp));
updated = true;
}
SET_FOREACH(p64, link->ndisc_pref64) {
- if (p64->lifetime_usec >= timestamp_usec)
+ if (p64->lifetime_usec > timestamp_usec)
continue; /* the pref64 prefix is still valid */
+ if (router && !in6_addr_equal(&p64->router, router))
+ continue;
+
free(set_remove(link->ndisc_pref64, p64));
/* The pref64 prefix is not exported through the state file, hence it is not necessary to set
* the 'updated' flag. */
@@ -1180,7 +1948,7 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) {
if (updated)
link_dirty(link);
- return r;
+ return ret;
}
static int ndisc_setup_expire(Link *link);
@@ -1193,7 +1961,7 @@ static int ndisc_expire_handler(sd_event_source *s, uint64_t usec, void *userdat
assert_se(sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec) >= 0);
- (void) ndisc_drop_outdated(link, now_usec);
+ (void) ndisc_drop_outdated(link, /* router = */ NULL, now_usec);
(void) ndisc_setup_expire(link);
return 0;
}
@@ -1211,10 +1979,23 @@ static int ndisc_setup_expire(Link *link) {
assert(link);
assert(link->manager);
- SET_FOREACH(route, link->routes) {
+ sd_ndisc_router *rt;
+ HASHMAP_FOREACH(rt, link->ndisc_routers_by_sender) {
+ usec_t t;
+
+ if (sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &t) < 0)
+ continue;
+
+ lifetime_usec = MIN(lifetime_usec, t);
+ }
+
+ SET_FOREACH(route, link->manager->routes) {
if (route->source != NETWORK_CONFIG_SOURCE_NDISC)
continue;
+ if (route->nexthop.ifindex != link->ifindex)
+ continue;
+
if (!route_exists(route))
continue;
@@ -1260,7 +2041,13 @@ static int ndisc_start_dhcp6_client(Link *link, sd_ndisc_router *rt) {
assert(link);
assert(link->network);
- switch (link->network->ipv6_accept_ra_start_dhcp6_client) {
+ /* Do not start DHCPv6 client if the router lifetime is zero, as the message sent as a signal of
+ * that the router is e.g. shutting down, revoked, etc,. */
+ r = sd_ndisc_router_get_lifetime(rt, NULL);
+ if (r <= 0)
+ return r;
+
+ switch (link->network->ndisc_start_dhcp6_client) {
case IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO:
return 0;
@@ -1307,7 +2094,7 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
assert(link->manager);
assert(rt);
- r = sd_ndisc_router_get_address(rt, &router);
+ r = sd_ndisc_router_get_sender_address(rt, &router);
if (r == -ENODATA) {
log_link_debug(link, "Received RA without router address, ignoring.");
return 0;
@@ -1333,7 +2120,11 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
if (r < 0)
return r;
- r = ndisc_drop_outdated(link, timestamp_usec);
+ r = ndisc_drop_outdated(link, /* router = */ NULL, timestamp_usec);
+ if (r < 0)
+ return r;
+
+ r = ndisc_remember_router(link, rt);
if (r < 0)
return r;
@@ -1345,7 +2136,19 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
if (r < 0)
return r;
- r = ndisc_router_process_icmp6_ratelimit(link, rt);
+ r = ndisc_router_process_reachable_time(link, rt);
+ if (r < 0)
+ return r;
+
+ r = ndisc_router_process_retransmission_time(link, rt);
+ if (r < 0)
+ return r;
+
+ r = ndisc_router_process_hop_limit(link, rt);
+ if (r < 0)
+ return r;
+
+ r = ndisc_router_process_mtu(link, rt);
if (r < 0)
return r;
@@ -1357,6 +2160,9 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
if (r < 0)
return r;
+ if (sd_ndisc_router_get_lifetime(rt, NULL) <= 0)
+ (void) ndisc_drop_redirect(link, &router);
+
if (link->ndisc_messages == 0)
link->ndisc_configured = true;
else
@@ -1369,7 +2175,142 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
return 0;
}
-static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router *rt, void *userdata) {
+static int ndisc_neighbor_handle_non_router_message(Link *link, sd_ndisc_neighbor *na) {
+ struct in6_addr address;
+ int r;
+
+ assert(link);
+ assert(na);
+
+ /* Received Neighbor Advertisement message without Router flag. The node might have been a router,
+ * and now it is not. Let's drop all configurations based on RAs sent from the node. */
+
+ r = sd_ndisc_neighbor_get_target_address(na, &address);
+ if (r == -ENODATA)
+ return 0;
+ if (r < 0)
+ return r;
+
+ (void) ndisc_drop_outdated(link, /* router = */ &address, /* timestamp_usec = */ USEC_INFINITY);
+ (void) ndisc_drop_redirect(link, &address);
+
+ return 0;
+}
+
+static int ndisc_neighbor_handle_router_message(Link *link, sd_ndisc_neighbor *na) {
+ struct in6_addr current_address, original_address;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(na);
+
+ /* Received Neighbor Advertisement message with Router flag. If the router address is changed, update
+ * the provider field of configurations. */
+
+ r = sd_ndisc_neighbor_get_sender_address(na, &current_address);
+ if (r == -ENODATA)
+ return 0;
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_neighbor_get_target_address(na, &original_address);
+ if (r == -ENODATA)
+ return 0;
+ if (r < 0)
+ return r;
+
+ if (in6_addr_equal(&current_address, &original_address))
+ return 0; /* the router address is not changed */
+
+ r = ndisc_update_router_address(link, &original_address, &current_address);
+ if (r < 0)
+ return r;
+
+ r = ndisc_update_redirect_sender(link, &original_address, &current_address);
+ if (r < 0)
+ return r;
+
+ Route *route;
+ SET_FOREACH(route, link->manager->routes) {
+ if (route->source != NETWORK_CONFIG_SOURCE_NDISC)
+ continue;
+
+ if (route->nexthop.ifindex != link->ifindex)
+ continue;
+
+ if (!in6_addr_equal(&route->provider.in6, &original_address))
+ continue;
+
+ route->provider.in6 = current_address;
+ }
+
+ Address *address;
+ SET_FOREACH(address, link->addresses) {
+ if (address->source != NETWORK_CONFIG_SOURCE_NDISC)
+ continue;
+
+ if (!in6_addr_equal(&address->provider.in6, &original_address))
+ continue;
+
+ address->provider.in6 = current_address;
+ }
+
+ NDiscRDNSS *rdnss;
+ SET_FOREACH(rdnss, link->ndisc_rdnss) {
+ if (!in6_addr_equal(&rdnss->router, &original_address))
+ continue;
+
+ rdnss->router = current_address;
+ }
+
+ NDiscDNSSL *dnssl;
+ SET_FOREACH(dnssl, link->ndisc_dnssl) {
+ if (!in6_addr_equal(&dnssl->router, &original_address))
+ continue;
+
+ dnssl->router = current_address;
+ }
+
+ NDiscCaptivePortal *cp;
+ SET_FOREACH(cp, link->ndisc_captive_portals) {
+ if (!in6_addr_equal(&cp->router, &original_address))
+ continue;
+
+ cp->router = current_address;
+ }
+
+ NDiscPREF64 *p64;
+ SET_FOREACH(p64, link->ndisc_pref64) {
+ if (!in6_addr_equal(&p64->router, &original_address))
+ continue;
+
+ p64->router = current_address;
+ }
+
+ return 0;
+}
+
+static int ndisc_neighbor_handler(Link *link, sd_ndisc_neighbor *na) {
+ int r;
+
+ assert(link);
+ assert(na);
+
+ r = sd_ndisc_neighbor_is_router(na);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ r = ndisc_neighbor_handle_non_router_message(link, na);
+ else
+ r = ndisc_neighbor_handle_router_message(link, na);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata) {
Link *link = ASSERT_PTR(userdata);
int r;
@@ -1379,13 +2320,30 @@ static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router
switch (event) {
case SD_NDISC_EVENT_ROUTER:
- r = ndisc_router_handler(link, rt);
+ r = ndisc_router_handler(link, ASSERT_PTR(message));
+ if (r < 0 && r != -EBADMSG) {
+ link_enter_failed(link);
+ return;
+ }
+ break;
+
+ case SD_NDISC_EVENT_NEIGHBOR:
+ r = ndisc_neighbor_handler(link, ASSERT_PTR(message));
if (r < 0 && r != -EBADMSG) {
link_enter_failed(link);
return;
}
break;
+ case SD_NDISC_EVENT_REDIRECT:
+ r = ndisc_redirect_handler(link, ASSERT_PTR(message));
+ if (r < 0 && r != -EBADMSG) {
+ log_link_warning_errno(link, r, "Failed to process Redirect message: %m");
+ link_enter_failed(link);
+ return;
+ }
+ break;
+
case SD_NDISC_EVENT_TIMEOUT:
log_link_debug(link, "NDisc handler get timeout event");
if (link->ndisc_messages == 0) {
@@ -1393,8 +2351,9 @@ static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router
link_check_ready(link);
}
break;
+
default:
- assert_not_reached();
+ log_link_debug(link, "Received unsupported NDisc event, ignoring.");
}
}
@@ -1403,7 +2362,7 @@ static int ndisc_configure(Link *link) {
assert(link);
- if (!link_ipv6_accept_ra_enabled(link))
+ if (!link_ndisc_enabled(link))
return 0;
if (link->ndisc)
@@ -1448,6 +2407,10 @@ int ndisc_start(Link *link) {
if (in6_addr_is_null(&link->ipv6ll_address))
return 0;
+ r = sd_ndisc_set_link_local_address(link->ndisc, &link->ipv6ll_address);
+ if (r < 0)
+ return r;
+
log_link_debug(link, "Discovering IPv6 routers");
r = sd_ndisc_start(link->ndisc);
@@ -1483,7 +2446,7 @@ int link_request_ndisc(Link *link) {
assert(link);
- if (!link_ipv6_accept_ra_enabled(link))
+ if (!link_ndisc_enabled(link))
return 0;
if (link->ndisc)
@@ -1505,27 +2468,29 @@ int ndisc_stop(Link *link) {
return sd_ndisc_stop(link->ndisc);
}
-
void ndisc_flush(Link *link) {
assert(link);
- /* Remove all RDNSS, DNSSL, and Captive Portal entries, without exception. */
+ /* Remove all addresses, routes, RDNSS, DNSSL, and Captive Portal entries, without exception. */
+ (void) ndisc_drop_outdated(link, /* router = */ NULL, /* timestamp_usec = */ USEC_INFINITY);
+ (void) ndisc_drop_redirect(link, /* router = */ NULL);
+ link->ndisc_routers_by_sender = hashmap_free(link->ndisc_routers_by_sender);
link->ndisc_rdnss = set_free(link->ndisc_rdnss);
link->ndisc_dnssl = set_free(link->ndisc_dnssl);
link->ndisc_captive_portals = set_free(link->ndisc_captive_portals);
link->ndisc_pref64 = set_free(link->ndisc_pref64);
+ link->ndisc_redirects = set_free(link->ndisc_redirects);
+ link->ndisc_mtu = 0;
}
-static const char* const ipv6_accept_ra_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = {
+static const char* const ndisc_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = {
[IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO] = "no",
[IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS] = "always",
[IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES] = "yes",
};
-DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES);
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(ndisc_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES);
-DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_accept_ra_use_domains, dhcp_use_domains, DHCPUseDomains,
- "Failed to parse UseDomains= setting");
-DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_accept_ra_start_dhcp6_client, ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client,
+DEFINE_CONFIG_PARSE_ENUM(config_parse_ndisc_start_dhcp6_client, ndisc_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client,
"Failed to parse DHCPv6Client= setting");