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:
commit
d8a23f5e4f
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user