diff options
Diffstat (limited to 'src/network/networkd-ndisc.c')
-rw-r--r-- | src/network/networkd-ndisc.c | 1347 |
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, ¤t_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(¤t_address, &original_address)) + return 0; /* the router address is not changed */ + + r = ndisc_update_router_address(link, &original_address, ¤t_address); + if (r < 0) + return r; + + r = ndisc_update_redirect_sender(link, &original_address, ¤t_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"); |