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

Merge pull request #28287 from yuwata/network-null-address

network: fix null address handling
This commit is contained in:
Yu Watanabe 2023-07-08 01:13:17 +09:00 committed by GitHub
commit 9bf0fe3633
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 192 additions and 106 deletions

View File

@ -262,6 +262,19 @@ static void address_set_lifetime(Manager *m, Address *a, const struct ifa_cachei
a->lifetime_preferred_usec = sec_to_usec(cinfo->ifa_prefered, now_usec);
}
static bool address_is_static_null(const Address *address) {
assert(address);
if (!address->network)
return false;
if (!address->requested_as_null)
return false;
assert(!in_addr_is_set(address->family, &address->in_addr));
return true;
}
static uint32_t address_prefix(const Address *a) {
assert(a);
@ -339,30 +352,6 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
address_kernel_compare_func,
address_free);
/* The functions below are mainly used by managing Request. */
static void address_hash_func(const Address *a, struct siphash *state) {
assert(a);
siphash24_compress(&a->family, sizeof(a->family), state);
/* treat any other address family as AF_UNSPEC */
if (!IN_SET(a->family, AF_INET, AF_INET6))
return;
siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state);
siphash24_compress(&a->in_addr, FAMILY_ADDRESS_SIZE(a->family), state);
siphash24_compress(&a->in_addr_peer, FAMILY_ADDRESS_SIZE(a->family), state);
if (a->family == AF_INET) {
/* On update, the kernel ignores the address label and broadcast address, hence we need
* to distinguish addresses with different labels or broadcast addresses. Otherwise,
* the label or broadcast address change will not be applied when we reconfigure the
* interface. */
siphash24_compress_string(a->label, state);
siphash24_compress(&a->broadcast, sizeof(a->broadcast), state);
}
}
int address_compare_func(const Address *a1, const Address *a2) {
int r;
@ -386,6 +375,10 @@ int address_compare_func(const Address *a1, const Address *a2) {
return r;
if (a1->family == AF_INET) {
/* On update, the kernel ignores the address label and broadcast address, hence we need
* to distinguish addresses with different labels or broadcast addresses. Otherwise,
* the label or broadcast address change will not be applied when we reconfigure the
* interface. */
r = strcmp_ptr(a1->label, a2->label);
if (r != 0)
return r;
@ -409,16 +402,31 @@ int address_equal(const Address *a1, const Address *a2) {
}
static int address_equalify(Address *address, const Address *src) {
bool src_is_null;
int r;
assert(address);
assert(src);
if (address_kernel_compare_func(address, src) != 0)
src_is_null = address_is_static_null(src);
if (src_is_null) {
/* When the source address has an null address, then we cannot compare the two objects with
* address_kernel_compare_func(). Let's do minimal check here. */
if (address->family != src->family)
return -EINVAL;
if (address->prefixlen != src->prefixlen)
return -EINVAL;
} else if (address_kernel_compare_func(address, src) != 0)
return -EINVAL;
if (address->family == AF_INET) {
address->broadcast = src->broadcast;
/* When the source is null, then the broadcast address will be determined based on the
* acquired address. Hence, do not copy it here. */
if (!src_is_null)
address->broadcast = src->broadcast;
r = free_and_strdup(&address->label, src->label);
if (r < 0)
return r;
@ -572,18 +580,40 @@ static int address_drop(Address *address) {
}
int address_get(Link *link, const Address *in, Address **ret) {
Address *existing;
Address *a;
assert(link);
assert(in);
existing = set_get(link->addresses, in);
if (!existing)
return -ENOENT;
a = set_get(link->addresses, in);
if (a) {
if (ret)
*ret = a;
return 0;
}
if (ret)
*ret = existing;
return 0;
/* Find matching address that originally requested as null address. */
if (address_is_static_null(in))
SET_FOREACH(a, link->addresses) {
if (!a->requested_as_null)
continue;
/* Currently, null address is supported only by static addresses. Note that static
* address may be set as foreign during reconfiguring the interface. */
if (!IN_SET(a->source, NETWORK_CONFIG_SOURCE_FOREIGN, NETWORK_CONFIG_SOURCE_STATIC))
continue;
if (a->family != in->family)
continue;
if (a->prefixlen != in->prefixlen)
continue;
if (ret)
*ret = a;
return 0;
}
return -ENOENT;
}
int link_get_address(Link *link, int family, const union in_addr_union *address, unsigned char prefixlen, Address **ret) {
@ -708,16 +738,22 @@ static void log_address_debug(const Address *address, const char *str, const Lin
const char *peer = in_addr_is_set(address->family, &address->in_addr_peer) ?
IN_ADDR_TO_STRING(address->family, &address->in_addr_peer) : NULL;
const char *broadcast = (address->family == AF_INET && in4_addr_is_set(&address->broadcast)) ?
IN4_ADDR_TO_STRING(&address->broadcast) : NULL;
(void) address_flags_to_string_alloc(address->flags, address->family, &flags_str);
(void) route_scope_to_string_alloc(address->scope, &scope_str);
log_link_debug(link, "%s %s address (%s): %s%s%s/%u (valid %s, preferred %s), flags: %s, scope: %s",
log_link_debug(link, "%s %s address (%s): %s%s%s/%u%s%s (valid %s, preferred %s), flags: %s, scope: %s%s%s",
str, strna(network_config_source_to_string(address->source)), strna(state),
IN_ADDR_TO_STRING(address->family, &address->in_addr),
peer ? " peer " : "", strempty(peer), address->prefixlen,
broadcast ? " broadcast " : "", strempty(broadcast),
FORMAT_LIFETIME(address->lifetime_valid_usec),
FORMAT_LIFETIME(address->lifetime_preferred_usec),
strna(flags_str), strna(scope_str));
strna(flags_str), strna(scope_str),
address->family == AF_INET ? ", label: " : "",
address->family == AF_INET ? strna(address->label) : "");
}
static int address_set_netlink_message(const Address *address, sd_netlink_message *m, Link *link) {
@ -985,12 +1021,24 @@ int link_drop_foreign_addresses(Link *link) {
ORDERED_HASHMAP_FOREACH(address, link->network->addresses_by_section) {
Address *existing;
if (address_get(link, address, &existing) < 0)
continue;
/* On update, the kernel ignores the address label and broadcast address. Hence we need to
* distinguish addresses with different labels or broadcast addresses. Thus, we need to check
* the existing address with address_equal(). Otherwise, the label or broadcast address
* change will not be applied when we reconfigure the interface. */
if (address_get(link, address, &existing) >= 0 && address_equal(address, existing))
address_unmark(existing);
if (address_is_static_null(address)) {
/* If the static configuration has null address, then the broadcast address will be
* determined automatically. Hence, here we need to compare only address label. */
if (address->family == AF_INET && !streq_ptr(address->label, existing->label))
continue;
} else if (!address_equal(address, existing))
continue;
/* Found matching static configuration. Keep the existing address. */
address_unmark(existing);
}
/* Finally, remove all marked addresses. */
@ -1050,10 +1098,8 @@ static int address_acquire(Link *link, const Address *original, Address **ret) {
assert(ret);
/* Something useful was configured? just use it */
if (in_addr_is_set(original->family, &original->in_addr)) {
*ret = NULL;
return 0;
}
if (in_addr_is_set(original->family, &original->in_addr))
return address_dup(original, ret);
/* The address is configured to be 0.0.0.0 or [::] by the user?
* Then let's acquire something more useful from the pool. */
@ -1076,7 +1122,7 @@ static int address_acquire(Link *link, const Address *original, Address **ret) {
na->in_addr = in_addr;
*ret = TAKE_PTR(na);
return 1;
return 0;
}
int address_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg) {
@ -1207,79 +1253,54 @@ int link_request_address(
address_netlink_handler_t netlink_handler,
Request **ret) {
Address *acquired, *existing = NULL;
_unused_ _cleanup_(address_freep) Address *address_will_be_freed = NULL;
Address *existing;
int r;
assert(link);
assert(address);
assert(address->source != NETWORK_CONFIG_SOURCE_FOREIGN);
r = address_acquire(link, address, &acquired);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to acquire an address from pool: %m");
if (r > 0) {
if (consume_object)
address_free(address);
if (consume_object)
address_will_be_freed = address;
address = acquired;
consume_object = true;
}
if (address_needs_to_set_broadcast(address, link)) {
if (!consume_object) {
Address *a;
r = address_dup(address, &a);
if (r < 0)
return r;
address = a;
consume_object = true;
}
address_set_broadcast(address, link);
}
(void) address_get(link, address, &existing);
if (address->lifetime_valid_usec == 0) {
if (consume_object)
address_free(address);
/* The requested address is outdated. Let's remove it. */
return address_remove_and_drop(existing);
}
if (!existing) {
if (address_get(link, address, &existing) < 0) {
_cleanup_(address_freep) Address *tmp = NULL;
if (consume_object)
tmp = address;
else {
r = address_dup(address, &tmp);
if (r < 0)
return r;
}
if (address->lifetime_valid_usec == 0)
/* The requested address is outdated. Let's ignore the request. */
return 0;
r = address_acquire(link, address, &tmp);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to acquire an address from pool: %m");
address_set_broadcast(tmp, link);
/* Consider address tentative until we get the real flags from the kernel */
tmp->flags |= IFA_F_TENTATIVE;
r = address_add(link, tmp);
if (r < 0)
return r;
return log_link_warning_errno(link, r, "Failed to save requested address: %m");
existing = TAKE_PTR(tmp);
} else {
if (address->lifetime_valid_usec == 0)
/* The requested address is outdated. Let's remove it. */
return address_remove_and_drop(existing);
r = address_equalify(existing, address);
if (r < 0)
return r;
return log_link_warning_errno(link, r, "Failed to equalify saved address with the requested one: %m");
address_set_broadcast(existing, link);
existing->source = address->source;
existing->provider = address->provider;
existing->duplicate_address_detection = address->duplicate_address_detection;
existing->lifetime_valid_usec = address->lifetime_valid_usec;
existing->lifetime_preferred_usec = address->lifetime_preferred_usec;
if (consume_object)
address_free(address);
}
r = ipv4acd_configure(existing);
@ -1289,8 +1310,8 @@ int link_request_address(
log_address_debug(existing, "Requesting", link);
r = link_queue_request_safe(link, REQUEST_TYPE_ADDRESS,
existing, NULL,
address_hash_func,
address_compare_func,
address_kernel_hash_func,
address_kernel_compare_func,
address_process_request,
message_counter, netlink_handler, ret);
if (r < 0)
@ -1377,8 +1398,8 @@ void address_cancel_request(Address *address) {
.link = address->link,
.type = REQUEST_TYPE_ADDRESS,
.userdata = address,
.hash_func = (hash_func_t) address_hash_func,
.compare_func = (compare_func_t) address_compare_func,
.hash_func = (hash_func_t) address_kernel_hash_func,
.compare_func = (compare_func_t) address_kernel_compare_func,
};
request_detach(address->link->manager, &req);
@ -1741,9 +1762,10 @@ int config_parse_address(
n->family = f;
n->prefixlen = prefixlen;
if (streq(lvalue, "Address"))
if (streq(lvalue, "Address")) {
n->in_addr = buffer;
else
n->requested_as_null = !in_addr_is_set(n->family, &n->in_addr);
} else
n->in_addr_peer = buffer;
TAKE_PTR(n);
@ -2072,6 +2094,36 @@ int config_parse_address_netlabel(
return 0;
}
static void address_section_adjust_broadcast(Address *address) {
assert(address);
assert(address->section);
if (!in4_addr_is_set(&address->broadcast))
return;
if (address->family == AF_INET6)
log_warning("%s: broadcast address is set for an IPv6 address. "
"Ignoring Broadcast= setting in the [Address] section from line %u.",
address->section->filename, address->section->line);
else if (address->prefixlen > 30)
log_warning("%s: broadcast address is set for an IPv4 address with prefix length larger than 30. "
"Ignoring Broadcast= setting in the [Address] section from line %u.",
address->section->filename, address->section->line);
else if (in4_addr_is_set(&address->in_addr_peer.in))
log_warning("%s: broadcast address is set for an IPv4 address with peer address. "
"Ignoring Broadcast= setting in the [Address] section from line %u.",
address->section->filename, address->section->line);
else if (!in4_addr_is_set(&address->in_addr.in))
log_warning("%s: broadcast address is set for an IPv4 address with null address. "
"Ignoring Broadcast= setting in the [Address] section from line %u.",
address->section->filename, address->section->line);
else
/* Otherwise, keep the specified broadcast address. */
return;
address->broadcast.s_addr = 0;
}
static int address_section_verify(Address *address) {
if (section_is_invalid(address->section))
return -EINVAL;
@ -2087,16 +2139,7 @@ static int address_section_verify(Address *address) {
assert(IN_SET(address->family, AF_INET, AF_INET6));
if (in4_addr_is_set(&address->broadcast) &&
(address->family == AF_INET6 || address->prefixlen > 30 ||
in_addr_is_set(address->family, &address->in_addr_peer))) {
log_warning("%s: broadcast address is set for an IPv6 address, "
"an IPv4 address with peer address, or with prefix length larger than 30. "
"Ignoring Broadcast= setting in the [Address] section from line %u.",
address->section->filename, address->section->line);
address->broadcast.s_addr = 0;
}
address_section_adjust_broadcast(address);
if (address->family == AF_INET6 && address->label) {
log_warning("%s: address label is set for IPv6 address in the [Address] section from line %u. "

View File

@ -53,6 +53,7 @@ struct Address {
bool scope_set:1;
bool ip_masquerade_done:1;
bool requested_as_null:1;
/* duplicate_address_detection is only used by static or IPv4 dynamic addresses.
* To control DAD for IPv6 dynamic addresses, set IFA_F_NODAD to flags. */

View File

@ -0,0 +1,14 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=dummy98
[Network]
IPv6AcceptRA=no
Address=0.0.0.0/16
Address=0.0.0.0/30
Address=::/64
[Address]
Address=0.0.0.0/24
# The broadcast address will be ignored.
Broadcast=192.168.0.255

View File

@ -2342,6 +2342,34 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
# TODO: check json string
check_output(*networkctl_cmd, '--json=short', 'status', env=env)
def test_address_null(self):
copy_network_unit('25-address-null.network', '12-dummy.netdev')
start_networkd()
self.wait_online(['dummy98:routable'])
output = check_output('ip address show dev dummy98 scope global')
print(output)
ipv4_address_16 = re.findall(r'inet 172.[0-9]*.0.1/16 brd 172.[0-9]*.255.255', output)
self.assertEqual(len(ipv4_address_16), 1)
ipv4_address_24 = re.findall(r'inet 192.168.[0-9]*.1/24 brd 192.168.[0-9]*.255', output)
self.assertEqual(len(ipv4_address_24), 1)
ipv4_address_30 = re.findall(r'inet 192.168.[0-9]*.[0-9]*/30 brd 192.168.[0-9]*.[0-9]*', output)
self.assertEqual(len(ipv4_address_30), 1)
ipv6_address = re.findall(r'inet6 fd[0-9a-f:]*/64', output)
self.assertEqual(len(ipv6_address), 1)
networkctl_reconfigure('dummy98')
self.wait_online(['dummy98:routable'])
output = check_output('ip address show dev dummy98 scope global')
print(output)
self.assertIn(ipv4_address_16[0], output)
self.assertIn(ipv4_address_24[0], output)
self.assertIn(ipv4_address_30[0], output)
self.assertIn(ipv6_address[0], output)
def test_address_ipv4acd(self):
check_output('ip netns add ns99')
check_output('ip link add veth99 type veth peer veth-peer')