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)