diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index bb1eedc6f1d..259cd312c99 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -765,6 +765,18 @@ int address_remove(Address *address) { return 0; } +int address_remove_and_drop(Address *address) { + if (!address) + return 0; + + address_cancel_request(address); + + if (address_exists(address)) + return address_remove(address); + + return address_drop(address); +} + bool link_address_is_dynamic(const Link *link, const Address *address) { Route *route; @@ -1142,7 +1154,7 @@ int link_request_address( address_netlink_handler_t netlink_handler, Request **ret) { - Address *acquired, *existing; + Address *acquired, *existing = NULL; int r; assert(link); @@ -1175,7 +1187,13 @@ int link_request_address( address_set_broadcast(address, link); } - if (address_get(link, address, &existing) < 0) { + (void) address_get(link, address, &existing); + + if (address->lifetime_valid_usec == 0) + /* The requested address is outdated. Let's remove it. */ + return address_remove_and_drop(existing); + + if (!existing) { _cleanup_(address_freep) Address *tmp = NULL; if (consume_object) diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index ef29caf4e78..7a1e44632d5 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -78,6 +78,7 @@ Address* address_free(Address *address); int address_get(Link *link, const Address *in, Address **ret); int address_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg); int address_remove(Address *address); +int address_remove_and_drop(Address *address); int address_dup(const Address *src, Address **ret); bool address_is_ready(const Address *a); void address_set_broadcast(Address *a, Link *link); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 57335216115..69d5eb9d1b9 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -332,7 +332,7 @@ int link_stop_engines(Link *link, bool may_keep_dhcp) { if (k < 0) r = log_link_warning_errno(link, k, "Could not remove DHCPv6 PD addresses and routes: %m"); - k = sd_ndisc_stop(link->ndisc); + k = ndisc_stop(link); if (k < 0) r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m"); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 362f439940a..9f1cdca3127 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -147,6 +147,7 @@ typedef struct Link { sd_dhcp_server *dhcp_server; sd_ndisc *ndisc; + sd_event_source *ndisc_expire; Set *ndisc_rdnss; Set *ndisc_dnssl; unsigned ndisc_messages; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 2250235d4eb..95722e24ef9 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -10,6 +10,7 @@ #include "sd-ndisc.h" +#include "event-util.h" #include "missing_network.h" #include "networkd-address-generation.h" #include "networkd-address.h" @@ -234,9 +235,6 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { if (r < 0) return log_link_error_errno(link, r, "Failed to get gateway lifetime from RA: %m"); - if (lifetime_sec == 0) /* not a default router */ - return 0; - r = sd_ndisc_router_get_timestamp(rt, CLOCK_BOOTTIME, ×tamp_usec); if (r < 0) return log_link_error_errno(link, r, "Failed to get RA timestamp: %m"); @@ -350,11 +348,6 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r if (r < 0) return log_link_error_errno(link, r, "Failed to get prefix valid lifetime: %m"); - if (lifetime_valid_sec == 0) { - log_link_debug(link, "Ignoring prefix as its valid lifetime is zero."); - return 0; - } - r = sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred_sec); if (r < 0) return log_link_error_errno(link, r, "Failed to get prefix preferred lifetime: %m"); @@ -372,7 +365,6 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r SET_FOREACH(a, addresses) { _cleanup_(address_freep) Address *address = NULL; - Address *e; r = address_new(&address); if (r < 0) @@ -385,17 +377,6 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r address->lifetime_valid_usec = lifetime_valid_usec; address->lifetime_preferred_usec = lifetime_preferred_usec; - /* See RFC4862, section 5.5.3.e. But the following logic is deviated from RFC4862 by - * honoring all valid lifetimes to improve the reaction of SLAAC to renumbering events. - * See draft-ietf-6man-slaac-renum-02, section 4.2. */ - r = address_get(link, address, &e); - if (r > 0) { - /* If the address is already assigned, but not valid anymore, then refuse to - * update the address, and it will be removed. */ - if (e->lifetime_valid_usec < timestamp_usec) - continue; - } - r = ndisc_request_address(TAKE_PTR(address), link, rt); if (r < 0) return log_link_error_errno(link, r, "Could not request SLAAC address: %m"); @@ -408,6 +389,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { _cleanup_(route_freep) Route *route = NULL; usec_t timestamp_usec; uint32_t lifetime_sec; + struct in6_addr prefix; unsigned prefixlen; int r; @@ -422,13 +404,14 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { if (r < 0) return log_link_error_errno(link, r, "Failed to get prefix lifetime: %m"); - if (lifetime_sec == 0) - return 0; - r = sd_ndisc_router_get_timestamp(rt, CLOCK_BOOTTIME, ×tamp_usec); if (r < 0) return log_link_error_errno(link, r, "Failed to get RA timestamp: %m"); + r = sd_ndisc_router_prefix_get_address(rt, &prefix); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get prefix address: %m"); + r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen); if (r < 0) return log_link_error_errno(link, r, "Failed to get prefix length: %m"); @@ -439,13 +422,10 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { route->family = AF_INET6; route->flags = RTM_F_PREFIX; + route->dst.in6 = prefix; route->dst_prefixlen = prefixlen; route->lifetime_usec = sec_to_usec(lifetime_sec, timestamp_usec); - r = sd_ndisc_router_prefix_get_address(rt, &route->dst.in6); - if (r < 0) - return log_link_error_errno(link, r, "Failed to get prefix address: %m"); - r = ndisc_request_route(TAKE_PTR(route), link, rt); if (r < 0) return log_link_error_errno(link, r, "Could not request prefix route: %m"); @@ -467,6 +447,14 @@ static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) { if (r < 0) return log_link_error_errno(link, r, "Failed to get prefix address: %m"); + /* RFC 4861 Section 4.6.2: + * 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."); + return 0; + } + r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen); if (r < 0) return log_link_error_errno(link, r, "Failed to get prefix length: %m"); @@ -516,9 +504,6 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { if (r < 0) return log_link_error_errno(link, r, "Failed to get route lifetime from RA: %m"); - if (lifetime_sec == 0) - return 0; - r = sd_ndisc_router_route_get_address(rt, &dst); if (r < 0) return log_link_error_errno(link, r, "Failed to get route destination address: %m"); @@ -602,7 +587,7 @@ static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) { uint32_t lifetime_sec; const struct in6_addr *a; struct in6_addr router; - bool updated = false; + bool updated = false, logged_about_too_many = false; int n, r; assert(link); @@ -624,26 +609,25 @@ static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) { if (r < 0) return log_link_error_errno(link, r, "Failed to get RDNSS lifetime: %m"); - if (lifetime_sec == 0) - return 0; - lifetime_usec = sec_to_usec(lifetime_sec, timestamp_usec); n = sd_ndisc_router_rdnss_get_addresses(rt, &a); if (n < 0) return log_link_error_errno(link, n, "Failed to get RDNSS addresses: %m"); - if (n >= (int) NDISC_RDNSS_MAX) { - log_link_warning(link, "Too many RDNSS records per link. Only first %u records will be used.", NDISC_RDNSS_MAX); - n = NDISC_RDNSS_MAX; - } - for (int j = 0; j < n; j++) { _cleanup_free_ NDiscRDNSS *x = NULL; NDiscRDNSS *rdnss, d = { .address = a[j], }; + if (lifetime_usec == 0) { + /* The entry is outdated. */ + free(set_remove(link->ndisc_rdnss, &d)); + updated = true; + continue; + } + rdnss = set_get(link->ndisc_rdnss, &d); if (rdnss) { rdnss->router = router; @@ -651,6 +635,13 @@ static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) { continue; } + if (set_size(link->ndisc_rdnss) >= NDISC_RDNSS_MAX) { + if (!logged_about_too_many) + log_link_warning(link, "Too many RDNSS records per link. Only first %u records will be used.", NDISC_RDNSS_MAX); + logged_about_too_many = true; + continue; + } + x = new(NDiscRDNSS, 1); if (!x) return log_oom(); @@ -695,7 +686,7 @@ static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { usec_t lifetime_usec, timestamp_usec; struct in6_addr router; uint32_t lifetime_sec; - bool updated = false; + bool updated = false, logged_about_too_many = false; int r; assert(link); @@ -717,21 +708,12 @@ static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { if (r < 0) return log_link_error_errno(link, r, "Failed to get DNSSL lifetime: %m"); - if (lifetime_sec == 0) - return 0; - lifetime_usec = sec_to_usec(lifetime_sec, timestamp_usec); r = sd_ndisc_router_dnssl_get_domains(rt, &l); if (r < 0) return log_link_error_errno(link, r, "Failed to get DNSSL addresses: %m"); - if (strv_length(l) >= NDISC_DNSSL_MAX) { - log_link_warning(link, "Too many DNSSL records per link. Only first %u records will be used.", NDISC_DNSSL_MAX); - STRV_FOREACH(j, l + NDISC_DNSSL_MAX) - *j = mfree(*j); - } - STRV_FOREACH(j, l) { _cleanup_free_ NDiscDNSSL *s = NULL; NDiscDNSSL *dnssl; @@ -742,6 +724,13 @@ static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { strcpy(NDISC_DNSSL_DOMAIN(s), *j); + if (lifetime_usec == 0) { + /* The entry is outdated. */ + free(set_remove(link->ndisc_dnssl, s)); + updated = true; + continue; + } + dnssl = set_get(link->ndisc_dnssl, s); if (dnssl) { dnssl->router = router; @@ -749,6 +738,13 @@ static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { continue; } + if (set_size(link->ndisc_dnssl) >= NDISC_DNSSL_MAX) { + if (!logged_about_too_many) + log_link_warning(link, "Too many DNSSL records per link. Only first %u records will be used.", NDISC_DNSSL_MAX); + logged_about_too_many = true; + continue; + } + s->router = router; s->lifetime_usec = lifetime_usec; @@ -814,6 +810,131 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { } } +static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { + bool updated = false; + NDiscDNSSL *dnssl; + NDiscRDNSS *rdnss; + Address *address; + Route *route; + int r = 0, k; + + assert(link); + + /* If an address or friends is already assigned, but not valid anymore, then refuse to update it, + * and let's immediately remove it. + * See RFC4862, section 5.5.3.e. But the following logic is deviated from RFC4862 by honoring all + * 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) { + if (route->source != NETWORK_CONFIG_SOURCE_NDISC) + continue; + + 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"); + } + + SET_FOREACH(address, link->addresses) { + if (address->source != NETWORK_CONFIG_SOURCE_NDISC) + continue; + + 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"); + } + + SET_FOREACH(rdnss, link->ndisc_rdnss) { + if (rdnss->lifetime_usec >= timestamp_usec) + continue; /* the DNS server is still valid */ + + free(set_remove(link->ndisc_rdnss, rdnss)); + updated = true; + } + + SET_FOREACH(dnssl, link->ndisc_dnssl) { + if (dnssl->lifetime_usec >= timestamp_usec) + continue; /* the DNS domain is still valid */ + + free(set_remove(link->ndisc_dnssl, dnssl)); + updated = true; + } + + if (updated) + link_dirty(link); + + return r; +} + +static int ndisc_setup_expire(Link *link); + +static int ndisc_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) { + Link *link = ASSERT_PTR(userdata); + usec_t now_usec; + + assert(link->manager); + + assert_se(sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec) >= 0); + + (void) ndisc_drop_outdated(link, now_usec); + (void) ndisc_setup_expire(link); + return 0; +} + +static int ndisc_setup_expire(Link *link) { + usec_t lifetime_usec = USEC_INFINITY; + NDiscDNSSL *dnssl; + NDiscRDNSS *rdnss; + Address *address; + Route *route; + int r; + + assert(link); + assert(link->manager); + + SET_FOREACH(route, link->routes) { + if (route->source != NETWORK_CONFIG_SOURCE_NDISC) + continue; + + if (!route_exists(route)) + continue; + + lifetime_usec = MIN(lifetime_usec, route->lifetime_usec); + } + + SET_FOREACH(address, link->addresses) { + if (address->source != NETWORK_CONFIG_SOURCE_NDISC) + continue; + + if (!address_exists(address)) + continue; + + lifetime_usec = MIN(lifetime_usec, address->lifetime_valid_usec); + } + + SET_FOREACH(rdnss, link->ndisc_rdnss) + lifetime_usec = MIN(lifetime_usec, rdnss->lifetime_usec); + + SET_FOREACH(dnssl, link->ndisc_dnssl) + lifetime_usec = MIN(lifetime_usec, dnssl->lifetime_usec); + + if (lifetime_usec == USEC_INFINITY) + return 0; + + r = event_reset_time(link->manager->event, &link->ndisc_expire, CLOCK_BOOTTIME, + lifetime_usec, 0, ndisc_expire_handler, link, 0, "ndisc-expiration", true); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to update expiration timer for ndisc: %m"); + + return 0; +} + static int ndisc_start_dhcp6_client(Link *link, sd_ndisc_router *rt) { int r; @@ -859,6 +980,7 @@ static int ndisc_start_dhcp6_client(Link *link, sd_ndisc_router *rt) { static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { struct in6_addr router; + usec_t timestamp_usec; int r; assert(link); @@ -880,6 +1002,14 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { return 0; } + r = sd_ndisc_router_get_timestamp(rt, CLOCK_BOOTTIME, ×tamp_usec); + if (r < 0) + return r; + + r = ndisc_drop_outdated(link, timestamp_usec); + if (r < 0) + return r; + r = ndisc_start_dhcp6_client(link, rt); if (r < 0) return r; @@ -892,6 +1022,10 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { if (r < 0) return r; + r = ndisc_setup_expire(link); + if (r < 0) + return r; + if (link->ndisc_messages == 0) link->ndisc_configured = true; else @@ -1032,6 +1166,15 @@ int link_request_ndisc(Link *link) { return 0; } +int ndisc_stop(Link *link) { + assert(link); + + link->ndisc_expire = sd_event_source_disable_unref(link->ndisc_expire); + + return sd_ndisc_stop(link->ndisc); +} + + void ndisc_vacuum(Link *link) { NDiscRDNSS *r; NDiscDNSSL *d; diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h index bdbe2f41f1d..b696db9df13 100644 --- a/src/network/networkd-ndisc.h +++ b/src/network/networkd-ndisc.h @@ -40,6 +40,7 @@ bool link_ipv6_accept_ra_enabled(Link *link); void network_adjust_ipv6_accept_ra(Network *network); int ndisc_start(Link *link); +int ndisc_stop(Link *link); void ndisc_vacuum(Link *link); void ndisc_flush(Link *link); diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index f1fba4ff425..514845f7c5c 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -781,6 +781,21 @@ int route_remove(Route *route) { return 0; } +int route_remove_and_drop(Route *route) { + if (!route) + return 0; + + route_cancel_request(route, NULL); + + if (route_exists(route)) + return route_remove(route); + + if (route->state == 0) + route_free(route); + + return 0; +} + static void manager_mark_routes(Manager *manager, bool foreign, const Link *except) { Route *route; Link *link; @@ -1411,7 +1426,7 @@ int link_request_route( route_netlink_handler_t netlink_handler, Request **ret) { - Route *existing; + Route *existing = NULL; int r; assert(link); @@ -1420,7 +1435,13 @@ int link_request_route( assert(route->source != NETWORK_CONFIG_SOURCE_FOREIGN); assert(!route_needs_convert(route)); - if (route_get(link->manager, link, route, &existing) < 0) { + (void) route_get(link->manager, link, route, &existing); + + if (route->lifetime_usec == 0) + /* The requested route is outdated. Let's remove it. */ + return route_remove_and_drop(existing); + + if (!existing) { _cleanup_(route_freep) Route *tmp = NULL; if (consume_object) diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index 071803cefbd..5fc76b17f85 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -85,6 +85,7 @@ int route_dup(const Route *src, Route **ret); int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg); int route_remove(Route *route); +int route_remove_and_drop(Route *route); int route_get(Manager *manager, Link *link, const Route *in, Route **ret); diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h index 84fdc99958f..e8c390196e1 100644 --- a/src/network/networkd-util.h +++ b/src/network/networkd-util.h @@ -36,12 +36,15 @@ typedef enum NetworkConfigState { NETWORK_CONFIG_STATE_REMOVING = 1 << 4, /* e.g. address_remove() is called, but no response is received yet */ } NetworkConfigState; -static inline usec_t sec16_to_usec(uint16_t sec, usec_t timestamp_usec) { - return sec == UINT16_MAX ? USEC_INFINITY : usec_add(timestamp_usec, sec * USEC_PER_SEC); +static inline usec_t sec_to_usec(uint32_t sec, usec_t timestamp_usec) { + return + sec == 0 ? 0 : + sec == UINT32_MAX ? USEC_INFINITY : + usec_add(timestamp_usec, sec * USEC_PER_SEC); } -static inline usec_t sec_to_usec(uint32_t sec, usec_t timestamp_usec) { - return sec == UINT32_MAX ? USEC_INFINITY : usec_add(timestamp_usec, sec * USEC_PER_SEC); +static inline usec_t sec16_to_usec(uint16_t sec, usec_t timestamp_usec) { + return sec_to_usec(sec == UINT16_MAX ? UINT32_MAX : (uint32_t) sec, timestamp_usec); } static inline uint32_t usec_to_sec(usec_t usec, usec_t now_usec) {