@@ -91,6 +91,11 @@ struct l_netconfig {
unsigned int orig_disable_ipv6;
uint8_t mac[ETH_ALEN];
struct l_timeout *ra_timeout;
+ enum {
+ NETCONFIG_V6_METHOD_UNSET,
+ NETCONFIG_V6_METHOD_DHCP,
+ NETCONFIG_V6_METHOD_SLAAC,
+ } v6_auto_method;
/* These objects, if not NULL, are owned by @addresses and @routes */
struct l_rtnl_address *v4_address;
@@ -677,7 +682,7 @@ static bool netconfig_match(const void *a, const void *b)
static bool netconfig_check_start_dhcp6(struct l_netconfig *nc)
{
/* Don't start DHCPv6 until we get an RA with the managed bit set */
- if (nc->ra_timeout)
+ if (nc->ra_timeout || nc->v6_auto_method != NETCONFIG_V6_METHOD_DHCP)
return true;
/* Don't start DHCPv6 while waiting for the link-local address */
@@ -698,6 +703,109 @@ static void netconfig_ra_timeout_cb(struct l_timeout *timeout, void *user_data)
netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_FAILED);
}
+static void netconfig_add_slaac_address(struct l_netconfig *nc,
+ const struct l_icmp6_router *r)
+{
+ unsigned int i;
+ const struct autoconf_prefix_info *longest = &r->ac_prefixes[0];
+ uint8_t addr[16];
+ char addr_str[INET6_ADDRSTRLEN];
+ uint32_t p, v;
+
+ /* Find the autoconfiguration prefix that offers the longest lifetime */
+ for (i = 1; i < r->n_ac_prefixes; i++)
+ if (r->ac_prefixes[i].preferred_lifetime >
+ longest->preferred_lifetime)
+ longest = &r->ac_prefixes[i];
+
+ memcpy(addr, longest->prefix, 8);
+ /* EUI-64-based Interface Identifier (RFC2464 Section 4) */
+ addr[ 8] = nc->mac[0] ^ 0x02;
+ addr[ 9] = nc->mac[1];
+ addr[10] = nc->mac[2];
+ addr[11] = 0xff;
+ addr[12] = 0xfe;
+ addr[13] = nc->mac[3];
+ addr[14] = nc->mac[4];
+ addr[15] = nc->mac[5];
+ inet_ntop(AF_INET6, addr, addr_str, sizeof(addr_str));
+ p = longest->preferred_lifetime;
+ v = longest->valid_lifetime;
+
+ nc->v6_address = l_rtnl_address_new(addr_str, 128);
+ l_rtnl_address_set_noprefixroute(nc->v6_address, true);
+
+ if (p != 0xffffffff || v != 0xffffffff) {
+ l_rtnl_address_set_lifetimes(nc->v6_address,
+ p != 0xffffffff ? p : 0,
+ v != 0xffffffff ? v : 0);
+ l_rtnl_address_set_expiry(nc->v6_address,
+ p != 0xffffffff ?
+ r->start_time + p * L_USEC_PER_SEC : 0,
+ v != 0xffffffff ?
+ r->start_time + v * L_USEC_PER_SEC : 0);
+ }
+
+ l_queue_push_tail(nc->addresses.current, nc->v6_address);
+ l_queue_push_tail(nc->addresses.added, nc->v6_address);
+ netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_CONFIGURE);
+
+ /* TODO: set a renew timeout */
+}
+
+static void netconfig_set_slaac_address_lifetimes(struct l_netconfig *nc,
+ const struct l_icmp6_router *r)
+{
+ const uint8_t *addr = l_rtnl_address_get_in_addr(nc->v6_address);
+ bool updated = false;
+ uint64_t p_expiry;
+ uint64_t v_expiry;
+ uint32_t remaining = 0xffffffff;
+ unsigned int i;
+
+ if (L_WARN_ON(!addr))
+ return;
+
+ l_rtnl_address_get_expiry(nc->v6_address, &p_expiry, &v_expiry);
+
+ if (v_expiry)
+ remaining = (v_expiry - r->start_time) / L_USEC_PER_SEC;
+
+ for (i = 0; i < r->n_ac_prefixes; i++) {
+ const struct autoconf_prefix_info *prefix = &r->ac_prefixes[i];
+ uint32_t p = prefix->preferred_lifetime;
+ uint32_t v = prefix->valid_lifetime;
+
+ if (memcmp(prefix->prefix, addr, 8))
+ continue;
+
+ /* RFC4862 Section 5.5.3 e) */
+ if (v < 120 * 60 && v < remaining)
+ v = 120 * 60; /* 2 hours */
+
+ l_rtnl_address_set_lifetimes(nc->v6_address,
+ p != 0xffffffff ? p : 0,
+ v != 0xffffffff ? v : 0);
+ p_expiry = p != 0xffffffff ? r->start_time + p * L_USEC_PER_SEC : 0;
+ v_expiry = v != 0xffffffff ? r->start_time + v * L_USEC_PER_SEC : 0;
+ l_rtnl_address_set_expiry(nc->v6_address, p_expiry, v_expiry);
+ updated = true;
+
+ /*
+ * TODO: modify the renew timeout.
+ *
+ * Also we probably want to apply a mechanism similar to that
+ * in netconfig_check_route_need_update() to avoid generating
+ * and UPDATED event for every RA that covers this prefix
+ * with constant lifetime values.
+ */
+ }
+
+ if (updated && !l_queue_find(nc->addresses.added, netconfig_match,
+ nc->v6_address))
+ l_queue_push_tail(nc->addresses.updated, nc->v6_address);
+}
+
static uint64_t now;
static bool netconfig_check_route_expired(void *data, void *user_data)
@@ -916,17 +1024,14 @@ static void netconfig_icmp6_event_handler(struct l_icmp6_client *client,
const struct l_icmp6_router *r;
struct netconfig_route_data *default_rd;
unsigned int i;
- bool first_ra = false;
if (event != L_ICMP6_CLIENT_EVENT_ROUTER_FOUND)
return;
r = event_data;
- if (nc->ra_timeout) {
- first_ra = true;
+ if (nc->ra_timeout)
l_timeout_remove(l_steal_ptr(nc->ra_timeout));
- }
netconfig_expire_routes(nc);
@@ -984,16 +1089,72 @@ process_nondefault_routes:
netconfig_remove_icmp6_route(nc, rd);
}
- /* See if we should start DHCPv6 now */
- if (first_ra) {
- if (!l_icmp6_router_get_managed(r) ||
- !netconfig_check_start_dhcp6(nc)) {
+ /*
+ * For lack of a better policy, select between DHCPv6 and SLAAC based
+ * on the first RA received. Prefer DHCPv6.
+ *
+ * Just like we currently only request one address in l_dhcp6_client,
+ * we only set up one address using SLAAC regardless of how many
+ * prefixes are available. Generate the address in the prefix that
+ * offers the longest preferred_lifetime.
+ */
+ if (nc->v6_auto_method == NETCONFIG_V6_METHOD_UNSET &&
+ l_icmp6_router_get_managed(r)) {
+ nc->v6_auto_method = NETCONFIG_V6_METHOD_DHCP;
+
+ if (!netconfig_check_start_dhcp6(nc)) {
netconfig_emit_event(nc, AF_INET6,
L_NETCONFIG_EVENT_FAILED);
return;
}
+
+ goto emit_event;
+ }
+
+ /*
+ * DHCP not available according to this router, check if any of the
+ * prefixes allow SLAAC.
+ */
+ if (nc->v6_auto_method == NETCONFIG_V6_METHOD_UNSET &&
+ r->n_ac_prefixes) {
+ nc->v6_auto_method = NETCONFIG_V6_METHOD_SLAAC;
+
+ /*
+ * The DAD for the link-local address may be still running
+ * but again we can generate the global address already and
+ * commit it to start in-kernel DAD for it.
+ *
+ * The global address alone should work for most uses. On
+ * the other hand since both the link-local address and the
+ * global address are based on the same MAC, there's some
+ * correlation between one failing DAD and the other
+ * failing DAD due to another host using the same address.
+ * As RFC4862 Section 5.4 notes we can't rely on that to
+ * skip DAD for one of the addresses.
+ */
+
+ netconfig_add_slaac_address(nc, r);
+ return;
}
+ /* Neither method seems available, fail */
+ if (nc->v6_auto_method == NETCONFIG_V6_METHOD_UNSET) {
+ netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_FAILED);
+ return;
+ }
+
+ /* DHCP already started or waiting for the LL address, nothing to do */
+ if (nc->v6_auto_method == NETCONFIG_V6_METHOD_DHCP)
+ goto emit_event;
+
+ /*
+ * Otherwise we already have a SLAAC address, just check if any of the
+ * auto-configuration prefixes in this RA covers our existing address
+ * and allows us to extend its lifetime.
+ */
+ netconfig_set_slaac_address_lifetimes(nc, r);
+
+emit_event:
/*
* Note: we may be emitting this before L_NETCONFIG_EVENT_CONFIGURE.
* We should probably instead save the affected routes in separate
@@ -1003,7 +1164,8 @@ process_nondefault_routes:
if (!l_queue_isempty(nc->routes.added) ||
!l_queue_isempty(nc->routes.updated) ||
!l_queue_isempty(nc->routes.removed) ||
- !l_queue_isempty(nc->routes.expired))
+ !l_queue_isempty(nc->routes.expired) ||
+ !l_queue_isempty(nc->addresses.updated))
netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_UPDATE);
}
@@ -1706,6 +1868,8 @@ configure_ipv6:
goto done;
}
+ netconfig->v6_auto_method = NETCONFIG_V6_METHOD_UNSET;
+
/*
* We only care about being on addr_wait_list if we're waiting for
* the link-local address for DHCP6. Add ourself to the list here