From 198afaab908b80667bf4071b572ae7455b6cc854 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 20 Feb 2019 16:28:12 +0900 Subject: [PATCH 01/11] network: make address_pool_new() static --- src/network/networkd-address-pool.c | 2 +- src/network/networkd-address-pool.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/network/networkd-address-pool.c b/src/network/networkd-address-pool.c index 1650515064f..26c73acbb7a 100644 --- a/src/network/networkd-address-pool.c +++ b/src/network/networkd-address-pool.c @@ -6,7 +6,7 @@ #include "set.h" #include "string-util.h" -int address_pool_new( +static int address_pool_new( Manager *m, AddressPool **ret, int family, diff --git a/src/network/networkd-address-pool.h b/src/network/networkd-address-pool.h index bd479a517db..7db1c4f26c1 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); From f2efbeafb81d8699ca6e8f8ddbf47167a0489515 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 20 Feb 2019 16:58:58 +0900 Subject: [PATCH 02/11] network: use fd00::/8 for ipv6 address pool fc00::/8 may be used in the future. See rfc4193. --- src/network/networkd-manager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 00f6545e24f..b3151bd7ecd 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -39,7 +39,7 @@ 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; From c9207ff3a4f548061f58d1b162078db12ac3f7f6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 20 Feb 2019 18:36:17 +0900 Subject: [PATCH 03/11] network: check prefixlen when null address is specified to Address= --- src/network/networkd-address.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 7c1ee754052..b53461fd903 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -761,6 +761,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; From e6ef3a130f04c0aff32471c16238ae927bfb13db Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 20 Feb 2019 19:01:49 +0900 Subject: [PATCH 04/11] man: mention limit about prefix length when null address is specified to Address= --- man/systemd.network.xml | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 8b6c187f346..e354e485162 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. From 4e68898ed7ccb20f2a77208b40c893c14a3a6c7b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 20 Feb 2019 19:02:31 +0900 Subject: [PATCH 05/11] man: mention Address= can be specified once in [Address] section --- man/systemd.network.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index e354e485162..d44312a9419 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -818,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. @@ -837,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. From 7e43ebfbf9960a7237479e8a4e67830ae4d3a730 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 Feb 2019 11:46:51 +0900 Subject: [PATCH 06/11] network: propagate error from in_addr_is_null() --- src/network/networkd-address.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index b53461fd903..e057978400d 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. */ From c5236acdd7c99a7c9a6254075386bf669264c533 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 Feb 2019 13:23:25 +0900 Subject: [PATCH 07/11] util: introduce in_addr_random_prefix() --- src/basic/in-addr-util.c | 78 ++++++++++++++++++++++++++++++++++++++++ src/basic/in-addr-util.h | 1 + 2 files changed, 79 insertions(+) diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index c715075c14f..bc6b9ce44d5 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 c21567122ca..3517fe1ea35 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); From e762ea74812c8c75a40eb707f8fca10304ce2abc Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 1 Mar 2019 16:28:20 +0900 Subject: [PATCH 08/11] test: add tests for in_addr_random_prefix() --- src/test/test-in-addr-util.c | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/test/test-in-addr-util.c b/src/test/test-in-addr-util.c index 16844e95652..49ca4dc4af4 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; } From 304e7e9d53621b78b44c52e6aff20ed5828afc67 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 Feb 2019 13:31:35 +0900 Subject: [PATCH 09/11] network: generate random prefix from address pool Fixes #9955. --- src/network/networkd-address-pool.c | 31 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/network/networkd-address-pool.c b/src/network/networkd-address-pool.c index 26c73acbb7a..eaf056d1186 100644 --- a/src/network/networkd-address-pool.c +++ b/src/network/networkd-address-pool.c @@ -6,6 +6,8 @@ #include "set.h" #include "string-util.h" +#define RANDOM_PREFIX_TRIAL_MAX 1024 + static int address_pool_new( Manager *m, AddressPool **ret, @@ -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; From b085cd3786bf9f1ac5e55d0c429fcc7122c02014 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 Feb 2019 14:08:46 +0900 Subject: [PATCH 10/11] network: generate addresses from pool with larger prefixlen --- src/network/networkd-manager.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index b3151bd7ecd..079cd3e1d15 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -43,7 +43,7 @@ static int setup_default_address_pool(Manager *m) { 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; From 26bf9c302d322255ab8d70c963e812ed25faab38 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 Feb 2019 14:21:21 +0900 Subject: [PATCH 11/11] test-network: add tests for address pool --- .../conf/25-address-section.network | 11 +++++++++++ test/test-network/systemd-networkd-tests.py | 19 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/test/test-network/conf/25-address-section.network b/test/test-network/conf/25-address-section.network index 3904953443a..29bbee4f60b 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 ced5e048f9a..757e6da6571 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)