1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2025-01-06 13:17:44 +03:00

network: manage addresses in the way the kernel does

This effectively reverts 5d0030310c.

With the commit 5d0030310c, networkd manages
addresses with the detailed hash and compare functions. But that causes
networkd cannot detect address update by the kernel or an external tool.
See issue
https://github.com/systemd/systemd/issues/481#issuecomment-1328132401.

With this commit, networkd (again) manages addresses in the way that the
kernel does. Hence, we can correctly detect address update.

(cherry picked from commit 42f8b6a808)
(cherry picked from commit 13de548fca)
This commit is contained in:
Yu Watanabe 2022-11-29 03:20:33 +09:00 committed by Zbigniew Jędrzejewski-Szmek
parent 3bb53f281d
commit f2f863c51f
4 changed files with 74 additions and 34 deletions

View File

@ -317,6 +317,14 @@ DEFINE_PRIVATE_HASH_OPS(
address_kernel_hash_func,
address_kernel_compare_func);
DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
address_kernel_hash_ops_free,
Address,
address_kernel_hash_func,
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);
@ -375,12 +383,37 @@ int address_compare_func(const Address *a1, const Address *a2) {
return 0;
}
DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
address_hash_ops_free,
Address,
address_hash_func,
address_compare_func,
address_free);
int address_equal(const Address *a1, const Address *a2) {
if (a1 == a2)
return true;
if (!a1 || !a2)
return false;
return address_compare_func(a1, a2) == 0;
}
static int address_equalify(Address *address, const Address *src) {
int r;
assert(address);
assert(src);
if (address_kernel_compare_func(address, src) != 0)
return -EINVAL;
if (address->family == AF_INET) {
address->broadcast = src->broadcast;
r = free_and_strdup(&address->label, src->label);
if (r < 0)
return r;
} else {
address->prefixlen = src->prefixlen;
address->in_addr_peer = src->in_addr_peer;
}
return 0;
}
int address_dup(const Address *src, Address **ret) {
_cleanup_(address_freep) Address *dest = NULL;
@ -454,7 +487,7 @@ static int address_add(Link *link, Address *address) {
assert(link);
assert(address);
r = set_ensure_put(&link->addresses, &address_hash_ops_free, address);
r = set_ensure_put(&link->addresses, &address_kernel_hash_ops_free, address);
if (r < 0)
return r;
if (r == 0)
@ -556,10 +589,10 @@ int link_get_address(Link *link, int family, const union in_addr_union *address,
* and does not have peer address. When the prefixlen is zero, then an Address object with an
* arbitrary prefixlen will be returned. */
if (prefixlen != 0) {
if (family == AF_INET6 || prefixlen != 0) {
_cleanup_(address_freep) Address *tmp = NULL;
/* If prefixlen is set, then we can use address_get(). */
/* In this case, we can use address_get(). */
r = address_new(&tmp);
if (r < 0)
@ -568,20 +601,24 @@ int link_get_address(Link *link, int family, const union in_addr_union *address,
tmp->family = family;
tmp->in_addr = *address;
tmp->prefixlen = prefixlen;
address_set_broadcast(tmp, link);
if (address_get(link, tmp, &a) >= 0) {
if (ret)
*ret = a;
r = address_get(link, tmp, &a);
if (r < 0)
return r;
return 0;
if (family == AF_INET6) {
/* IPv6 addresses are managed without peer address and prefix length. Hence, we need
* to check them explicitly. */
if (in_addr_is_set(family, &a->in_addr_peer))
return -ENOENT;
if (prefixlen != 0 && a->prefixlen != prefixlen)
return -ENOENT;
}
if (family == AF_INET6)
return -ENOENT;
if (ret)
*ret = a;
/* IPv4 addresses may have label and/or non-default broadcast address.
* Hence, we need to always fallback below. */
return 0;
}
SET_FOREACH(a, link->addresses) {
@ -926,7 +963,11 @@ 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)
/* 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);
}
@ -1184,6 +1225,9 @@ int link_request_address(
existing = TAKE_PTR(tmp);
} else {
r = address_equalify(existing, address);
if (r < 0)
return r;
existing->source = address->source;
existing->provider = address->provider;
existing->lifetime_valid_usec = address->lifetime_valid_usec;
@ -1452,6 +1496,13 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
case RTM_NEWADDR:
if (address) {
/* update flags and etc. */
r = address_equalify(address, tmp);
if (r < 0) {
_cleanup_free_ char *str = NULL;
(void) in_addr_prefix_to_string(address->family, &address->in_addr, address->prefixlen, &str);
log_link_warning_errno(link, r, "Failed to update properties of address %s, ignoring: %m", strnull(str));
return 0;
}
address->flags = tmp->flags;
address->scope = tmp->scope;
address_set_lifetime(address, &cinfo);
@ -2032,9 +2083,8 @@ int network_drop_invalid_addresses(Network *network) {
address_free(dup);
}
/* Use address_kernel_hash_ops here. The function address_kernel_compare_func() matches
* how kernel compares addresses, and is more lenient than address_compare_func().
* Hence, the logic of dedup here is stricter than when address_hash_ops is used. */
/* Use address_kernel_hash_ops, instead of address_kernel_hash_ops_free. Otherwise, the
* Address objects will be freed. */
r = set_ensure_put(&addresses, &address_kernel_hash_ops, address);
if (r < 0)
return log_oom();

View File

@ -116,6 +116,7 @@ int manager_rtnl_process_address(sd_netlink *nl, sd_netlink_message *message, Ma
int network_drop_invalid_addresses(Network *network);
int address_compare_func(const Address *a1, const Address *a2);
int address_equal(const Address *a1, const Address *a2);
DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(Address, address);
static inline void address_enter_probing(Address *address) {

View File

@ -164,8 +164,7 @@ static int verify_dhcp6_address(Link *link, const Address *address) {
(void) in6_addr_to_string(&address->in_addr.in6, &buffer);
if (address_get(link, address, &existing) < 0 &&
link_get_address(link, AF_INET6, &address->in_addr, 0, &existing) < 0) {
if (address_get(link, address, &existing) < 0) {
/* New address. */
log_level = LOG_INFO;
goto simple_log;

View File

@ -176,16 +176,6 @@ static int test_load_config(Manager *manager) {
return 0;
}
static bool address_equal(const Address *a1, const Address *a2) {
if (a1 == a2)
return true;
if (!a1 || !a2)
return false;
return address_compare_func(a1, a2) == 0;
}
static void test_address_equality(void) {
_cleanup_(address_freep) Address *a1 = NULL, *a2 = NULL;