Merge pull request #24900 from yuwata/network-ndisc-drop-outdated

network: ndisc: fix zero lifetime handling
This commit is contained in:
Lennart Poettering
2022-10-04 18:03:25 +02:00
committed by GitHub
9 changed files with 247 additions and 58 deletions

View File

@@ -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)

View File

@@ -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);

View File

@@ -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");

View File

@@ -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;

View File

@@ -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, &timestamp_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, &timestamp_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, &timestamp_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;

View File

@@ -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);

View File

@@ -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)

View File

@@ -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);

View File

@@ -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) {