networkd: Add support for setting a preferred subnet id for IPv6 PD leases

This allows users to configure a subnet id that should be used instead
of automatically (sequentially) assigned subnets. The previous attempt
had the downside that the subnet id would not be the same between
networkd restarts. In some setups it is desirable to have predictable
subnet ids across restarts of services and systems.

The code for the assignment had to be broken up into two pieces. One of
them is the old (sequential) assignment of prefixes and the other is the
new assignment based on configured subnet ids. The new assignment code
has to be executed first and has to be taken into account when (later
on) allocating the "old" subnets from the same pool.

Instead of having one iteration through the links we are now trying to
allocate a prefix for every link on every delegated prefix, unless they
received an assignment in a previous iteration.
This commit is contained in:
Andreas Rammhold 2020-05-14 00:54:37 +02:00
parent 171f625b9e
commit 02e9e34bd9
No known key found for this signature in database
GPG Key ID: E432E410B5E48C86
8 changed files with 230 additions and 38 deletions

View File

@ -701,6 +701,16 @@
sections for more configuration options.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>IPv6PDSubnetId=</varname></term>
<listitem><para>Configure a specific subnet ID on the interface from a (previously) received prefix delegation.
You can either set "auto" (the default) or a specific subnet ID
(as defined in <ulink url="https://tools.ietf.org/html/rfc4291#section-2.5.4">RFC 4291</ulink>, section 2.5.4),
in which case the allowed value is hexadecimal, from 0 to 0x7fffffffffffffff inclusive.
This option is only effective when used together with <varname>IPv6PrefixDelegation=</varname>
and the corresponding configuration on the upstream interface.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>IPv6MTUBytes=</varname></term>
<listitem><para>Configures IPv6 maximum transmission unit (MTU).

View File

@ -25,6 +25,7 @@ static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link);
static Link *dhcp6_prefix_get(Manager *m, struct in6_addr *addr);
static int dhcp6_prefix_add(Manager *m, struct in6_addr *addr, Link *link);
static int dhcp6_prefix_remove_all(Manager *m, Link *link);
static bool dhcp6_link_has_dhcpv6_prefix(Link *link);
static bool dhcp6_get_prefix_delegation(Link *link) {
if (!link->network)
@ -35,6 +36,97 @@ static bool dhcp6_get_prefix_delegation(Link *link) {
RADV_PREFIX_DELEGATION_BOTH);
}
static bool dhcp6_has_preferred_subnet_id(Link *link) {
if (!link->network)
return false;
return link->network->router_prefix_subnet_id >= 0;
}
static int dhcp6_get_preferred_delegated_prefix(
Manager* manager,
Link *link,
const struct in6_addr *pd_prefix,
uint8_t pd_prefix_len,
struct in6_addr *ret_addr) {
int r;
union in_addr_union pd_prefix_union = {
.in6 = *pd_prefix,
};
int64_t subnet_id = link->network->router_prefix_subnet_id;
assert(pd_prefix_len <= 64);
uint8_t prefix_bits = 64 - pd_prefix_len;
uint64_t n_prefixes = UINT64_C(1) << prefix_bits;
_cleanup_free_ char *assigned_buf = NULL;
/* We start off with the original PD prefix we have been assigned and
* iterate from there */
union in_addr_union prefix = {
.in6 = *pd_prefix,
};
if (subnet_id >= 0) {
/* If the link has a preference for a particular subnet id try to allocate that */
if ((uint64_t)subnet_id >= n_prefixes)
return log_link_debug_errno(link,
SYNTHETIC_ERRNO(ERANGE),
"subnet id %" PRIi64 " is out of range. Only have %" PRIu64 " subnets.",
subnet_id,
n_prefixes);
r = in_addr_prefix_nth(AF_INET6, &prefix, 64, subnet_id);
if (r < 0)
return log_link_debug_errno(link,
r,
"subnet id %" PRIi64 " is out of range. Only have %" PRIu64 " subnets.",
subnet_id,
n_prefixes);
/* Verify that the prefix we did calculate fits in the pd prefix.
* This should not fail as we checked the prefix size beforehand */
assert_se(in_addr_prefix_covers(AF_INET6, &pd_prefix_union, pd_prefix_len, &prefix) > 0);
Link* assigned_link = dhcp6_prefix_get(manager, &prefix.in6);
(void) in_addr_to_string(AF_INET6, &prefix, &assigned_buf);
if (assigned_link && assigned_link != link)
return log_link_error_errno(link, SYNTHETIC_ERRNO(EAGAIN),
"The requested prefix %s is already assigned to another link: %s",
strnull(assigned_buf),
strnull(assigned_link->ifname));
*ret_addr = prefix.in6;
log_link_debug(link, "The requested prefix %s is available. Using it.",
strnull(assigned_buf));
return 0;
} else {
for (uint64_t n = 0; n < n_prefixes; n++) {
/* if we do not have an allocation preference just iterate
* through the address space and return the first free prefix. */
Link* assigned_link = dhcp6_prefix_get(manager, &prefix.in6);
if (!assigned_link || assigned_link == link) {
*ret_addr = prefix.in6;
return 0;
}
r = in_addr_prefix_next(AF_INET6, &prefix, 64);
if (r < 0)
return log_link_error_errno(link,
r,
"Can't allocate another prefix. Out of address space?");
}
log_link_warning(link, "Couldn't find a suitable prefix. Ran out of address space.");
}
return -ERANGE;
}
static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) {
Manager *manager;
Link *l;
@ -165,24 +257,27 @@ int dhcp6_lease_pd_prefix_lost(sd_dhcp6_client *client, Link* link) {
return 0;
}
static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
static int dhcp6_pd_prefix_distribute(Link *dhcp6_link,
struct in6_addr *pd_prefix,
uint8_t pd_prefix_len,
uint32_t lifetime_preferred,
uint32_t lifetime_valid) {
uint32_t lifetime_valid,
bool assign_preferred_subnet_id) {
Iterator i;
Link *link;
Manager *manager = dhcp6_link->manager;
union in_addr_union prefix;
uint64_t n_prefixes, n_used = 0;
union in_addr_union prefix = {
.in6 = *pd_prefix,
};
uint64_t n_prefixes;
_cleanup_free_ char *buf = NULL;
_cleanup_free_ char *assigned_buf = NULL;
int r;
bool pool_depleted = false;
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;
@ -193,15 +288,8 @@ static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
log_link_debug(dhcp6_link, "Assigning up to %" PRIu64 " 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 %" PRIu64 "/%" PRIu64 " prefixes from %s/%u",
n_used, n_prefixes, strnull(buf), pd_prefix_len);
return -EAGAIN;
}
HASHMAP_FOREACH(link, manager->links, i) {
union in_addr_union assigned_prefix;
if (link == dhcp6_link)
continue;
@ -209,35 +297,42 @@ static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
if (!dhcp6_get_prefix_delegation(link))
continue;
assigned_link = dhcp6_prefix_get(manager, &prefix.in6);
if (assigned_link && assigned_link != link)
if (dhcp6_link_has_dhcpv6_prefix(link))
continue;
(void) in_addr_to_string(AF_INET6, &prefix, &assigned_buf);
r = dhcp6_pd_prefix_assign(link, &prefix.in6, 64,
if (assign_preferred_subnet_id != dhcp6_has_preferred_subnet_id(link))
continue;
r = dhcp6_get_preferred_delegated_prefix(manager, link, &prefix.in6, pd_prefix_len,
&assigned_prefix.in6);
if (assign_preferred_subnet_id && r == -EAGAIN) {
/* A link has a preferred subnet_id but that one is
* already taken by another link. Now all the remaining
* links will also not obtain a prefix. */
pool_depleted = true;
continue;
} else if (r < 0)
return r;
(void) in_addr_to_string(AF_INET6, &assigned_prefix, &assigned_buf);
r = dhcp6_pd_prefix_assign(link, &assigned_prefix.in6, 64,
lifetime_preferred, lifetime_valid);
if (r < 0) {
log_link_error_errno(link, r, "Unable to %s prefix %s/64 from %s/%u for link: %m",
assigned_link ? "update": "assign",
log_link_error_errno(link, r, "Unable to assign/update prefix %s/64 from %s/%u for link: %m",
strnull(assigned_buf),
strnull(buf), pd_prefix_len);
if (!assigned_link)
continue;
} else
log_link_debug(link, "Assigned prefix %" PRIu64 "/%" PRIu64 " %s/64 from %s/%u to link",
n_used + 1, n_prefixes,
log_link_debug(link, "Assigned prefix %s/64 from %s/%u to link",
strnull(assigned_buf),
strnull(buf), pd_prefix_len);
n_used++;
r = in_addr_prefix_next(AF_INET6, &prefix, 64);
if (r < 0 && n_used < n_prefixes)
return r;
}
/* If one of the link requests couldn't be fulfilled, signal that we
should try again with another prefix. */
if (pool_depleted)
return -EAGAIN;
return 0;
}
@ -262,7 +357,6 @@ static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
union in_addr_union pd_prefix;
uint8_t pd_prefix_len;
uint32_t lifetime_preferred, lifetime_valid;
Iterator i = ITERATOR_FIRST;
r = sd_dhcp6_client_get_lease(client, &lease);
if (r < 0)
@ -314,15 +408,47 @@ static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
} else
log_link_debug(link, "Not adding a blocking route since distributed prefix is /64");
r = dhcp6_pd_prefix_distribute(link, &i, &pd_prefix.in6,
/* We are doing prefix allocation in two steps:
* 1. all those links that have a preferred subnet id will be assigned their subnet
* 2. all those links that remain will receive prefixes in sequential
* order. Prefixes that were previously already allocated to another
* link will be skipped.
* If a subnet id request couldn't be fullfilled the failure will be logged (as error)
* and no further attempts at obtaining a prefix will be made.
* The assignment has to be split in two phases since subnet id
* preferences should be honored. Meaning that any subnet id should be
* handed out to the requesting link and not to some link that didn't
* specify any preference. */
r = dhcp6_pd_prefix_distribute(link, &pd_prefix.in6,
pd_prefix_len,
lifetime_preferred,
lifetime_valid);
lifetime_valid,
true);
if (r < 0 && r != -EAGAIN)
return r;
if (r >= 0)
i = ITERATOR_FIRST;
/* if r == -EAGAIN then the allocation failed because we ran
* out of addresses for the preferred subnet id's. This doesn't
* mean we can't fullfill other prefix requests.
*
* Since we do not have dedicated lists of links that request
* specific subnet id's and those that accept any prefix we
* *must* reset the iterator to the start as otherwise some
* links might not get their requested prefix. */
r = dhcp6_pd_prefix_distribute(link, &pd_prefix.in6,
pd_prefix_len,
lifetime_preferred,
lifetime_valid,
false);
if (r < 0 && r != -EAGAIN)
return r;
/* If the prefix distribution did return -EAGAIN we will try to
* fullfill those with the next available pd delegated prefix. */
}
return 0;
@ -842,3 +968,17 @@ static int dhcp6_prefix_remove_all(Manager *m, Link *link) {
return 0;
}
static bool dhcp6_link_has_dhcpv6_prefix(Link *link) {
Iterator i;
Link *l;
assert(link);
assert(link->manager);
HASHMAP_FOREACH(l, link->manager->dhcp6_prefixes, i)
if (link == l)
return true;
return false;
}

View File

@ -243,6 +243,7 @@ BridgeVLAN.PVID, config_parse_brvlan_pvid,
BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0
BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0
Network.IPv6PrefixDelegation, config_parse_router_prefix_delegation, 0, 0
Network.IPv6PDSubnetId, config_parse_router_prefix_subnet_id, 0, 0
IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec)
IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information)

View File

@ -414,6 +414,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.dhcp_server_emit_router = true,
.dhcp_server_emit_timezone = true,
.router_prefix_subnet_id = -1,
.router_emit_dns = true,
.router_emit_domains = true,

View File

@ -173,6 +173,7 @@ struct Network {
/* IPv6 prefix delegation support */
RADVPrefixDelegation router_prefix_delegation;
int64_t router_prefix_subnet_id;
usec_t router_lifetime_usec;
uint8_t router_preference;
bool router_managed;

View File

@ -864,3 +864,40 @@ int config_parse_router_preference(const char *unit,
return 0;
}
int config_parse_router_prefix_subnet_id(const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
uint64_t t;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if (isempty(rvalue) || streq(rvalue, "auto")) {
network->router_prefix_subnet_id = -1;
return 0;
}
r = safe_atoux64(rvalue, &t);
if (r < 0 || t > INT64_MAX) {
log_syntax(unit, LOG_ERR, filename, line, r,
"Subnet id '%s' is invalid, ignoring assignment.",
rvalue);
return 0;
}
network->router_prefix_subnet_id = (int64_t)t;
return 0;
}

View File

@ -58,6 +58,7 @@ RADVPrefixDelegation radv_prefix_delegation_from_string(const char *s) _pure_;
CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_delegation);
CONFIG_PARSER_PROTOTYPE(config_parse_router_preference);
CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_subnet_id);
CONFIG_PARSER_PROTOTYPE(config_parse_prefix);
CONFIG_PARSER_PROTOTYPE(config_parse_prefix_flags);
CONFIG_PARSER_PROTOTYPE(config_parse_prefix_lifetime);

View File

@ -179,6 +179,7 @@ NTP=
DHCP=
Domains=
IPv6PrefixDelegation=
IPv6PDSubnetId=
VLAN=
DHCPServer=
BindCarrier=