diff options
author | Lennart Poettering <lennart@poettering.net> | 2020-05-27 18:47:26 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-27 18:47:26 +0200 |
commit | bb2294e4545ef2685d52ef27e7bc8a5a265d530b (patch) | |
tree | cb3e696dc6b564b2f292f203b09edfcac0b5f881 | |
parent | 6bce17455e880404984761bfe064ed8ee1dce303 (diff) | |
parent | 02e9e34bd9a8a252610e181533e477ec10733e27 (diff) | |
download | systemd-bb2294e4545ef2685d52ef27e7bc8a5a265d530b.tar.gz |
Merge pull request #15669 from andir/systemd-ipv6-pd-subnet-id
networkd: subnet id support for ipv6 prefix delegation
-rw-r--r-- | man/systemd.network.xml | 10 | ||||
-rw-r--r-- | src/basic/in-addr-util.c | 74 | ||||
-rw-r--r-- | src/basic/in-addr-util.h | 1 | ||||
-rw-r--r-- | src/network/networkd-dhcp6.c | 216 | ||||
-rw-r--r-- | src/network/networkd-network-gperf.gperf | 1 | ||||
-rw-r--r-- | src/network/networkd-network.c | 1 | ||||
-rw-r--r-- | src/network/networkd-network.h | 1 | ||||
-rw-r--r-- | src/network/networkd-radv.c | 37 | ||||
-rw-r--r-- | src/network/networkd-radv.h | 1 | ||||
-rw-r--r-- | src/test/test-socket-util.c | 40 | ||||
-rw-r--r-- | test/fuzz/fuzz-network-parser/directives.network | 1 |
11 files changed, 329 insertions, 54 deletions
diff --git a/man/systemd.network.xml b/man/systemd.network.xml index f9e8fa5c25..a5e64167e4 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -721,6 +721,16 @@ </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). An integer greater than or equal to 1280 bytes. When unset, the kernel's default will be used. diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index bfe855fb4b..9feee66343 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -177,47 +177,89 @@ 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. */ + + return in_addr_prefix_nth(family, u, prefixlen, 1); +} + +/* + * 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 0; + return -ERANGE; - if (family == AF_INET) { - uint32_t c, n; + 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); - n = c + (1UL << (32 - prefixlen)); - if (n < c) - return 0; - n &= 0xFFFFFFFFUL << (32 - prefixlen); + 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 add = {}, result; + struct in6_addr result = {}; uint8_t overflow = 0; - unsigned i; + 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 */ - add.s6_addr[(prefixlen-1) / 8] = 1 << (7 - (prefixlen-1) % 8); + delta = nth << ((128 - prefixlen) % 8); - for (i = 16; i > 0; i--) { + for (unsigned i = 16; i > 0; i--) { unsigned j = i - 1; + unsigned d = 0; - 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 (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) - return 0; + if (overflow || delta != 0) + return -ERANGE; u->in6 = result; return 1; 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/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 29226b64bf..b3ebdc8cd7 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; @@ -878,3 +1004,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 a2dd5479b2..8484669ad9 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -250,6 +250,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 1b4083febf..a3a1b070e0 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -415,6 +415,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 362ef6508f..64d6ef0553 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -178,6 +178,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/src/test/test-socket-util.c b/src/test/test-socket-util.c index 05c375d79b..024e12a799 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; @@ -691,6 +730,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(); diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 2b95192672..178bd9aaae 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -186,6 +186,7 @@ NTP= DHCP= Domains= IPv6PrefixDelegation= +IPv6PDSubnetId= VLAN= DHCPServer= BindCarrier= |