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:
commit
9bf0fe3633
@ -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. "
|
||||
|
@ -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. */
|
||||
|
14
test/test-network/conf/25-address-null.network
Normal file
14
test/test-network/conf/25-address-null.network
Normal 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
|
@ -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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user