diff options
-rw-r--r-- | src/network/networkd-address.c | 93 | ||||
-rw-r--r-- | src/network/networkd-address.h | 1 | ||||
-rw-r--r-- | src/network/networkd-dhcp6.c | 3 | ||||
-rw-r--r-- | src/network/test-network.c | 10 |
4 files changed, 73 insertions, 34 deletions
diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index eae374323e..5b3b7d128a 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -309,6 +309,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); @@ -367,12 +375,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; @@ -451,7 +484,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) @@ -542,10 +575,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) @@ -554,20 +587,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) { @@ -925,7 +962,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); } @@ -1199,6 +1240,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->duplicate_address_detection = address->duplicate_address_detection; @@ -1468,6 +1512,12 @@ 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) { + log_link_warning_errno(link, r, "Failed to update properties of address %s, ignoring: %m", + IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen)); + return 0; + } address->flags = tmp->flags; address->scope = tmp->scope; address_set_lifetime(m, address, &cinfo); @@ -2085,9 +2135,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(); diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 7a1e44632d..89b9621791 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -118,6 +118,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); diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index d6ec233351..c44c37f3aa 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -163,8 +163,7 @@ static int verify_dhcp6_address(Link *link, const Address *address) { const char *pretty = IN6_ADDR_TO_STRING(&address->in_addr.in6); - 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; diff --git a/src/network/test-network.c b/src/network/test-network.c index 0145c8b7c7..250ab9eff4 100644 --- a/src/network/test-network.c +++ b/src/network/test-network.c @@ -174,16 +174,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; |