1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-27 18:04:05 +03:00

Merge pull request #11770 from yuwata/fix-9955

network: rework address pool
This commit is contained in:
Lennart Poettering 2019-03-04 12:11:07 +01:00 committed by GitHub
commit d8a23f5e4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 204 additions and 39 deletions

View File

@ -479,18 +479,14 @@
specified more than once.
</para>
<para>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.</para>
<para>If the specified address is <literal>0.0.0.0</literal> (for IPv4) or <literal>::</literal>
(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.</para>
</listitem>
</varlistentry>
@ -822,15 +818,15 @@
<varlistentry>
<term><varname>Address=</varname></term>
<listitem>
<para>As in the <literal>[Network]</literal> section. This
key is mandatory.</para>
<para>As in the <literal>[Network]</literal> section. This key is mandatory. Each
<literal>[Address]</literal> section can contain one <varname>Address=</varname> setting.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>Peer=</varname></term>
<listitem>
<para>The peer address in a point-to-point connection.
Accepts the same format as the <literal>Address</literal>
Accepts the same format as the <varname>Address=</varname>
key.</para>
</listitem>
</varlistentry>
@ -841,7 +837,7 @@
described in
<citerefentry project='man-pages'><refentrytitle>inet_pton</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
This key only applies to IPv4 addresses. If it is not
given, it is derived from the <literal>Address</literal>
given, it is derived from the <varname>Address=</varname>
key.</para>
</listitem>
</varlistentry>

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -1,8 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <fnmatch.h>
#include <netinet/in.h>
#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;
}

View File

@ -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

View File

@ -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)