network: Don't send RA with zero router lifetime when restarting radv

While investigating https://github.com/systemd/systemd/issues/16356, I
discovered that networkd stops the radv service before adding or updating
prefixes and then starts it again.  This causes networkd to send an RA with
a router lifetime of zero, causing the routes to flap on systems receiving
the RA for a fraction of a second before radv is started again and proper
RAs are sent.  That has the potential to cause issues with latency-sensitive
traffic like gaming or VoIP.  This patch adds a boolean argument to the
sd_radv_stop() function to control this behavior.  The zero lifetime RA is
still sent whenever radv is actually being stopped, but when it is being
restarted for a prefix update (from networkd-dhcp6.c), the final RA is no
longer sent to avoid the route flapping.
This commit is contained in:
Michael Marley 2020-07-05 06:46:27 -04:00 committed by Yu Watanabe
parent c82012605b
commit d469cea3bd
5 changed files with 16 additions and 11 deletions

View File

@ -339,12 +339,12 @@ static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
return 0;
fail:
sd_radv_stop(ra);
sd_radv_stop(ra, true);
return 0;
}
_public_ int sd_radv_stop(sd_radv *ra) {
_public_ int sd_radv_stop(sd_radv *ra, bool zero_router_lifetime) {
int r;
assert_return(ra, -EINVAL);
@ -354,11 +354,15 @@ _public_ int sd_radv_stop(sd_radv *ra) {
log_radv("Stopping IPv6 Router Advertisement daemon");
/* RFC 4861, Section 6.2.5, send at least one Router Advertisement
with zero lifetime */
r = radv_send(ra, NULL, 0);
if (r < 0)
log_radv_errno(r, "Unable to send last Router Advertisement with router lifetime set to zero: %m");
if (zero_router_lifetime) {
/* RFC 4861, Section 6.2.5, send at least one Router Advertisement
with zero lifetime */
r = radv_send(ra, NULL, 0);
if (r < 0)
log_radv_errno(r, "Unable to send last Router Advertisement with router lifetime set to zero: %m");
else
log_radv("Sent last Router Advertisement with router lifetime set to zero");
}
radv_reset(ra);
ra->fd = safe_close(ra->fd);

View File

@ -284,7 +284,7 @@ static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdat
return 0;
}
assert_se(sd_radv_stop(ra) >= 0);
assert_se(sd_radv_stop(ra, true) >= 0);
test_stopped = true;
return 0;

View File

@ -188,7 +188,7 @@ static int dhcp6_pd_prefix_assign(Link *link, struct in6_addr *prefix,
if (r < 0)
return r;
r = sd_radv_stop(radv);
r = sd_radv_stop(radv, false);
if (r < 0)
return r;

View File

@ -840,7 +840,7 @@ int link_stop_clients(Link *link, bool may_keep_dhcp) {
}
if (link->radv) {
k = sd_radv_stop(link->radv);
k = sd_radv_stop(link->radv, true);
if (k < 0)
r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Advertisement: %m");
}

View File

@ -22,6 +22,7 @@
#include <inttypes.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <sys/types.h>
#include "_sd-common.h"
@ -49,7 +50,7 @@ int sd_radv_detach_event(sd_radv *nd);
sd_event *sd_radv_get_event(sd_radv *ra);
int sd_radv_start(sd_radv *ra);
int sd_radv_stop(sd_radv *ra);
int sd_radv_stop(sd_radv *ra, bool zero_router_lifetime);
int sd_radv_set_ifindex(sd_radv *ra, int interface_index);
int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr);