diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 8b6c187f34..d44312a941 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -479,18 +479,14 @@ specified more than once. - If the specified address is 0.0.0.0 (for IPv4) or - [::] (for IPv6), a new address range of the requested size - is automatically allocated from a system-wide pool of - unused ranges. The allocated range is checked against all - current network interfaces and all known network - configuration files to avoid address range conflicts. The - default system-wide pool consists of 192.168.0.0/16, - 172.16.0.0/12 and 10.0.0.0/8 for IPv4, and fc00::/7 for - IPv6. This functionality is useful to manage a large - number of dynamically created network interfaces with the - same network configuration and automatic address range - assignment. + If the specified address is 0.0.0.0 (for IPv4) or :: + (for IPv6), a new address range of the requested size is automatically allocated from a + system-wide pool of unused ranges. Note that the prefix length must be equal or larger than 8 for + IPv4, and 64 for IPv6. The allocated range is checked against all current network interfaces and + all known network configuration files to avoid address range conflicts. The default system-wide + pool consists of 192.168.0.0/16, 172.16.0.0/12 and 10.0.0.0/8 for IPv4, and fd00::/8 for IPv6. + This functionality is useful to manage a large number of dynamically created network interfaces + with the same network configuration and automatic address range assignment. @@ -822,15 +818,15 @@ Address= - As in the [Network] section. This - key is mandatory. + As in the [Network] section. This key is mandatory. Each + [Address] section can contain one Address= setting. Peer= The peer address in a point-to-point connection. - Accepts the same format as the Address + Accepts the same format as the Address= key. @@ -841,7 +837,7 @@ described in inet_pton3. This key only applies to IPv4 addresses. If it is not - given, it is derived from the Address + given, it is derived from the Address= key. diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index c715075c14..bc6b9ce44d 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -11,6 +11,7 @@ #include "in-addr-util.h" #include "macro.h" #include "parse-util.h" +#include "random-util.h" #include "util.h" bool in4_addr_is_null(const struct in_addr *a) { @@ -215,6 +216,83 @@ int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen) return -EAFNOSUPPORT; } +int in_addr_random_prefix( + int family, + union in_addr_union *u, + unsigned prefixlen_fixed_part, + unsigned prefixlen) { + + assert(u); + + /* Random network part of an address by one. */ + + if (prefixlen <= 0) + return 0; + + if (family == AF_INET) { + uint32_t c, n; + + if (prefixlen_fixed_part > 32) + prefixlen_fixed_part = 32; + if (prefixlen > 32) + prefixlen = 32; + if (prefixlen_fixed_part >= prefixlen) + return -EINVAL; + + c = be32toh(u->in.s_addr); + c &= ((UINT32_C(1) << prefixlen_fixed_part) - 1) << (32 - prefixlen_fixed_part); + + random_bytes(&n, sizeof(n)); + n &= ((UINT32_C(1) << (prefixlen - prefixlen_fixed_part)) - 1) << (32 - prefixlen); + + u->in.s_addr = htobe32(n | c); + return 1; + } + + if (family == AF_INET6) { + struct in6_addr n; + unsigned i, j; + + if (prefixlen_fixed_part > 128) + prefixlen_fixed_part = 128; + if (prefixlen > 128) + prefixlen = 128; + if (prefixlen_fixed_part >= prefixlen) + return -EINVAL; + + random_bytes(&n, sizeof(n)); + + for (i = 0; i < 16; i++) { + uint8_t mask_fixed_part = 0, mask = 0; + + if (i < (prefixlen_fixed_part + 7) / 8) { + if (i < prefixlen_fixed_part / 8) + mask_fixed_part = 0xffu; + else { + j = prefixlen_fixed_part % 8; + mask_fixed_part = ((UINT8_C(1) << (j + 1)) - 1) << (8 - j); + } + } + + if (i < (prefixlen + 7) / 8) { + if (i < prefixlen / 8) + mask = 0xffu ^ mask_fixed_part; + else { + j = prefixlen % 8; + mask = (((UINT8_C(1) << (j + 1)) - 1) << (8 - j)) ^ mask_fixed_part; + } + } + + u->in6.s6_addr[i] &= mask_fixed_part; + u->in6.s6_addr[i] |= n.s6_addr[i] & mask; + } + + return 1; + } + + return -EAFNOSUPPORT; +} + int in_addr_to_string(int family, const union in_addr_union *u, char **ret) { char *x; size_t l; diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h index c21567122c..3517fe1ea3 100644 --- a/src/basic/in-addr-util.h +++ b/src/basic/in-addr-util.h @@ -35,6 +35,7 @@ bool in4_addr_is_non_local(const struct in_addr *a); 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_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_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret); int in_addr_from_string(int family, const char *s, union in_addr_union *ret); diff --git a/src/network/networkd-address-pool.c b/src/network/networkd-address-pool.c index 1650515064..eaf056d118 100644 --- a/src/network/networkd-address-pool.c +++ b/src/network/networkd-address-pool.c @@ -6,7 +6,9 @@ #include "set.h" #include "string-util.h" -int address_pool_new( +#define RANDOM_PREFIX_TRIAL_MAX 1024 + +static int address_pool_new( Manager *m, AddressPool **ret, int family, @@ -121,35 +123,34 @@ static bool address_pool_prefix_is_taken( int address_pool_acquire(AddressPool *p, unsigned prefixlen, union in_addr_union *found) { union in_addr_union u; + unsigned i; + int r; assert(p); assert(prefixlen > 0); assert(found); - if (p->prefixlen > prefixlen) + if (p->prefixlen >= prefixlen) return 0; u = p->in_addr; - for (;;) { + + for (i = 0; i < RANDOM_PREFIX_TRIAL_MAX; i++) { + r = in_addr_random_prefix(p->family, &u, p->prefixlen, prefixlen); + if (r <= 0) + return r; + if (!address_pool_prefix_is_taken(p, &u, prefixlen)) { - _cleanup_free_ char *s = NULL; - int r; + if (DEBUG_LOGGING) { + _cleanup_free_ char *s = NULL; - r = in_addr_to_string(p->family, &u, &s); - if (r < 0) - return r; - - log_debug("Found range %s/%u", strna(s), prefixlen); + (void) in_addr_to_string(p->family, &u, &s); + log_debug("Found range %s/%u", strna(s), prefixlen); + } *found = u; return 1; } - - if (!in_addr_prefix_next(p->family, &u, prefixlen)) - return 0; - - if (!in_addr_prefix_intersect(p->family, &p->in_addr, p->prefixlen, &u, prefixlen)) - return 0; } return 0; diff --git a/src/network/networkd-address-pool.h b/src/network/networkd-address-pool.h index bd479a517d..7db1c4f26c 100644 --- a/src/network/networkd-address-pool.h +++ b/src/network/networkd-address-pool.h @@ -19,7 +19,6 @@ struct AddressPool { LIST_FIELDS(AddressPool, address_pools); }; -int address_pool_new(Manager *m, AddressPool **ret, int family, const union in_addr_union *u, unsigned prefixlen); int address_pool_new_from_string(Manager *m, AddressPool **ret, int family, const char *p, unsigned prefixlen); void address_pool_free(AddressPool *p); diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 7c1ee75405..e057978400 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -495,8 +495,9 @@ static int address_acquire(Link *link, Address *original, Address **ret) { assert(ret); /* Something useful was configured? just use it */ - if (in_addr_is_null(original->family, &original->in_addr) <= 0) - return 0; + r = in_addr_is_null(original->family, &original->in_addr); + if (r <= 0) + return r; /* The address is configured to be 0.0.0.0 or [::] by the user? * Then let's acquire something more useful from the pool. */ @@ -761,6 +762,19 @@ int config_parse_address(const char *unit, return 0; } + if (in_addr_is_null(f, &buffer)) { + /* Will use address from address pool. Note that for ipv6 case, prefix of the address + * pool is 8, but 40 bit is used by the global ID and 16 bit by the subnet ID. So, + * let's limit the prefix length to 64 or larger. See RFC4193. */ + if ((f == AF_INET && prefixlen < 8) || + (f == AF_INET6 && prefixlen < 64)) { + log_syntax(unit, LOG_ERR, filename, line, 0, + "Null address with invalid prefixlen='%u', ignoring assignment: %s", + prefixlen, rvalue); + return 0; + } + } + n->family = f; n->prefixlen = prefixlen; diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 00f6545e24..079cd3e1d1 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -39,11 +39,11 @@ static int setup_default_address_pool(Manager *m) { /* Add in the well-known private address ranges. */ - r = address_pool_new_from_string(m, &p, AF_INET6, "fc00::", 7); + r = address_pool_new_from_string(m, &p, AF_INET6, "fd00::", 8); if (r < 0) return r; - r = address_pool_new_from_string(m, &p, AF_INET, "192.168.0.0", 16); + r = address_pool_new_from_string(m, &p, AF_INET, "10.0.0.0", 8); if (r < 0) return r; @@ -51,7 +51,7 @@ static int setup_default_address_pool(Manager *m) { if (r < 0) return r; - r = address_pool_new_from_string(m, &p, AF_INET, "10.0.0.0", 8); + r = address_pool_new_from_string(m, &p, AF_INET, "192.168.0.0", 16); if (r < 0) return r; diff --git a/src/test/test-in-addr-util.c b/src/test/test-in-addr-util.c index 16844e9565..49ca4dc4af 100644 --- a/src/test/test-in-addr-util.c +++ b/src/test/test-in-addr-util.c @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ +#include #include #include "log.h" +#include "strv.h" #include "in-addr-util.h" static void test_in_addr_prefix_from_string( @@ -55,6 +57,50 @@ static void test_in_addr_prefix_from_string( } } +static void test_in_addr_random_prefix(void) { + _cleanup_free_ char *str = NULL; + union in_addr_union a; + + assert_se(in_addr_from_string(AF_INET, "192.168.10.1", &a) >= 0); + + assert_se(in_addr_random_prefix(AF_INET, &a, 31, 32) >= 0); + assert_se(in_addr_to_string(AF_INET, &a, &str) >= 0); + assert_se(STR_IN_SET(str, "192.168.10.0", "192.168.10.1")); + str = mfree(str); + + assert_se(in_addr_random_prefix(AF_INET, &a, 24, 26) >= 0); + assert_se(in_addr_to_string(AF_INET, &a, &str) >= 0); + assert_se(startswith(str, "192.168.10.")); + str = mfree(str); + + assert_se(in_addr_random_prefix(AF_INET, &a, 16, 24) >= 0); + assert_se(in_addr_to_string(AF_INET, &a, &str) >= 0); + assert_se(fnmatch("192.168.[0-9]*.0", str, 0) == 0); + str = mfree(str); + + assert_se(in_addr_random_prefix(AF_INET, &a, 8, 24) >= 0); + assert_se(in_addr_to_string(AF_INET, &a, &str) >= 0); + assert_se(fnmatch("192.[0-9]*.[0-9]*.0", str, 0) == 0); + str = mfree(str); + + assert_se(in_addr_random_prefix(AF_INET, &a, 8, 16) >= 0); + assert_se(in_addr_to_string(AF_INET, &a, &str) >= 0); + assert_se(fnmatch("192.[0-9]*.0.0", str, 0) == 0); + str = mfree(str); + + assert_se(in_addr_from_string(AF_INET6, "fd00::1", &a) >= 0); + + assert_se(in_addr_random_prefix(AF_INET6, &a, 16, 64) >= 0); + assert_se(in_addr_to_string(AF_INET6, &a, &str) >= 0); + assert_se(startswith(str, "fd00:")); + str = mfree(str); + + assert_se(in_addr_random_prefix(AF_INET6, &a, 8, 16) >= 0); + assert_se(in_addr_to_string(AF_INET6, &a, &str) >= 0); + assert_se(fnmatch("fd??::", str, 0) == 0); + str = mfree(str); +} + int main(int argc, char *argv[]) { test_in_addr_prefix_from_string("", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0, -EINVAL, 0); test_in_addr_prefix_from_string("/", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0, -EINVAL, 0); @@ -82,5 +128,7 @@ int main(int argc, char *argv[]) { test_in_addr_prefix_from_string("::1/129", AF_INET6, -ERANGE, NULL, 0, -ERANGE, 0, -ERANGE, 0); test_in_addr_prefix_from_string("::1/-1", AF_INET6, -ERANGE, NULL, 0, -ERANGE, 0, -ERANGE, 0); + test_in_addr_random_prefix(); + return 0; } diff --git a/test/test-network/conf/25-address-section.network b/test/test-network/conf/25-address-section.network index 3904953443..29bbee4f60 100644 --- a/test/test-network/conf/25-address-section.network +++ b/test/test-network/conf/25-address-section.network @@ -16,3 +16,14 @@ Label=33 [Address] Address=2001:db8::20 Peer=2001:db8::10/128 + +[Address] +Address=0.0.0.0/24 +Label=34 + +[Address] +Address=0.0.0.0/16 +Label=35 + +[Address] +Address=::/64 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index ced5e048f9..757e6da657 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -765,11 +765,28 @@ class NetworkdNetWorkTests(unittest.TestCase, Utilities): self.assertTrue(self.link_exits('dummy98')) - output = subprocess.check_output(['ip', 'address', 'show', 'dummy98']).rstrip().decode('utf-8') + # This also tests address pool + + output = subprocess.check_output(['ip', 'address', 'show', 'dev', 'dummy98', 'label', '32']).rstrip().decode('utf-8') print(output) self.assertRegex(output, 'inet 10.2.3.4 peer 10.2.3.5/16 scope global 32') + + output = subprocess.check_output(['ip', 'address', 'show', 'dev', 'dummy98', 'label', '33']).rstrip().decode('utf-8') + print(output) self.assertRegex(output, 'inet 10.6.7.8/16 brd 10.6.255.255 scope global 33') + + output = subprocess.check_output(['ip', 'address', 'show', 'dev', 'dummy98', 'label', '34']).rstrip().decode('utf-8') + print(output) + self.assertRegex(output, 'inet 192.168.[0-9]*.1/24 brd 192.168.[0-9]*.255 scope global 34') + + output = subprocess.check_output(['ip', 'address', 'show', 'dev', 'dummy98', 'label', '35']).rstrip().decode('utf-8') + print(output) + self.assertRegex(output, 'inet 172.[0-9]*.0.1/16 brd 172.[0-9]*.255.255 scope global 35') + + output = subprocess.check_output(['ip', '-6', 'address', 'show', 'dev', 'dummy98']).rstrip().decode('utf-8') + print(output) self.assertRegex(output, 'inet6 2001:db8::20 peer 2001:db8::10/128 scope global') + self.assertRegex(output, 'inet6 fd[0-9a-f:]*1/64 scope global') output = subprocess.check_output(['networkctl', 'status', 'dummy98']).rstrip().decode('utf-8') print(output)