diff options
author | Andrew Zaborowski <andrew.zaborowski@intel.com> | 2022-09-19 15:31:04 +0200 |
---|---|---|
committer | Denis Kenzior <denkenz@gmail.com> | 2022-09-19 13:39:04 -0500 |
commit | 6adf0f5db4553568b8815240132f7ef890b41fe7 (patch) | |
tree | 47607fd682fac8a304cd016242571164e71443ea | |
parent | cb1c910ed0541fcb142c9b57f0ad9f2dc93e9964 (diff) |
netconfig: Create SLAAC address
If the Router Advertisement doesn't indicate DHCPv6 is available and
includes prefixes for address auto-configuration, fall back to using
those to generate a single IPv6 address. Other settings such as DNS
are not supported in this mode yet and there's no renewal timer for when
the address lifetime is finite.
-rw-r--r-- | ell/netconfig.c | 184 |
1 files changed, 174 insertions, 10 deletions
diff --git a/ell/netconfig.c b/ell/netconfig.c index 3ac03193..3336b30d 100644 --- a/ell/netconfig.c +++ b/ell/netconfig.c @@ -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 |