]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: make address_get() work for null address
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 7 Jul 2023 02:24:03 +0000 (11:24 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 7 Jul 2023 10:11:37 +0000 (19:11 +0900)
When a static null address is requested, e.g. Address=0.0.0.0/24,
then the corresponding Address object owned by Network object has
null address, and previously it did not match any addresses already
assigne to the interface.

Let's search matching Address object when originally it is requested as
the null address.

Then, the address configured with requested as a null address will be
kept on reconfigure.

Fixes #26113.

src/network/networkd-address.c
src/network/networkd-address.h

index 3d83eda985725bff31de9780fa71ad0f05c5f2d9..221f5c9efe7f87b08352e24c877b1b54b3a8a46d 100644 (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);
 
@@ -389,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;
@@ -552,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;
+        }
+
+        /* 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;
 
-        if (ret)
-                *ret = existing;
-        return 0;
+                        /* 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) {
@@ -971,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. */
@@ -1700,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);
index 18eeb6e48e303483b8c871af7e21e0cc48ae1e59..766da8ee7545098e60c77063a4ddcfbf7a641d56 100644 (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. */