Skip to content

Commit

Permalink
networkd: ndisc - handle router advertisement in userspace
Browse files Browse the repository at this point in the history
Router Discovery is a core part of IPv6, which by default is handled by the kernel.
However, the kernel implementation is meant as a fall-back, and to fully support
the protocol a userspace implementation is desired.

The protocol essentially listens for Router Advertisement packets from routers
on the local link and use these to configure the client automatically. The four
main pieces of information are: what kind (if any) of DHCPv6 configuration should
be performed; a default gateway; the prefixes that should be considered to be on
the local link; and the prefixes with which we can preform SLAAC in order to pick
a global IPv6 address.

A lot of additional information is also available, which we do not yet fully
support, but which will eventually allow us to avoid the need for DHCPv6 in the
common case.

Short-term, the reason for wanting this is in userspace was the desire to fully
track all the addresses on links we manage, and that is not possible for addresses
managed by the kernel (as the kernel does not expose to us the fact that it
manages these addresses). Moreover, we would like to support stable privacy
addresses, which will soon be mandated and the legacy MAC-based global addresses
deprecated, to do this well we need to handle the generation in userspace. Lastly,
more long-term we wish to support more RA options than what the kernel exposes.
  • Loading branch information
teg committed Nov 11, 2015
1 parent f5a8c43 commit 3b015d4
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 26 deletions.
11 changes: 5 additions & 6 deletions src/libsystemd-network/sd-ndisc.c
Original file line number Diff line number Diff line change
Expand Up @@ -519,23 +519,22 @@ static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t r
nd->state = NDISC_STATE_ADVERTISMENT_LISTEN;

stateful = ra->nd_ra_flags_reserved & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER);
switch (ra->nd_ra_flags_reserved & ND_RA_FLAG_PREF >> 3) {
pref = ra->nd_ra_flags_reserved & ND_RA_FLAG_PREF >> 3;

switch (pref) {
case ND_RA_FLAG_PREF_LOW:
pref = -1;
break;
case ND_RA_FLAG_PREF_HIGH:
pref = 1;
break;
default:
pref = 0;
pref = ND_RA_FLAG_PREF_MEDIUM;
break;
}

lifetime = be16toh(ra->nd_ra_router_lifetime);

log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %u sec",
stateful & ND_RA_FLAG_MANAGED ? "MANAGED" : stateful & ND_RA_FLAG_OTHER ? "OTHER" : "none",
pref == 1 ? "high" : pref == -1 ? "low" : "medium",
pref == ND_RA_FLAG_PREF_HIGH ? "high" : pref == ND_RA_FLAG_PREF_LOW ? "low" : "medium",
lifetime);

r = ndisc_ra_parse(nd, ra, len);
Expand Down
26 changes: 8 additions & 18 deletions src/network/networkd-link.c
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,9 @@ void link_check_ready(Link *link) {
!link->dhcp4_configured && !link->dhcp6_configured))
return;

if (link_ipv6_accept_ra_enabled(link) && !link->ndisc_configured)
return;

SET_FOREACH(a, link->addresses, i)
if (!address_is_ready(a))
return;
Expand Down Expand Up @@ -1915,7 +1918,7 @@ static int link_set_ipv6_privacy_extensions(Link *link) {
}

static int link_set_ipv6_accept_ra(Link *link) {
const char *p = NULL, *v = NULL;
const char *p = NULL;
int r;

/* Make this a NOP if IPv6 is not available */
Expand All @@ -1925,29 +1928,16 @@ static int link_set_ipv6_accept_ra(Link *link) {
if (link->flags & IFF_LOOPBACK)
return 0;

/* If unset use system default (enabled if local forwarding is disabled.
* disabled if local forwarding is enabled).
* If set, ignore or enforce RA independent of local forwarding state.
*/
if (link->network->ipv6_accept_ra < 0)
/* default to accept RA if ip_forward is disabled and ignore RA if ip_forward is enabled */
v = "1";
else if (link->network->ipv6_accept_ra > 0)
/* "2" means accept RA even if ip_forward is enabled */
v = "2";
else
/* "0" means ignore RA */
v = "0";

p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/accept_ra");

r = write_string_file(p, v, 0);
/* we handle router advertisments ourselves, tell the kernel to GTFO */
r = write_string_file(p, "0", 0);
if (r < 0) {
/* If the right value is set anyway, don't complain */
if (verify_one_line_file(p, v) > 0)
if (verify_one_line_file(p, "0") > 0)
return 0;

log_link_warning_errno(link, r, "Cannot configure IPv6 accept_ra for interface: %m");
log_link_warning_errno(link, r, "Cannot disable kernel IPv6 accept_ra for interface: %m");
}

return 0;
Expand Down
2 changes: 2 additions & 0 deletions src/network/networkd-link.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ struct Link {
unsigned dhcp4_messages;
bool dhcp4_configured;
bool dhcp6_configured;
unsigned ndisc_messages;
bool ndisc_configured;

sd_ipv4ll *ipv4ll;
bool ipv4ll_address;
Expand Down
148 changes: 146 additions & 2 deletions src/network/networkd-ndisc.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,129 @@

#include "networkd-link.h"

static int ndisc_netlink_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
_cleanup_link_unref_ Link *link = userdata;
int r;

assert(link);
assert(link->ndisc_messages > 0);

link->ndisc_messages --;

r = sd_netlink_message_get_errno(m);
if (r < 0 && r != -EEXIST) {
log_link_error_errno(link, r, "Could not set NDisc route or address: %m");
link_enter_failed(link);
}

if (link->ndisc_messages == 0) {
link->ndisc_configured = true;
link_check_ready(link);
}

return 1;
}

static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen,
unsigned lifetime_preferred, unsigned lifetime_valid, void *userdata) {
_cleanup_address_free_ Address *address = NULL;
Link *link = userdata;
usec_t time_now;
int r;

assert(nd);
assert(link);
assert(link->network);

if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
return;

r = address_new(&address);
if (r < 0) {
log_link_error_errno(link, r, "Could not allocate address: %m");
return;
}

assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);

address->family = AF_INET6;
address->in_addr.in6 = *prefix;
if (in_addr_is_null(AF_INET6, (const union in_addr_union *) &link->network->ipv6_token) == 0)
memcpy(&address->in_addr.in6 + 8, &link->network->ipv6_token + 8, 8);
else {
/* see RFC4291 section 2.5.1 */
address->in_addr.in6.__in6_u.__u6_addr8[8] = link->mac.ether_addr_octet[0];
address->in_addr.in6.__in6_u.__u6_addr8[8] ^= 1 << 1;
address->in_addr.in6.__in6_u.__u6_addr8[9] = link->mac.ether_addr_octet[1];
address->in_addr.in6.__in6_u.__u6_addr8[10] = link->mac.ether_addr_octet[2];
address->in_addr.in6.__in6_u.__u6_addr8[11] = 0xff;
address->in_addr.in6.__in6_u.__u6_addr8[12] = 0xfe;
address->in_addr.in6.__in6_u.__u6_addr8[13] = link->mac.ether_addr_octet[3];
address->in_addr.in6.__in6_u.__u6_addr8[14] = link->mac.ether_addr_octet[4];
address->in_addr.in6.__in6_u.__u6_addr8[15] = link->mac.ether_addr_octet[5];
}
address->prefixlen = prefixlen;
address->flags = IFA_F_NOPREFIXROUTE;
address->cinfo.ifa_prefered = lifetime_preferred;
address->cinfo.ifa_valid = lifetime_valid;

r = address_configure(address, link, ndisc_netlink_handler, true);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set SLAAC address: %m");
link_enter_failed(link);
return;
}

link->ndisc_messages ++;
}

static void ndisc_prefix_onlink_handler(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, unsigned lifetime, void *userdata) {
_cleanup_route_free_ Route *route = NULL;
Link *link = userdata;
usec_t time_now;
int r;

assert(nd);
assert(link);

if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
return;

r = route_new(&route);
if (r < 0) {
log_link_error_errno(link, r, "Could not allocate route: %m");
return;
}

assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);

route->family = AF_INET6;
route->table = RT_TABLE_MAIN;
route->protocol = RTPROT_RA;
route->flags = RTM_F_PREFIX;
route->dst.in6 = *prefix;
route->dst_prefixlen = prefixlen;
route->lifetime = time_now + lifetime * USEC_PER_SEC;

r = route_configure(route, link, ndisc_netlink_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set prefix route: %m");
link_enter_failed(link);
return;
}

link->ndisc_messages ++;
}

static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata) {
_cleanup_route_free_ Route *route = NULL;
Link *link = userdata;
usec_t time_now;
int r;

assert(link);
assert(link->network);
assert(link->manager);

if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
return;
Expand All @@ -45,6 +162,33 @@ static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_a
if (r < 0 && r != -EALREADY)
log_link_warning_errno(link, r, "Starting DHCPv6 client on NDisc request failed: %m");
}

if (!gateway)
return;

r = route_new(&route);
if (r < 0) {
log_link_error_errno(link, r, "Could not allocate route: %m");
return;
}

assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);

route->family = AF_INET6;
route->table = RT_TABLE_MAIN;
route->protocol = RTPROT_RA;
route->pref = pref;
route->gw.in6 = *gateway;
route->lifetime = time_now + lifetime * USEC_PER_SEC;

r = route_configure(route, link, ndisc_netlink_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set default route: %m");
link_enter_failed(link);
return;
}

link->ndisc_messages ++;
}

static void ndisc_handler(sd_ndisc *nd, int event, void *userdata) {
Expand Down Expand Up @@ -94,8 +238,8 @@ int ndisc_configure(Link *link) {

r = sd_ndisc_set_callback(link->ndisc_router_discovery,
ndisc_router_handler,
NULL,
NULL,
ndisc_prefix_onlink_handler,
ndisc_prefix_autonomous_handler,
ndisc_handler,
link);

Expand Down
8 changes: 8 additions & 0 deletions src/network/networkd-route.c
Original file line number Diff line number Diff line change
Expand Up @@ -494,10 +494,18 @@ int route_configure(Route *route, Link *link,
if (r < 0)
return log_error_errno(r, "Could not set scope: %m");

r = sd_rtnl_message_route_set_flags(req, route->flags);
if (r < 0)
return log_error_errno(r, "Colud not set flags: %m");

r = sd_netlink_message_append_u32(req, RTA_PRIORITY, route->priority);
if (r < 0)
return log_error_errno(r, "Could not append RTA_PRIORITY attribute: %m");

r = sd_netlink_message_append_u8(req, RTA_PREF, route->pref);
if (r < 0)
return log_error_errno(r, "Could not append RTA_PREF attribute: %m");

r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex);
if (r < 0)
return log_error_errno(r, "Could not append RTA_OIF attribute: %m");
Expand Down
2 changes: 2 additions & 0 deletions src/network/networkd-route.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ struct Route {
unsigned char tos;
uint32_t priority; /* note that ip(8) calls this 'metric' */
unsigned char table;
unsigned char pref;
unsigned flags;

union in_addr_union gw;
union in_addr_union dst;
Expand Down

0 comments on commit 3b015d4

Please sign in to comment.