about summary refs log tree commit diff
path: root/src/core/nm-l3-config-data.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2023-02-10 11:50:34 +0100
committerMichael Biebl <biebl@debian.org>2023-02-10 11:50:34 +0100
commit1372848511cb896b80b51ed1a3e9606bd9816631 (patch)
tree674792b9385bdef935988894b45f06b2af39f88c /src/core/nm-l3-config-data.c
parent40ec077ea305994c1fc2130add6787ca0c73e2c6 (diff)
New upstream version 1.42.0 upstream/1.42.0
Diffstat (limited to 'src/core/nm-l3-config-data.c')
-rw-r--r--src/core/nm-l3-config-data.c282
1 files changed, 198 insertions, 84 deletions
diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index bcd0b62a..17bb9db8 100644
--- a/src/core/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -5,7 +5,7 @@
 #include "nm-l3-config-data.h"
 
 #include <linux/if.h>
-#include <linux/if_addr.h>
+#include "nm-compat-headers/linux/if_addr.h"
 #include <linux/rtnetlink.h>
 
 #include "libnm-core-intern/nm-core-internal.h"
@@ -67,10 +67,10 @@ struct _NML3ConfigData {
 
     union {
         struct {
-            GArray *nameservers_6;
-            GArray *nameservers_4;
+            GPtrArray *nameservers_6;
+            GPtrArray *nameservers_4;
         };
-        GArray *nameservers_x[2];
+        GPtrArray *nameservers_x[2];
     };
 
     union {
@@ -269,17 +269,6 @@ _garray_inaddr_get(GArray *arr, guint *out_len)
     return arr->data;
 }
 
-static gconstpointer
-_garray_inaddr_at(GArray *arr, gboolean IS_IPv4, guint idx)
-{
-    nm_assert(arr);
-    nm_assert(idx < arr->len);
-
-    if (IS_IPv4)
-        return &g_array_index(arr, in_addr_t, idx);
-    return &g_array_index(arr, struct in6_addr, idx);
-}
-
 static gboolean
 _garray_inaddr_add(GArray **p_arr, int addr_family, gconstpointer addr)
 {
@@ -352,7 +341,7 @@ nm_l3_config_data_log(const NML3ConfigData *self,
                       NMLogDomain           log_domain)
 {
     char  sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
-    char  sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+    char  sbuf_addr[NM_INET_ADDRSTRLEN];
     int   IS_IPv4;
     guint i;
 
@@ -480,13 +469,11 @@ nm_l3_config_data_log(const NML3ConfigData *self,
                self->dns_priority_x[IS_IPv4]);
         }
 
-        for (i = 0; i < nm_g_array_len(self->nameservers_x[IS_IPv4]); i++) {
+        for (i = 0; i < nm_g_ptr_array_len(self->nameservers_x[IS_IPv4]); i++) {
             _L("nameserver%c[%u]: %s",
                nm_utils_addr_family_to_char(addr_family),
                i,
-               nm_utils_inet_ntop(addr_family,
-                                  _garray_inaddr_at(self->nameservers_x[IS_IPv4], IS_IPv4, i),
-                                  sbuf_addr));
+               (char *) self->nameservers_x[IS_IPv4]->pdata[i]);
         }
 
         for (i = 0; i < nm_g_ptr_array_len(self->domains_x[IS_IPv4]); i++) {
@@ -514,12 +501,12 @@ nm_l3_config_data_log(const NML3ConfigData *self,
             for (i = 0; i < nm_g_array_len(self->wins); i++) {
                 _L("wins[%u]: %s",
                    i,
-                   _nm_utils_inet4_ntop(g_array_index(self->wins, in_addr_t, i), sbuf_addr));
+                   nm_inet4_ntop(nm_g_array_index(self->wins, in_addr_t, i), sbuf_addr));
             }
             for (i = 0; i < nm_g_array_len(self->nis_servers); i++) {
                 _L("nis-server[%u]: %s",
                    i,
-                   _nm_utils_inet4_ntop(g_array_index(self->nis_servers, in_addr_t, i), sbuf_addr));
+                   nm_inet4_ntop(nm_g_array_index(self->nis_servers, in_addr_t, i), sbuf_addr));
             }
             if (self->nis_domain)
                 _L("nis-domain: %s", self->nis_domain->str);
@@ -621,8 +608,7 @@ nm_l3_config_data_log(const NML3ConfigData *self,
 static gboolean
 _route_valid_4(const NMPlatformIP4Route *r)
 {
-    return r && r->plen <= 32
-           && r->network == nm_utils_ip4_address_clear_host_address(r->network, r->plen);
+    return r && r->plen <= 32 && r->network == nm_ip4_addr_clear_host_address(r->network, r->plen);
 }
 
 static gboolean
@@ -632,7 +618,7 @@ _route_valid_6(const NMPlatformIP6Route *r)
 
     return r && r->plen <= 128
            && (memcmp(&r->network,
-                      nm_utils_ip6_address_clear_host_address(&n, &r->network, r->plen),
+                      nm_ip6_addr_clear_host_address(&n, &r->network, r->plen),
                       sizeof(n))
                == 0);
 }
@@ -793,8 +779,8 @@ nm_l3_config_data_unref(const NML3ConfigData *self)
     nm_clear_pointer(&mutable->dhcp_lease_4, nm_dhcp_lease_unref);
     nm_clear_pointer(&mutable->dhcp_lease_6, nm_dhcp_lease_unref);
 
-    nm_clear_pointer(&mutable->nameservers_4, g_array_unref);
-    nm_clear_pointer(&mutable->nameservers_6, g_array_unref);
+    nm_clear_pointer(&mutable->nameservers_4, g_ptr_array_unref);
+    nm_clear_pointer(&mutable->nameservers_6, g_ptr_array_unref);
 
     nm_clear_pointer(&mutable->domains_4, g_ptr_array_unref);
     nm_clear_pointer(&mutable->domains_6, g_ptr_array_unref);
@@ -1422,7 +1408,7 @@ _check_and_add_domain(GPtrArray **p_arr, const char *domain)
     return TRUE;
 }
 
-gconstpointer
+const char *const *
 nm_l3_config_data_get_nameservers(const NML3ConfigData *self, int addr_family, guint *out_len)
 {
     nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
@@ -1434,33 +1420,68 @@ nm_l3_config_data_get_nameservers(const NML3ConfigData *self, int addr_family, g
         return NULL;
     }
 
-    return _garray_inaddr_get(self->nameservers_x[NM_IS_IPv4(addr_family)], out_len);
+    return nm_strv_ptrarray_get_unsafe(self->nameservers_x[NM_IS_IPv4(addr_family)], out_len);
 }
 
 gboolean
-nm_l3_config_data_add_nameserver(NML3ConfigData                        *self,
-                                 int                                    addr_family,
-                                 gconstpointer /* (const NMIPAddr *) */ nameserver)
+nm_l3_config_data_add_nameserver(NML3ConfigData *self, int addr_family, const char *nameserver)
 {
+    GPtrArray **p_arr;
+
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
     nm_assert_addr_family(addr_family);
     nm_assert(nameserver);
 
-    return _garray_inaddr_add(&self->nameservers_x[NM_IS_IPv4(addr_family)],
-                              addr_family,
-                              nameserver);
+    if (NM_MORE_ASSERTS > 5) {
+        gs_free char *s_free = NULL;
+
+        nm_assert(
+            nm_streq0(nm_utils_dnsname_normalize(addr_family, nameserver, &s_free), nameserver));
+    }
+
+    p_arr = &self->nameservers_x[NM_IS_IPv4(addr_family)];
+
+    if (nm_strv_ptrarray_contains(*p_arr, nameserver))
+        return FALSE;
+
+    nm_strv_ptrarray_add_string_dup(nm_strv_ptrarray_ensure(p_arr), nameserver);
+    return TRUE;
+}
+
+gboolean
+nm_l3_config_data_add_nameserver_detail(NML3ConfigData *self,
+                                        int             addr_family,
+                                        gconstpointer   addr_bin,
+                                        const char     *server_name)
+{
+    gs_free char *s_free = NULL;
+    char         *s;
+    gsize         l;
+
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert_addr_family(addr_family);
+    nm_assert(addr_bin);
+
+    l = (NM_INET_ADDRSTRLEN + 2u) + (server_name ? strlen(server_name) : 0u);
+
+    s = nm_malloc_maybe_a(300, l, &s_free);
+
+    if (!nm_utils_dnsname_construct(addr_family, addr_bin, server_name, s, l))
+        nm_assert_not_reached();
+
+    return nm_l3_config_data_add_nameserver(self, addr_family, s);
 }
 
 gboolean
 nm_l3_config_data_clear_nameservers(NML3ConfigData *self, int addr_family)
 {
-    gs_unref_array GArray *old = NULL;
+    gs_unref_ptrarray GPtrArray *old = NULL;
 
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
     nm_assert_addr_family(addr_family);
 
     old = g_steal_pointer(&self->nameservers_x[NM_IS_IPv4(addr_family)]);
-    return (nm_g_array_len(old) > 0);
+    return (nm_g_ptr_array_len(old) > 0);
 }
 
 const in_addr_t *
@@ -2185,8 +2206,7 @@ _dedup_multi_index_cmp(const NML3ConfigData *a,
 
                 NM_CMP_DIRECT(ra.metric, rb.metric);
                 NM_CMP_DIRECT(ra.plen, rb.plen);
-                NM_CMP_RETURN_DIRECT(
-                    nm_utils_ip4_address_same_prefix_cmp(ra.network, rb.network, ra.plen));
+                NM_CMP_RETURN_DIRECT(nm_ip4_addr_same_prefix_cmp(ra.network, rb.network, ra.plen));
                 break;
             }
             case NMP_OBJECT_TYPE_IP6_ROUTE:
@@ -2197,7 +2217,7 @@ _dedup_multi_index_cmp(const NML3ConfigData *a,
                 NM_CMP_DIRECT(ra.metric, rb.metric);
                 NM_CMP_DIRECT(ra.plen, rb.plen);
                 NM_CMP_RETURN_DIRECT(
-                    nm_utils_ip6_address_same_prefix_cmp(&ra.network, &rb.network, ra.plen));
+                    nm_ip6_addr_same_prefix_cmp(&ra.network, &rb.network, ra.plen));
                 break;
             }
             default:
@@ -2274,7 +2294,6 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a,
     }
 
     for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
-        const int        addr_family = IS_IPv4 ? AF_INET : AF_INET6;
         const NMPObject *def_route_a = a->best_default_route_x[IS_IPv4];
         const NMPObject *def_route_b = b->best_default_route_x[IS_IPv4];
 
@@ -2298,8 +2317,7 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a,
 
                 NM_CMP_DIRECT(ra.metric, rb.metric);
                 NM_CMP_DIRECT(ra.plen, rb.plen);
-                NM_CMP_RETURN_DIRECT(
-                    nm_utils_ip4_address_same_prefix_cmp(ra.network, rb.network, ra.plen));
+                NM_CMP_RETURN_DIRECT(nm_ip4_addr_same_prefix_cmp(ra.network, rb.network, ra.plen));
             } else {
                 NMPlatformIP6Route ra = def_route_a->ip6_route;
                 NMPlatformIP6Route rb = def_route_b->ip6_route;
@@ -2307,16 +2325,15 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a,
                 NM_CMP_DIRECT(ra.metric, rb.metric);
                 NM_CMP_DIRECT(ra.plen, rb.plen);
                 NM_CMP_RETURN_DIRECT(
-                    nm_utils_ip6_address_same_prefix_cmp(&ra.network, &rb.network, ra.plen));
+                    nm_ip6_addr_same_prefix_cmp(&ra.network, &rb.network, ra.plen));
             }
         }
 
         if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_DNS)) {
             const NML3ConfigDatFlags FLAG = NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY(IS_IPv4);
 
-            NM_CMP_RETURN(_garray_inaddr_cmp(a->nameservers_x[IS_IPv4],
-                                             b->nameservers_x[IS_IPv4],
-                                             addr_family));
+            NM_CMP_RETURN(
+                nm_strv_ptrarray_cmp(a->nameservers_x[IS_IPv4], b->nameservers_x[IS_IPv4]));
             NM_CMP_RETURN(nm_strv_ptrarray_cmp(a->domains_x[IS_IPv4], b->domains_x[IS_IPv4]));
             NM_CMP_RETURN(nm_strv_ptrarray_cmp(a->searches_x[IS_IPv4], b->searches_x[IS_IPv4]));
             NM_CMP_RETURN(
@@ -2391,10 +2408,7 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a,
 /*****************************************************************************/
 
 static const NMPObject *
-_data_get_direct_route_for_host(const NML3ConfigData *self,
-                                int                   addr_family,
-                                gconstpointer         host,
-                                guint32               route_table)
+_data_get_direct_route_for_host(const NML3ConfigData *self, int addr_family, gconstpointer host)
 {
     const int                 IS_IPv4        = NM_IS_IPv4(addr_family);
     const NMPObject          *best_route_obj = NULL;
@@ -2422,13 +2436,7 @@ _data_get_direct_route_for_host(const NML3ConfigData *self,
         if (best_route && best_route->rx.plen > item->rx.plen)
             continue;
 
-        if (nm_platform_route_table_uncoerce(item->rx.table_coerced, TRUE) != route_table)
-            continue;
-
-        if (!nm_utils_ip_address_same_prefix(addr_family,
-                                             host,
-                                             item->rx.network_ptr,
-                                             item->rx.plen))
+        if (!nm_ip_addr_same_prefix(addr_family, host, item->rx.network_ptr, item->rx.plen))
             continue;
 
         if (best_route && best_route->rx.metric <= item->rx.metric)
@@ -2478,9 +2486,9 @@ nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, gboolea
         if (my_addr->plen == 0)
             continue;
 
-        network_4 = nm_utils_ip4_address_clear_host_address(my_addr->peer_address, my_addr->plen);
+        network_4 = nm_ip4_addr_clear_host_address(my_addr->peer_address, my_addr->plen);
 
-        if (nm_utils_ip4_address_is_zeronet(network_4)) {
+        if (nm_ip4_addr_is_zeronet(network_4)) {
             /* Kernel doesn't add device-routes for destinations that
              * start with 0.x.y.z. Skip them. */
             continue;
@@ -2555,12 +2563,10 @@ nm_l3_config_data_add_dependent_onlink_routes(NML3ConfigData *self, int addr_fam
         if (nm_ip_addr_is_null(addr_family, p_gateway))
             continue;
 
-        if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route_src)
-            || _data_get_direct_route_for_host(
-                self,
-                addr_family,
-                p_gateway,
-                nm_platform_route_table_uncoerce(route_src->rx.table_coerced, TRUE)))
+        if (NM_FLAGS_HAS(route_src->rx.r_rtm_flags, (unsigned) RTNH_F_ONLINK))
+            continue;
+
+        if (_data_get_direct_route_for_host(self, addr_family, p_gateway))
             continue;
 
         new_route = nmp_object_clone(obj_src, FALSE);
@@ -2672,9 +2678,9 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData       *self,
                         a6 = &addr_src->a6.peer_address;
                     plen = 128;
                 } else {
-                    a6   = nm_utils_ip6_address_clear_host_address(&a6_stack,
-                                                                 &addr_src->a6.address,
-                                                                 addr_src->a6.plen);
+                    a6   = nm_ip6_addr_clear_host_address(&a6_stack,
+                                                        &addr_src->a6.address,
+                                                        addr_src->a6.plen);
                     plen = addr_src->a6.plen;
                 }
 
@@ -2819,12 +2825,12 @@ _init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *co
 
         plen = nm_ip_route_get_prefix(s_route);
 
-        nm_utils_ipx_address_clear_host_address(addr_family, &network_bin, &network_bin, plen);
+        nm_ip_addr_clear_host_address(addr_family, &network_bin, &network_bin, plen);
 
         if (IS_IPv4) {
             r.r4 = (NMPlatformIP4Route){
                 .network    = network_bin.addr4,
-                .plen       = nm_ip_route_get_prefix(s_route),
+                .plen       = plen,
                 .gateway    = next_hop_bin.addr4,
                 .metric_any = metric_any,
                 .metric     = metric,
@@ -2834,7 +2840,7 @@ _init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *co
         } else {
             r.r6 = (NMPlatformIP6Route){
                 .network    = network_bin.addr6,
-                .plen       = nm_ip_route_get_prefix(s_route),
+                .plen       = plen,
                 .gateway    = next_hop_bin.addr6,
                 .metric_any = metric_any,
                 .metric     = metric,
@@ -2845,19 +2851,28 @@ _init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *co
 
         nm_utils_ip_route_attribute_to_platform(addr_family, s_route, &r.rx, -1);
 
+        if (IS_IPv4 && r.r4.gateway == 0) {
+            /* As far as kernel is concerned, an IPv4 without gateway cannot have
+             * the onlink flag set, we need to clear it. If we wouldn't clear it,
+             * then the address we would add in kernel would be entirely different than
+             * the one we create here (because the "onlink" flag is part of the
+             * identifier of a route, see nm_platform_ip4_route_cmp()).
+             *
+             * Note however that for ECMP routes we currently can only merge routes
+             * that agree in their onlink flag. So a route without gateway cannot
+             * merge with an onlink route that has a gateway. That needs fixing,
+             * by not treating the onlink flag as for the entire route, but allowing
+             * to merge ECMP routes with different onlink flag. And first, we need
+             * to track the onlink flag for the nexthop (NMPlatformIP4RtNextHop). */
+            r.r4.r_rtm_flags &= ~((unsigned) RTNH_F_ONLINK);
+        }
+
         nm_l3_config_data_add_route(self, addr_family, NULL, &r.rx);
     }
 
     nnameservers = nm_setting_ip_config_get_num_dns(s_ip);
-    for (i = 0; i < nnameservers; i++) {
-        const char *s;
-        NMIPAddr    ip;
-
-        s = nm_setting_ip_config_get_dns(s_ip, i);
-        if (!nm_utils_parse_inaddr_bin(addr_family, s, NULL, &ip))
-            continue;
-        nm_l3_config_data_add_nameserver(self, addr_family, &ip);
-    }
+    for (i = 0; i < nnameservers; i++)
+        nm_l3_config_data_add_nameserver(self, addr_family, nm_setting_ip_config_get_dns(s_ip, i));
 
     nsearches = nm_setting_ip_config_get_num_dns_searches(s_ip);
     for (i = 0; i < nsearches; i++) {
@@ -3023,6 +3038,107 @@ nm_l3_config_data_new_from_platform(NMDedupMultiIndex        *multi_idx,
 /*****************************************************************************/
 
 void
+nm_l3_config_data_hash_dns(const NML3ConfigData *l3cd,
+                           GChecksum            *sum,
+                           int                   addr_family,
+                           NMDnsIPConfigType     dns_ip_config_type)
+{
+    guint              i;
+    int                val;
+    const char *const *strarr;
+    const in_addr_t   *wins;
+    const char *const *domains;
+    const char *const *searches;
+    const char *const *options;
+    guint              num_nameservers;
+    guint              num_wins;
+    guint              num_domains;
+    guint              num_searches;
+    guint              num_options;
+    gboolean           empty = TRUE;
+
+    g_return_if_fail(l3cd);
+    g_return_if_fail(sum);
+
+    strarr = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num_nameservers);
+    for (i = 0; i < num_nameservers; i++) {
+        g_checksum_update(sum, (gpointer) strarr[i], strlen(strarr[i]));
+        empty = FALSE;
+    }
+
+    if (addr_family == AF_INET) {
+        wins = nm_l3_config_data_get_wins(l3cd, &num_wins);
+        for (i = 0; i < num_wins; i++) {
+            g_checksum_update(sum, (guint8 *) &wins[i], 4);
+            empty = FALSE;
+        }
+    }
+
+    domains = nm_l3_config_data_get_domains(l3cd, addr_family, &num_domains);
+    for (i = 0; i < num_domains; i++) {
+        g_checksum_update(sum, (const guint8 *) domains[i], strlen(domains[i]));
+        empty = FALSE;
+    }
+
+    searches = nm_l3_config_data_get_searches(l3cd, addr_family, &num_searches);
+    for (i = 0; i < num_searches; i++) {
+        g_checksum_update(sum, (const guint8 *) searches[i], strlen(searches[i]));
+        empty = FALSE;
+    }
+
+    options = nm_l3_config_data_get_dns_options(l3cd, addr_family, &num_options);
+    for (i = 0; i < num_options; i++) {
+        g_checksum_update(sum, (const guint8 *) options[i], strlen(options[i]));
+        empty = FALSE;
+    }
+
+    val = nm_l3_config_data_get_mdns(l3cd);
+    if (val != NM_SETTING_CONNECTION_MDNS_DEFAULT) {
+        g_checksum_update(sum, (const guint8 *) &val, sizeof(val));
+        empty = FALSE;
+    }
+
+    val = nm_l3_config_data_get_llmnr(l3cd);
+    if (val != NM_SETTING_CONNECTION_LLMNR_DEFAULT) {
+        g_checksum_update(sum, (const guint8 *) &val, sizeof(val));
+        empty = FALSE;
+    }
+
+    val = nm_l3_config_data_get_dns_over_tls(l3cd);
+    if (val != NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT) {
+        g_checksum_update(sum, (const guint8 *) &val, sizeof(val));
+        empty = FALSE;
+    }
+
+    if (!empty) {
+        int prio = 0;
+
+        /* In the DNS manager we rely on the fact that an empty (i.e. without
+         * any name server, domain, option, etc. parameters) configuration
+         * has a zero checksum. This is needed to avoid rewriting resolv.conf
+         * when not needed. Since the dns-type and the priority are always
+         * present, hash them only when the rest of configuration is not empty.
+         */
+        g_checksum_update(sum, (const guint8 *) &dns_ip_config_type, sizeof(dns_ip_config_type));
+        nm_l3_config_data_get_dns_priority(l3cd, addr_family, &prio);
+        g_checksum_update(sum, (const guint8 *) &prio, sizeof(prio));
+    }
+
+    /* FIXME(ip-config-checksum): the DNS priority should be considered relevant
+     * and added into the checksum as well, but this can't be done right now
+     * because in the DNS manager we rely on the fact that an empty
+     * configuration (i.e. just created) has a zero checksum. This is needed to
+     * avoid rewriting resolv.conf when there is no change.
+     *
+     * The DNS priority initial value depends on the connection type (VPN or
+     * not), so it's a bit difficult to add it to checksum maintaining the
+     * assumption of checksum(empty)=0
+     */
+}
+
+/*****************************************************************************/
+
+void
 nm_l3_config_data_merge(NML3ConfigData       *self,
                         const NML3ConfigData *src,
                         NML3ConfigMergeFlags  merge_flags,
@@ -3212,9 +3328,7 @@ nm_l3_config_data_merge(NML3ConfigData       *self,
         }
 
         if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS))
-            _garray_inaddr_merge(&self->nameservers_x[IS_IPv4],
-                                 src->nameservers_x[IS_IPv4],
-                                 addr_family);
+            _strv_ptrarray_merge(&self->nameservers_x[IS_IPv4], src->nameservers_x[IS_IPv4]);
 
         if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS))
             _strv_ptrarray_merge(&self->domains_x[IS_IPv4], src->domains_x[IS_IPv4]);