From 863b99cdd90c74415d00b01c00b3eb3fac6656a0 Mon Sep 17 00:00:00 2001 From: Andreas Rammhold Date: Thu, 14 May 2020 00:52:18 +0200 Subject: [PATCH 1/3] in-addr-util: introduce in_addr_prefix_nth --- src/basic/in-addr-util.c | 86 +++++++++++++++++++++++++++++++++++++ src/basic/in-addr-util.h | 1 + src/test/test-socket-util.c | 40 +++++++++++++++++ 3 files changed, 127 insertions(+) diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index bfe855fb4b..d1e457b647 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -226,6 +226,92 @@ int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen) return -EAFNOSUPPORT; } +/* + * Calculates the nth prefix of size prefixlen starting from the address denoted by u. + * + * On success 1 will be returned and the calculated prefix will be available in + * u. In the case nth == 0 the input will be left unchanged and 1 will be returned. + * In case the calculation cannot be performed (invalid prefix length, + * overflows would occur) -ERANGE is returned. If the address family given isn't + * supported -EAFNOSUPPORT will be returned. + * + * + * Examples: + * - in_addr_prefix_nth(AF_INET, 192.168.0.0, 24, 2), returns 1, writes 192.168.2.0 to u + * - in_addr_prefix_nth(AF_INET, 192.168.0.0, 24, 0), returns 1, no data written + * - in_addr_prefix_nth(AF_INET, 255.255.255.0, 24, 1), returns -ERANGE, no data written + * - in_addr_prefix_nth(AF_INET, 255.255.255.0, 0, 1), returns -ERANGE, no data written + * - in_addr_prefix_nth(AF_INET6, 2001:db8, 64, 0xff00) returns 1, writes 2001:0db8:0000:ff00:: to u + */ +int in_addr_prefix_nth(int family, union in_addr_union *u, unsigned prefixlen, uint64_t nth) { + assert(u); + + if (prefixlen <= 0) + return -ERANGE; + + if (nth == 0) + return 1; + + if (family == AF_INET) { + uint32_t c, n, t; + if (prefixlen > 32) + prefixlen = 32; + + c = be32toh(u->in.s_addr); + + t = nth << (32 - prefixlen); + + /* Check for wrap */ + if (c > UINT32_MAX - t) + return -ERANGE; + + n = c + t; + + n &= UINT32_C(0xFFFFFFFF) << (32 - prefixlen); + u->in.s_addr = htobe32(n); + return 1; + } + + if (family == AF_INET6) { + struct in6_addr result = {}; + uint8_t overflow = 0; + uint64_t delta; /* this assumes that we only ever have to up to 1<<64 subnets */ + unsigned start_byte = (prefixlen - 1) / 8; + + if (prefixlen > 128) + prefixlen = 128; + + /* First calculate what we have to add */ + delta = nth << ((128 - prefixlen) % 8); + + for (unsigned i = 16; i > 0; i--) { + unsigned j = i - 1; + unsigned d = 0; + + if (j <= start_byte) { + int16_t t; + + d = delta & 0xFF; + delta >>= 8; + + t = u->in6.s6_addr[j] + d + overflow; + overflow = t > UINT8_MAX ? t - UINT8_MAX : 0; + + result.s6_addr[j] = (uint8_t)t; + } else + result.s6_addr[j] = u->in6.s6_addr[j]; + } + + if (overflow || delta != 0) + return -ERANGE; + + u->in6 = result; + return 1; + } + + return -EAFNOSUPPORT; +} + int in_addr_random_prefix( int family, union in_addr_union *u, diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h index ae2dad0bb1..90d79a5ef5 100644 --- a/src/basic/in-addr-util.h +++ b/src/basic/in-addr-util.h @@ -36,6 +36,7 @@ bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b); int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b); int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen); int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen); +int in_addr_prefix_nth(int family, union in_addr_union *u, unsigned prefixlen, uint64_t nth); int in_addr_random_prefix(int family, union in_addr_union *u, unsigned prefixlen_fixed_part, unsigned prefixlen); int in_addr_to_string(int family, const union in_addr_union *u, char **ret); int in_addr_prefix_to_string(int family, const union in_addr_union *u, unsigned prefixlen, char **ret); diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index d36caaa71e..d63f212c19 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -173,6 +173,45 @@ static void test_in_addr_prefix_next(void) { test_in_addr_prefix_next_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", 120, NULL); } +static void test_in_addr_prefix_nth_one(unsigned f, const char *before, unsigned pl, uint64_t nth, const char *after) { + union in_addr_union ubefore, uafter, t; + + assert_se(in_addr_from_string(f, before, &ubefore) >= 0); + + t = ubefore; + assert_se((in_addr_prefix_nth(f, &t, pl, nth) > 0) == !!after); + + if (after) { + assert_se(in_addr_from_string(f, after, &uafter) >= 0); + assert_se(in_addr_equal(f, &t, &uafter) > 0); + } +} + +static void test_in_addr_prefix_nth(void) { + log_info("/* %s */", __func__); + + test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 24, 0, "192.168.0.0"); + test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 24, 1, "192.168.1.0"); + test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 24, 4, "192.168.4.0"); + test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 25, 1, "192.168.0.128"); + test_in_addr_prefix_nth_one(AF_INET, "192.168.255.0", 25, 1, "192.168.255.128"); + test_in_addr_prefix_nth_one(AF_INET, "192.168.255.0", 24, 0, "192.168.255.0"); + test_in_addr_prefix_nth_one(AF_INET, "255.255.255.255", 32, 1, NULL); + test_in_addr_prefix_nth_one(AF_INET, "255.255.255.255", 0, 1, NULL); + + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 8, 1, "4500::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 7, 1, "4600::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 1, "4400:0:0:1::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 2, "4400:0:0:2::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 0xbad, "4400:0:0:0bad::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400:0:0:ffff::", 64, 1, "4400:0:1::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 56, ((uint64_t)1<<48) -1, "44ff:ffff:ffff:ff00::"); + test_in_addr_prefix_nth_one(AF_INET6, "0000::", 8, 255, "ff00::"); + test_in_addr_prefix_nth_one(AF_INET6, "0000::", 8, 256, NULL); + test_in_addr_prefix_nth_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, 1, NULL); + test_in_addr_prefix_nth_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0, 1, NULL); +} + static void test_in_addr_to_string_one(int f, const char *addr) { union in_addr_union ua; _cleanup_free_ char *r = NULL; @@ -673,6 +712,7 @@ int main(int argc, char *argv[]) { test_in_addr_is_null(); test_in_addr_prefix_intersect(); test_in_addr_prefix_next(); + test_in_addr_prefix_nth(); test_in_addr_to_string(); test_in_addr_ifindex_to_string(); test_in_addr_ifindex_from_string_auto(); From 171f625b9e1b942e6d61f4706f26864b7c9b1452 Mon Sep 17 00:00:00 2001 From: Andreas Rammhold Date: Wed, 29 Apr 2020 23:48:41 +0200 Subject: [PATCH 2/3] in-addr-util: removed in_addr_prefix_next implementation The in_addr_prefix_nth function does everything this function did and more. We can substitute 100% of its users with the new function. --- src/basic/in-addr-util.c | 48 ++-------------------------------------- 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index d1e457b647..9feee66343 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -177,53 +177,9 @@ int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen) assert(u); /* Increases the network part of an address by one. Returns - * positive it that succeeds, or 0 if this overflows. */ + * positive if that succeeds, or -ERANGE if this overflows. */ - if (prefixlen <= 0) - return 0; - - if (family == AF_INET) { - uint32_t c, n; - - if (prefixlen > 32) - prefixlen = 32; - - c = be32toh(u->in.s_addr); - n = c + (1UL << (32 - prefixlen)); - if (n < c) - return 0; - n &= 0xFFFFFFFFUL << (32 - prefixlen); - - u->in.s_addr = htobe32(n); - return 1; - } - - if (family == AF_INET6) { - struct in6_addr add = {}, result; - uint8_t overflow = 0; - unsigned i; - - if (prefixlen > 128) - prefixlen = 128; - - /* First calculate what we have to add */ - add.s6_addr[(prefixlen-1) / 8] = 1 << (7 - (prefixlen-1) % 8); - - for (i = 16; i > 0; i--) { - unsigned j = i - 1; - - result.s6_addr[j] = u->in6.s6_addr[j] + add.s6_addr[j] + overflow; - overflow = (result.s6_addr[j] < u->in6.s6_addr[j]); - } - - if (overflow) - return 0; - - u->in6 = result; - return 1; - } - - return -EAFNOSUPPORT; + return in_addr_prefix_nth(family, u, prefixlen, 1); } /* From 02e9e34bd9a8a252610e181533e477ec10733e27 Mon Sep 17 00:00:00 2001 From: Andreas Rammhold Date: Thu, 14 May 2020 00:54:37 +0200 Subject: [PATCH 3/3] 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. --- man/systemd.network.xml | 10 + src/network/networkd-dhcp6.c | 216 +++++++++++++++--- src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 + src/network/networkd-radv.c | 37 +++ src/network/networkd-radv.h | 1 + .../fuzz-network-parser/directives.network | 1 + 8 files changed, 230 insertions(+), 38 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index dc3e472cdb..ea004b74d9 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -701,6 +701,16 @@ sections for more configuration options. + + IPv6PDSubnetId= + 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 RFC 4291, 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 IPv6PrefixDelegation= + and the corresponding configuration on the upstream interface. + + IPv6MTUBytes= Configures IPv6 maximum transmission unit (MTU). diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 3580498e35..6161f66b35 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -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; +} diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index e376adbdee..162d466d02 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -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) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index d9646a21cb..5f6aab4271 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -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, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index cbdeda96fc..cd71a5dadc 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -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; diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index d9267dd805..f5f8ec65ed 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -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; +} diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h index 8bf697aff0..b115243ef5 100644 --- a/src/network/networkd-radv.h +++ b/src/network/networkd-radv.h @@ -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); diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 1cd18202f4..2f58e03d68 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -179,6 +179,7 @@ NTP= DHCP= Domains= IPv6PrefixDelegation= +IPv6PDSubnetId= VLAN= DHCPServer= BindCarrier=