From 76c3246d8dd06d09139e18b5ab6f3789562b134d Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:59 +0200 Subject: [PATCH] networkd: Assign prefixes received via DHCPv6 When receiving one or more prefixes with variable length, assign a 64 bit long prefix for each link that has been configured for DHCPv6 prefix delegation and is not using DHCPv6 to fetch IPv6 adresses. Keep assigning prefixes with length 64 from each prefix received via DHCPv6 as long as there are prefixes left. If the number of prefixes available from a prefix received via DHCPv6 is smaller than the number of links, continue with the next delegated prefix, if any. Remember the prefixes used for each link by storing them in a hash and checking the hash each time a prefix is to be delegated. If an error occurs when assigning a prefix to a link, try assigning the prefix to another link. If the error occurs while updating the prefix, log the situation and continue delegating the rest of the prefixes. --- src/network/networkd-dhcp6.c | 171 +++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 59dcf28764b..4bb42919dfc 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -20,13 +20,18 @@ #include #include +#include "sd-radv.h" #include "sd-dhcp6-client.h" +#include "hashmap.h" #include "hostname-util.h" #include "network-internal.h" #include "networkd-link.h" #include "networkd-manager.h" +#include "siphash24.h" +#include "string-util.h" +#include "radv-internal.h" static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link); @@ -74,6 +79,166 @@ static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, return 0; } +static int dhcp6_pd_prefix_assign(Link *link, struct in6_addr *prefix, + uint8_t prefix_len, + uint32_t lifetime_preferred, + uint32_t lifetime_valid) { + sd_radv *radv = link->radv; + int r; + _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL; + + r = sd_radv_prefix_new(&p); + if (r < 0) + return r; + + r = sd_radv_prefix_set_prefix(p, prefix, prefix_len); + if (r < 0) + return r; + + r = sd_radv_prefix_set_preferred_lifetime(p, lifetime_preferred); + if (r < 0) + return r; + + r = sd_radv_prefix_set_valid_lifetime(p, lifetime_valid); + if (r < 0) + return r; + + r = sd_radv_stop(radv); + if (r < 0) + return r; + + r = sd_radv_add_prefix(radv, p, true); + if (r < 0 && r != -EEXIST) + return r; + + r = manager_dhcp6_prefix_add(link->manager, &p->opt.in6_addr, link); + if (r < 0) + return r; + + return sd_radv_start(radv); +} + +static Network *dhcp6_reset_pd_prefix_network(Link *link) { + assert(link); + assert(link->manager); + assert(link->manager->networks); + + return link->manager->networks; +} + +static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i, + struct in6_addr *pd_prefix, + uint8_t pd_prefix_len, + uint32_t lifetime_preferred, + uint32_t lifetime_valid) { + Link *link; + Manager *manager = dhcp6_link->manager; + union in_addr_union prefix; + uint8_t n_prefixes, n_used = 0; + _cleanup_free_ char *buf = NULL; + int r; + + assert(manager); + assert(pd_prefix_len <= 64); + + prefix.in6 = *pd_prefix; + + r = in_addr_mask(AF_INET6, &prefix, pd_prefix_len); + if (r < 0) + return r; + + n_prefixes = 1 << (64 - pd_prefix_len); + + (void) in_addr_to_string(AF_INET6, &prefix, &buf); + log_link_debug(dhcp6_link, "Assigning up to %u prefixes from %s/%u", + n_prefixes, strnull(buf), pd_prefix_len); + + while (hashmap_iterate(manager->links, i, (void **)&link, NULL)) { + Link *assigned_link; + + if (n_used == n_prefixes) { + log_link_debug(dhcp6_link, "Assigned %u/%u prefixes from %s/%u", + n_used, n_prefixes, strnull(buf), pd_prefix_len); + + return -EAGAIN; + } + + if (link == dhcp6_link) + continue; + + if (!dhcp6_verify_link(link)) + continue; + + assigned_link = manager_dhcp6_prefix_get(manager, &prefix.in6); + if (assigned_link != NULL && assigned_link != link) + continue; + + r = dhcp6_pd_prefix_assign(link, &prefix.in6, 64, + lifetime_preferred, lifetime_valid); + if (r < 0) { + log_link_error_errno(link, r, "Unable to %s prefix %s/%u for link: %m", + assigned_link ? "update": "assign", + strnull(buf), pd_prefix_len); + + if (assigned_link == NULL) + continue; + + } else + log_link_debug(link, "Assigned prefix %u/%u %s/64 to link", + n_used + 1, n_prefixes, strnull(buf)); + + n_used++; + + r = in_addr_prefix_next(AF_INET6, &prefix, pd_prefix_len); + if (r < 0 && n_used < n_prefixes) + return r; + } + + return n_used; +} + +static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) { + int r; + sd_dhcp6_lease *lease; + struct in6_addr pd_prefix; + uint8_t pd_prefix_len; + uint32_t lifetime_preferred, lifetime_valid; + _cleanup_free_ char *buf = NULL; + Iterator i = ITERATOR_FIRST; + + r = sd_dhcp6_client_get_lease(client, &lease); + if (r < 0) + return r; + + (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &pd_prefix, &buf); + + dhcp6_reset_pd_prefix_network(link); + sd_dhcp6_lease_reset_pd_prefix_iter(lease); + + while (sd_dhcp6_lease_get_pd(lease, &pd_prefix, &pd_prefix_len, + &lifetime_preferred, + &lifetime_valid) >= 0) { + + if (pd_prefix_len > 64) { + log_link_debug(link, "PD Prefix length > 64, ignoring prefix %s/%u", + strnull(buf), pd_prefix_len); + continue; + } + + r = dhcp6_pd_prefix_distribute(link, &i, &pd_prefix, + pd_prefix_len, + lifetime_preferred, + lifetime_valid); + if (r < 0 && r != -EAGAIN) + return r; + + if (r >= 0) + i = ITERATOR_FIRST; + } + + return 0; +} + static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { _cleanup_link_unref_ Link *link = userdata; @@ -178,6 +343,8 @@ static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) { if (sd_dhcp6_client_get_lease(client, NULL) >= 0) log_link_warning(link, "DHCPv6 lease lost"); + (void) manager_dhcp6_prefix_remove_all(link->manager, link); + link->dhcp6_configured = false; break; @@ -188,6 +355,10 @@ static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) { return; } + r = dhcp6_lease_pd_prefix_acquired(client, link); + if (r < 0) + log_link_debug(link, "DHCPv6 did not receive prefixes to delegate"); + _fallthrough_; case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST: r = dhcp6_lease_information_acquired(client, link);