summary refs log tree commit diff
path: root/src/core/dns/nm-dns-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/dns/nm-dns-manager.c')
-rw-r--r--src/core/dns/nm-dns-manager.c784
1 files changed, 436 insertions, 348 deletions
diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c
index f0ec0c89..efbf847b 100644
--- a/src/core/dns/nm-dns-manager.c
+++ b/src/core/dns/nm-dns-manager.c
@@ -7,6 +7,8 @@
 
 #include "src/core/nm-default-daemon.h"
 
+#include "nm-dns-manager.h"
+
 #include <fcntl.h>
 #include <resolv.h>
 #include <stdlib.h>
@@ -22,22 +24,21 @@
 #include <libpsl.h>
 #endif
 
-#include "libnm-glib-aux/nm-str-buf.h"
-#include "nm-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
-#include "nm-dns-manager.h"
-#include "nm-ip4-config.h"
-#include "nm-ip6-config.h"
+#include "libnm-glib-aux/nm-str-buf.h"
+
 #include "NetworkManagerUtils.h"
+#include "devices/nm-device.h"
 #include "nm-config.h"
 #include "nm-dbus-object.h"
-#include "devices/nm-device.h"
-#include "nm-manager.h"
-
-#include "nm-dns-plugin.h"
 #include "nm-dns-dnsmasq.h"
+#include "nm-dns-plugin.h"
 #include "nm-dns-systemd-resolved.h"
 #include "nm-dns-unbound.h"
+#include "nm-ip-config.h"
+#include "nm-l3-config-data.h"
+#include "nm-manager.h"
+#include "nm-utils.h"
 
 #define HASH_LEN NM_UTILS_CHECKSUM_LENGTH_SHA1
 
@@ -60,11 +61,11 @@
 typedef enum { SR_SUCCESS, SR_NOTFOUND, SR_ERROR } SpawnResult;
 
 typedef struct {
-    GPtrArray * nameservers;
-    GPtrArray * searches;
-    GPtrArray * options;
+    GPtrArray  *nameservers;
+    GPtrArray  *searches;
+    GPtrArray  *options;
     const char *nis_domain;
-    GPtrArray * nis_servers;
+    GPtrArray  *nis_servers;
     NMTernary   has_trust_ad;
 } NMResolvConfData;
 
@@ -84,16 +85,18 @@ typedef struct {
     GHashTable *configs_dict;
     CList       configs_lst_head;
 
-    CList     ip_config_lst_head;
+    CList     ip_data_lst_head;
     GVariant *config_variant;
 
-    bool ip_config_lst_need_sort : 1;
+    bool ip_data_lst_need_sort : 1;
 
     bool configs_lst_need_sort : 1;
 
     bool dns_touched : 1;
     bool is_stopped : 1;
 
+    bool config_changed : 1;
+
     char *hostname;
     guint updates_queue;
 
@@ -101,9 +104,9 @@ typedef struct {
     guint8 prev_hash[HASH_LEN]; /* Hash when begin_updates() was called */
 
     NMDnsManagerResolvConfManager rc_manager;
-    char *                        mode;
-    NMDnsPlugin *                 sd_resolve_plugin;
-    NMDnsPlugin *                 plugin;
+    char                         *mode;
+    NMDnsPlugin                  *sd_resolve_plugin;
+    NMDnsPlugin                  *plugin;
 
     NMConfig *config;
 
@@ -159,11 +162,6 @@ NM_DEFINE_SINGLETON_GETTER(NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER
 
 /*****************************************************************************/
 
-static void
-_ip_config_dns_priority_changed(gpointer config, GParamSpec *pspec, NMDnsConfigIPData *ip_data);
-
-/*****************************************************************************/
-
 static gboolean
 domain_is_valid(const char *domain, gboolean check_public_suffix)
 {
@@ -208,6 +206,24 @@ static NM_UTILS_LOOKUP_STR_DEFINE(
 
 /*****************************************************************************/
 
+static int
+_dns_config_ip_data_get_dns_priority1(const NML3ConfigData *l3cd, int addr_family)
+{
+    int prio;
+
+    if (!nm_l3_config_data_get_dns_priority(l3cd, addr_family, &prio))
+        return 0;
+
+    nm_assert(prio != 0);
+    return prio;
+}
+
+static int
+_dns_config_ip_data_get_dns_priority(const NMDnsConfigIPData *ip_data)
+{
+    return _dns_config_ip_data_get_dns_priority1(ip_data->l3cd, ip_data->addr_family);
+}
+
 static void
 _ASSERT_dns_config_data(const NMDnsConfigData *data)
 {
@@ -221,9 +237,9 @@ _ASSERT_dns_config_ip_data(const NMDnsConfigIPData *ip_data)
 {
     nm_assert(ip_data);
     _ASSERT_dns_config_data(ip_data->data);
-    nm_assert(NM_IS_IP_CONFIG(ip_data->ip_config));
+    nm_assert(NM_IS_L3_CONFIG_DATA(ip_data->l3cd));
     nm_assert(c_list_contains(&ip_data->data->data_lst_head, &ip_data->data_lst));
-    nm_assert(ip_data->data->ifindex == nm_ip_config_get_ifindex(ip_data->ip_config));
+    nm_assert(ip_data->data->ifindex == nm_l3_config_data_get_ifindex(ip_data->l3cd));
 #if NM_MORE_ASSERTS > 5
     {
         gboolean has_default = FALSE;
@@ -243,37 +259,36 @@ _ASSERT_dns_config_ip_data(const NMDnsConfigIPData *ip_data)
         if (ip_data->domains.has_default_route_exclusive)
             nm_assert(ip_data->domains.has_default_route);
     }
+    nm_assert(_dns_config_ip_data_get_dns_priority(ip_data) != 0);
 #endif
 }
 
 static NMDnsConfigIPData *
-_dns_config_ip_data_new(NMDnsConfigData * data,
-                        NMIPConfig *      ip_config,
-                        NMDnsIPConfigType ip_config_type)
+_dns_config_ip_data_new(NMDnsConfigData      *data,
+                        int                   addr_family,
+                        gconstpointer         source_tag,
+                        const NML3ConfigData *l3cd,
+                        NMDnsIPConfigType     ip_config_type)
 {
     NMDnsConfigIPData *ip_data;
 
     _ASSERT_dns_config_data(data);
-    nm_assert(NM_IS_IP_CONFIG(ip_config));
+    nm_assert(NM_IS_L3_CONFIG_DATA(l3cd));
     nm_assert(ip_config_type != NM_DNS_IP_CONFIG_TYPE_REMOVED);
 
     ip_data  = g_slice_new(NMDnsConfigIPData);
     *ip_data = (NMDnsConfigIPData){
         .data           = data,
-        .ip_config      = g_object_ref(ip_config),
+        .source_tag     = source_tag,
+        .l3cd           = nm_l3_config_data_ref_and_seal(l3cd),
         .ip_config_type = ip_config_type,
+        .addr_family    = addr_family,
     };
     c_list_link_tail(&data->data_lst_head, &ip_data->data_lst);
-    c_list_link_tail(&NM_DNS_MANAGER_GET_PRIVATE(data->self)->ip_config_lst_head,
-                     &ip_data->ip_config_lst);
-
-    /* We also need to set priv->ip_config_lst_need_sort, but the caller will do that! */
+    c_list_link_tail(&NM_DNS_MANAGER_GET_PRIVATE(data->self)->ip_data_lst_head,
+                     &ip_data->ip_data_lst);
 
-    g_signal_connect(ip_config,
-                     NM_IS_IP4_CONFIG(ip_config) ? "notify::" NM_IP4_CONFIG_DNS_PRIORITY
-                                                 : "notify::" NM_IP6_CONFIG_DNS_PRIORITY,
-                     G_CALLBACK(_ip_config_dns_priority_changed),
-                     ip_data);
+    /* We also need to set priv->ip_data_lst_need_sort, but the caller will do that! */
 
     _ASSERT_dns_config_ip_data(ip_data);
     return ip_data;
@@ -285,35 +300,15 @@ _dns_config_ip_data_free(NMDnsConfigIPData *ip_data)
     _ASSERT_dns_config_ip_data(ip_data);
 
     c_list_unlink_stale(&ip_data->data_lst);
-    c_list_unlink_stale(&ip_data->ip_config_lst);
+    c_list_unlink_stale(&ip_data->ip_data_lst);
 
     g_free(ip_data->domains.search);
     g_strfreev(ip_data->domains.reverse);
 
-    g_signal_handlers_disconnect_by_func(ip_data->ip_config,
-                                         _ip_config_dns_priority_changed,
-                                         ip_data);
-
-    g_object_unref(ip_data->ip_config);
+    nm_l3_config_data_unref(ip_data->l3cd);
     nm_g_slice_free(ip_data);
 }
 
-static NMDnsConfigIPData *
-_dns_config_data_find_ip_config(NMDnsConfigData *data, NMIPConfig *ip_config)
-{
-    NMDnsConfigIPData *ip_data;
-
-    _ASSERT_dns_config_data(data);
-
-    c_list_for_each_entry (ip_data, &data->data_lst_head, data_lst) {
-        _ASSERT_dns_config_ip_data(ip_data);
-
-        if (ip_data->ip_config == ip_config)
-            return ip_data;
-    }
-    return NULL;
-}
-
 static void
 _dns_config_data_free(NMDnsConfigData *data)
 {
@@ -325,14 +320,13 @@ _dns_config_data_free(NMDnsConfigData *data)
 }
 
 static int
-_mgr_get_ip_config_lst_cmp(const CList *a_lst, const CList *b_lst, const void *user_data)
+_mgr_get_ip_data_lst_cmp(const CList *a_lst, const CList *b_lst, const void *user_data)
 {
-    const NMDnsConfigIPData *a = c_list_entry(a_lst, NMDnsConfigIPData, ip_config_lst);
-    const NMDnsConfigIPData *b = c_list_entry(b_lst, NMDnsConfigIPData, ip_config_lst);
+    const NMDnsConfigIPData *a = c_list_entry(a_lst, NMDnsConfigIPData, ip_data_lst);
+    const NMDnsConfigIPData *b = c_list_entry(b_lst, NMDnsConfigIPData, ip_data_lst);
 
     /* Configurations with lower priority value first */
-    NM_CMP_DIRECT(nm_ip_config_get_dns_priority(a->ip_config),
-                  nm_ip_config_get_dns_priority(b->ip_config));
+    NM_CMP_DIRECT(_dns_config_ip_data_get_dns_priority(a), _dns_config_ip_data_get_dns_priority(b));
 
     /* Sort according to type (descendingly) */
     NM_CMP_FIELD(b, a, ip_config_type);
@@ -341,16 +335,16 @@ _mgr_get_ip_config_lst_cmp(const CList *a_lst, const CList *b_lst, const void *u
 }
 
 static CList *
-_mgr_get_ip_config_lst_head(NMDnsManager *self)
+_mgr_get_ip_data_lst_head(NMDnsManager *self)
 {
     NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
 
-    if (G_UNLIKELY(priv->ip_config_lst_need_sort)) {
-        priv->ip_config_lst_need_sort = FALSE;
-        c_list_sort(&priv->ip_config_lst_head, _mgr_get_ip_config_lst_cmp, NULL);
+    if (G_UNLIKELY(priv->ip_data_lst_need_sort)) {
+        priv->ip_data_lst_need_sort = FALSE;
+        c_list_sort(&priv->ip_data_lst_head, _mgr_get_ip_data_lst_cmp, NULL);
     }
 
-    return &priv->ip_config_lst_head;
+    return &priv->ip_data_lst_head;
 }
 
 static int
@@ -382,7 +376,7 @@ NMDnsPlugin *
 nm_dns_manager_get_systemd_resolved(NMDnsManager *self)
 {
     NMDnsManagerPrivate *priv;
-    NMDnsPlugin *        plugin = NULL;
+    NMDnsPlugin         *plugin = NULL;
 
     g_return_val_if_fail(NM_IS_DNS_MANAGER(self), FALSE);
 
@@ -430,28 +424,33 @@ add_dns_option_item(GPtrArray *array, const char *str)
 }
 
 static void
-add_dns_domains(GPtrArray *       array,
-                const NMIPConfig *ip_config,
-                gboolean          include_routing,
-                gboolean          dup)
+add_dns_domains(GPtrArray            *array,
+                int                   addr_family,
+                const NML3ConfigData *l3cd,
+                gboolean              include_routing,
+                gboolean              dup)
 {
-    guint       num_domains, num_searches, i;
-    const char *str;
+    const char *const *domains;
+    const char *const *searches;
+    guint              num_domains;
+    guint              num_searches;
+    guint              i;
+    const char        *str;
 
-    num_domains  = nm_ip_config_get_num_domains(ip_config);
-    num_searches = nm_ip_config_get_num_searches(ip_config);
+    domains  = nm_l3_config_data_get_domains(l3cd, addr_family, &num_domains);
+    searches = nm_l3_config_data_get_searches(l3cd, addr_family, &num_searches);
 
     for (i = 0; i < num_searches; i++) {
-        str = nm_ip_config_get_search(ip_config, i);
+        str = searches[i];
         if (!include_routing && domain_is_routing(str))
             continue;
         if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE))
             continue;
         add_string_item(array, str, dup);
     }
-    if (num_domains > 1 || !num_searches) {
+    if (num_domains > 1 || num_searches == 0) {
         for (i = 0; i < num_domains; i++) {
-            str = nm_ip_config_get_domain(ip_config, i);
+            str = domains[i];
             if (!include_routing && domain_is_routing(str))
                 continue;
             if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE))
@@ -462,26 +461,24 @@ add_dns_domains(GPtrArray *       array,
 }
 
 static void
-merge_one_ip_config(NMResolvConfData *rc, int ifindex, const NMIPConfig *ip_config)
+merge_one_l3cd(NMResolvConfData *rc, int addr_family, int ifindex, const NML3ConfigData *l3cd)
 {
-    int      addr_family;
-    char     buf[NM_UTILS_INET_ADDRSTRLEN + 50];
-    gboolean has_trust_ad;
-    guint    num_nameservers;
-    guint    num;
-    guint    i;
+    char               buf[NM_UTILS_INET_ADDRSTRLEN + 50];
+    gboolean           has_trust_ad;
+    guint              num_nameservers;
+    guint              num;
+    guint              i;
+    gconstpointer      nameservers;
+    const char *const *strv;
 
-    addr_family = nm_ip_config_get_addr_family(ip_config);
+    nm_assert(ifindex == nm_l3_config_data_get_ifindex(l3cd));
 
-    nm_assert_addr_family(addr_family);
-    nm_assert(ifindex > 0);
-    nm_assert(ifindex == nm_ip_config_get_ifindex(ip_config));
-
-    num_nameservers = nm_ip_config_get_num_nameservers(ip_config);
+    nameservers = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num_nameservers);
     for (i = 0; i < num_nameservers; i++) {
         const NMIPAddr *addr;
 
-        addr = nm_ip_config_get_nameserver(ip_config, i);
+        addr = nm_ip_addr_from_packed_array(addr_family, nameservers, i);
+
         if (addr_family == AF_INET)
             nm_utils_inet_ntop(addr_family, addr, buf);
         else if (IN6_IS_ADDR_V4MAPPED(addr))
@@ -502,22 +499,23 @@ merge_one_ip_config(NMResolvConfData *rc, int ifindex, const NMIPConfig *ip_conf
         add_string_item(rc->nameservers, buf, TRUE);
     }
 
-    add_dns_domains(rc->searches, ip_config, FALSE, TRUE);
+    add_dns_domains(rc->searches, addr_family, l3cd, FALSE, TRUE);
 
     has_trust_ad = FALSE;
-    num          = nm_ip_config_get_num_dns_options(ip_config);
+    strv         = nm_l3_config_data_get_dns_options(l3cd, addr_family, &num);
     for (i = 0; i < num; i++) {
-        const char *option = nm_ip_config_get_dns_option(ip_config, i);
+        const char *option = strv[i];
 
         if (nm_streq(option, NM_SETTING_DNS_OPTION_TRUST_AD)) {
             has_trust_ad = TRUE;
             continue;
         }
-        add_dns_option_item(rc->options, nm_ip_config_get_dns_option(ip_config, i));
+        add_dns_option_item(rc->options, option);
     }
+
     if (num_nameservers == 0) {
-        /* If the @ip_config contributes no DNS servers, ignore whether trust-ad is set or unset
-         * for this @ip_config. */
+        /* If the @l3cd contributes no DNS servers, ignore whether trust-ad is set or unset
+         * for this @l3cd. */
     } else if (has_trust_ad) {
         /* We only set has_trust_ad to TRUE, if all IP configs agree (or don't contribute).
          * Once set to FALSE, it doesn't get reset. */
@@ -527,20 +525,17 @@ merge_one_ip_config(NMResolvConfData *rc, int ifindex, const NMIPConfig *ip_conf
         rc->has_trust_ad = NM_TERNARY_FALSE;
 
     if (addr_family == AF_INET) {
-        const NMIP4Config *ip4_config = (const NMIP4Config *) ip_config;
+        const in_addr_t *nis_servers;
+        const char      *nis_domain;
 
-        /* NIS stuff */
-        num = nm_ip4_config_get_num_nis_servers(ip4_config);
-        for (i = 0; i < num; i++) {
-            add_string_item(rc->nis_servers,
-                            _nm_utils_inet4_ntop(nm_ip4_config_get_nis_server(ip4_config, i), buf),
-                            TRUE);
-        }
+        nis_servers = nm_l3_config_data_get_nis_servers(l3cd, &num);
+        for (i = 0; i < num; i++)
+            add_string_item(rc->nis_servers, _nm_utils_inet4_ntop(nis_servers[i], buf), TRUE);
 
-        if (nm_ip4_config_get_nis_domain(ip4_config)) {
+        if ((nis_domain = nm_l3_config_data_get_nis_domain(l3cd))) {
             /* FIXME: handle multiple domains */
             if (!rc->nis_domain)
-                rc->nis_domain = nm_ip4_config_get_nis_domain(ip4_config);
+                rc->nis_domain = nis_domain;
         }
     }
 }
@@ -548,7 +543,7 @@ merge_one_ip_config(NMResolvConfData *rc, int ifindex, const NMIPConfig *ip_conf
 static GPid
 run_netconfig(NMDnsManager *self, GError **error, int *stdin_fd)
 {
-    char *        argv[5];
+    char         *argv[5];
     gs_free char *tmp = NULL;
     GPid          pid = -1;
 
@@ -586,9 +581,9 @@ netconfig_construct_str(NMDnsManager *self, GString *str, const char *key, const
 }
 
 static void
-netconfig_construct_strv(NMDnsManager *     self,
-                         GString *          str,
-                         const char *       key,
+netconfig_construct_strv(NMDnsManager      *self,
+                         GString           *str,
+                         const char        *key,
                          const char *const *values)
 {
     if (values) {
@@ -600,18 +595,18 @@ netconfig_construct_strv(NMDnsManager *     self,
 }
 
 static SpawnResult
-dispatch_netconfig(NMDnsManager *     self,
+dispatch_netconfig(NMDnsManager      *self,
                    const char *const *searches,
                    const char *const *nameservers,
-                   const char *       nis_domain,
+                   const char        *nis_domain,
                    const char *const *nis_servers,
-                   GError **          error)
+                   GError           **error)
 {
-    GPid                 pid;
-    int                  fd;
-    int                  errsv;
-    int                  status;
-    gssize               l;
+    GPid                          pid;
+    int                           fd;
+    int                           errsv;
+    int                           status;
+    gssize                        l;
     nm_auto_free_gstring GString *str = NULL;
 
     pid = run_netconfig(self, error, &fd);
@@ -768,11 +763,11 @@ write_resolv_conf_contents(FILE *f, const char *content, GError **error)
 }
 
 static gboolean
-write_resolv_conf(FILE *             f,
+write_resolv_conf(FILE              *f,
                   const char *const *searches,
                   const char *const *nameservers,
                   const char *const *options,
-                  GError **          error)
+                  GError           **error)
 {
     gs_free char *content = NULL;
 
@@ -782,17 +777,17 @@ write_resolv_conf(FILE *             f,
 
 static SpawnResult
 dispatch_resolvconf(NMDnsManager *self,
-                    char **       searches,
-                    char **       nameservers,
-                    char **       options,
-                    GError **     error)
+                    char        **searches,
+                    char        **nameservers,
+                    char        **options,
+                    GError      **error)
 {
     gs_free char *cmd = NULL;
-    FILE *        f;
+    FILE         *f;
     gboolean      success = FALSE;
     int           errsv;
     int           err;
-    char *        argv[] = {RESOLVCONF_PATH, "-d", "NetworkManager", NULL};
+    char         *argv[] = {RESOLVCONF_PATH, "-d", "NetworkManager", NULL};
     int           status;
 
     if (!g_file_test(RESOLVCONF_PATH, G_FILE_TEST_IS_EXECUTABLE)) {
@@ -883,13 +878,13 @@ _read_link_cached(const char *path, gboolean *is_cached, char **cached)
 #define NO_STUB_RESOLV_CONF NMRUNDIR "/no-stub-resolv.conf"
 
 static void
-update_resolv_conf_no_stub(NMDnsManager *     self,
+update_resolv_conf_no_stub(NMDnsManager      *self,
                            const char *const *searches,
                            const char *const *nameservers,
                            const char *const *options)
 {
     gs_free char *content = NULL;
-    GError *      local   = NULL;
+    GError       *local   = NULL;
 
     content = create_resolv_conf(searches, nameservers, options);
 
@@ -903,14 +898,14 @@ update_resolv_conf_no_stub(NMDnsManager *     self,
 }
 
 static SpawnResult
-update_resolv_conf(NMDnsManager *                self,
-                   const char *const *           searches,
-                   const char *const *           nameservers,
-                   const char *const *           options,
-                   GError **                     error,
+update_resolv_conf(NMDnsManager                 *self,
+                   const char *const            *searches,
+                   const char *const            *nameservers,
+                   const char *const            *options,
+                   GError                      **error,
                    NMDnsManagerResolvConfManager rc_manager)
 {
-    FILE *        f;
+    FILE         *f;
     gboolean      success;
     gs_free char *content           = NULL;
     SpawnResult   write_file_result = SR_SUCCESS;
@@ -923,10 +918,10 @@ update_resolv_conf(NMDnsManager *                self,
     if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE
         || (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK
             && !_read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link))) {
-        gs_free char *     rc_path_syml = NULL;
+        gs_free char      *rc_path_syml = NULL;
         nm_auto_free char *rc_path_real = NULL;
-        const char *       rc_path      = _PATH_RESCONF;
-        GError *           local        = NULL;
+        const char        *rc_path      = _PATH_RESCONF;
+        GError            *local        = NULL;
 
         if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) {
             rc_path_real = realpath(_PATH_RESCONF, NULL);
@@ -1110,7 +1105,7 @@ static void
 compute_hash(NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[static HASH_LEN])
 {
     nm_auto_free_checksum GChecksum *sum = NULL;
-    NMDnsConfigIPData *              ip_data;
+    NMDnsConfigIPData               *ip_data;
 
     sum = g_checksum_new(G_CHECKSUM_SHA1);
     nm_assert(HASH_LEN == g_checksum_type_get_length(G_CHECKSUM_SHA1));
@@ -1122,9 +1117,9 @@ compute_hash(NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[
 
         /* FIXME(ip-config-checksum): this relies on the fact that an IP
          * configuration without DNS parameters gives a zero checksum. */
-        head = _mgr_get_ip_config_lst_head(self);
-        c_list_for_each_entry (ip_data, head, ip_config_lst)
-            nm_ip_config_hash(ip_data->ip_config, sum, TRUE);
+        head = _mgr_get_ip_data_lst_head(self);
+        c_list_for_each_entry (ip_data, head, ip_data_lst)
+            nm_ip_config_dns_hash(ip_data->l3cd, sum, ip_data->addr_family);
     }
 
     nm_utils_checksum_get_digest_len(sum, buffer, HASH_LEN);
@@ -1172,19 +1167,21 @@ merge_global_dns_config(NMResolvConfData *rc, NMGlobalDnsConfig *global_conf)
 }
 
 static const char *
-get_nameserver_list(const NMIPConfig *config, NMStrBuf *tmp_strbuf)
+get_nameserver_list(int addr_family, const NML3ConfigData *l3cd, NMStrBuf *tmp_strbuf)
 {
-    char  buf[NM_UTILS_INET_ADDRSTRLEN];
-    int   addr_family;
-    guint num;
-    guint i;
+    char          buf[NM_UTILS_INET_ADDRSTRLEN];
+    guint         num;
+    guint         i;
+    gconstpointer nameservers;
 
     nm_str_buf_reset(tmp_strbuf);
 
-    addr_family = nm_ip_config_get_addr_family(config);
-    num         = nm_ip_config_get_num_nameservers(config);
+    nameservers = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num);
     for (i = 0; i < num; i++) {
-        nm_utils_inet_ntop(addr_family, nm_ip_config_get_nameserver(config, i), buf);
+        const NMIPAddr *addr;
+
+        addr = nm_ip_addr_from_packed_array(addr_family, nameservers, i);
+        nm_utils_inet_ntop(addr_family, addr->addr_ptr, buf);
         if (i > 0)
             nm_str_buf_append_c(tmp_strbuf, ' ');
         nm_str_buf_append(tmp_strbuf, buf);
@@ -1203,22 +1200,22 @@ _ptrarray_to_strv(GPtrArray *parray)
 }
 
 static void
-_collect_resolv_conf_data(NMDnsManager *     self,
+_collect_resolv_conf_data(NMDnsManager      *self,
                           NMGlobalDnsConfig *global_config,
-                          char ***           out_searches,
-                          char ***           out_options,
-                          char ***           out_nameservers,
-                          char ***           out_nis_servers,
-                          const char **      out_nis_domain)
+                          char            ***out_searches,
+                          char            ***out_options,
+                          char            ***out_nameservers,
+                          char            ***out_nis_servers,
+                          const char       **out_nis_domain)
 {
     NMDnsManagerPrivate *priv;
     NMResolvConfData     rc = {
-        .nameservers  = g_ptr_array_new(),
-        .searches     = g_ptr_array_new(),
-        .options      = g_ptr_array_new(),
-        .nis_domain   = NULL,
-        .nis_servers  = g_ptr_array_new(),
-        .has_trust_ad = NM_TERNARY_DEFAULT,
+            .nameservers  = g_ptr_array_new(),
+            .searches     = g_ptr_array_new(),
+            .options      = g_ptr_array_new(),
+            .nis_domain   = NULL,
+            .nis_servers  = g_ptr_array_new(),
+            .has_trust_ad = NM_TERNARY_DEFAULT,
     };
 
     priv = NM_DNS_MANAGER_GET_PRIVATE(self);
@@ -1227,36 +1224,39 @@ _collect_resolv_conf_data(NMDnsManager *     self,
         merge_global_dns_config(&rc, global_config);
     else {
         nm_auto_str_buf NMStrBuf tmp_strbuf = NM_STR_BUF_INIT(0, FALSE);
-        int                      prio;
         int                      first_prio = 0;
         const NMDnsConfigIPData *ip_data;
-        const CList *            head;
+        const CList             *head;
         gboolean                 is_first = TRUE;
 
-        head = _mgr_get_ip_config_lst_head(self);
-        c_list_for_each_entry (ip_data, head, ip_config_lst) {
+        head = _mgr_get_ip_data_lst_head(self);
+        c_list_for_each_entry (ip_data, head, ip_data_lst) {
             gboolean skip = FALSE;
+            int      dns_priority;
 
             _ASSERT_dns_config_ip_data(ip_data);
 
-            prio = nm_ip_config_get_dns_priority(ip_data->ip_config);
+            if (!nm_l3_config_data_get_dns_priority(ip_data->l3cd,
+                                                    ip_data->addr_family,
+                                                    &dns_priority))
+                nm_assert_not_reached();
 
             if (is_first) {
                 is_first   = FALSE;
-                first_prio = prio;
-            } else if (first_prio < 0 && first_prio != prio)
+                first_prio = dns_priority;
+            } else if (first_prio < 0 && first_prio != dns_priority)
                 skip = TRUE;
 
             _LOGT("config: %8d %-7s v%c %-5d %s: %s",
-                  prio,
+                  dns_priority,
                   _config_type_to_string(ip_data->ip_config_type),
-                  nm_utils_addr_family_to_char(nm_ip_config_get_addr_family(ip_data->ip_config)),
+                  nm_utils_addr_family_to_char(ip_data->addr_family),
                   ip_data->data->ifindex,
                   skip ? "<SKIP>" : "",
-                  get_nameserver_list(ip_data->ip_config, &tmp_strbuf));
+                  get_nameserver_list(ip_data->addr_family, ip_data->l3cd, &tmp_strbuf));
 
             if (!skip)
-                merge_one_ip_config(&rc, ip_data->data->ifindex, ip_data->ip_config);
+                merge_one_l3cd(&rc, ip_data->addr_family, ip_data->data->ifindex, ip_data->l3cd);
         }
     }
 
@@ -1292,27 +1292,32 @@ _collect_resolv_conf_data(NMDnsManager *     self,
 /*****************************************************************************/
 
 static char **
-get_ip_rdns_domains(NMIPConfig *ip_config)
+get_ip_rdns_domains(int addr_family, const NML3ConfigData *l3cd)
 {
-    int                        addr_family = nm_ip_config_get_addr_family(ip_config);
-    char **                    strv;
-    GPtrArray *                domains;
-    NMDedupMultiIter           ipconf_iter;
-    const NMPlatformIPAddress *address;
-    const NMPlatformIPRoute *  route;
-
-    nm_assert_addr_family(addr_family);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
+    char           **strv;
+    GPtrArray       *domains;
+    NMDedupMultiIter ipconf_iter;
+    const NMPObject *obj;
 
     domains = g_ptr_array_sized_new(5);
 
-    nm_ip_config_iter_ip_address_for_each (&ipconf_iter, ip_config, &address) {
+    nm_l3_config_data_iter_obj_for_each (&ipconf_iter,
+                                         l3cd,
+                                         &obj,
+                                         NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
         nm_utils_get_reverse_dns_domains_ip(addr_family,
-                                            address->address_ptr,
-                                            address->plen,
+                                            NMP_OBJECT_CAST_IP_ADDRESS(obj)->address_ptr,
+                                            NMP_OBJECT_CAST_IP_ADDRESS(obj)->plen,
                                             domains);
     }
 
-    nm_ip_config_iter_ip_route_for_each (&ipconf_iter, ip_config, &route) {
+    nm_l3_config_data_iter_obj_for_each (&ipconf_iter,
+                                         l3cd,
+                                         &obj,
+                                         NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
+        const NMPlatformIPRoute *route = NMP_OBJECT_CAST_IP_ROUTE(obj);
+
         if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route)) {
             nm_utils_get_reverse_dns_domains_ip(addr_family,
                                                 route->network_ptr,
@@ -1345,11 +1350,11 @@ _domain_track_get_priority(GHashTable *ht, const char *domain, int *out_priority
 
 /* Check if the domain is shadowed by a parent domain with more negative priority */
 static gboolean
-_domain_track_is_shadowed(GHashTable * ht,
-                          const char * domain,
+_domain_track_is_shadowed(GHashTable  *ht,
+                          const char  *domain,
                           int          priority,
                           const char **out_parent,
-                          int *        out_parent_priority)
+                          int         *out_parent_priority)
 {
     char *parent;
     int   parent_priority;
@@ -1388,18 +1393,18 @@ _domain_track_is_shadowed(GHashTable * ht,
 static void
 _mgr_configs_data_construct(NMDnsManager *self)
 {
-    NMDnsConfigIPData *ip_data;
+    NMDnsConfigIPData             *ip_data;
     gs_unref_hashtable GHashTable *ht               = NULL;
     gs_unref_hashtable GHashTable *wildcard_entries = NULL;
-    CList *                        head;
+    CList                         *head;
     int                            prev_priority = G_MININT;
 
-    head = _mgr_get_ip_config_lst_head(self);
+    head = _mgr_get_ip_data_lst_head(self);
 
 #if NM_MORE_ASSERTS
     /* we call _mgr_configs_data_clear() at the end of update. We
      * don't expect any domain settings here. */
-    c_list_for_each_entry (ip_data, head, ip_config_lst) {
+    c_list_for_each_entry (ip_data, head, ip_data_lst) {
         nm_assert(!ip_data->domains.search);
         nm_assert(!ip_data->domains.reverse);
         nm_assert(!ip_data->domains.has_default_route_explicit);
@@ -1408,15 +1413,17 @@ _mgr_configs_data_construct(NMDnsManager *self)
     }
 #endif
 
-    c_list_for_each_entry (ip_data, head, ip_config_lst) {
-        NMIPConfig *ip_config    = ip_data->ip_config;
-        gboolean    add_wildcard = FALSE;
+    c_list_for_each_entry (ip_data, head, ip_data_lst) {
+        gboolean add_wildcard = FALSE;
+        guint    num;
 
-        if (!nm_ip_config_get_num_nameservers(ip_config))
+        nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &num);
+        if (num == 0)
             continue;
-        if (nm_ip_config_best_default_route_get(ip_config))
+        if (nm_l3_config_data_get_best_default_route(ip_data->l3cd, ip_data->addr_family)) {
+            /* FIXME(l3cfg): the best-default route of a l3cd is not significant! */
             add_wildcard = TRUE;
-        else {
+        } else {
             /* If a VPN has never-default=no but doesn't get a default
              * route (this can happen for example when the server
              * pushes routes with openconnect), and there are no
@@ -1424,9 +1431,10 @@ _mgr_configs_data_construct(NMDnsManager *self)
              * by the server would be unused. It is preferable in this
              * case to use the VPN DNS server for all queries. */
             if (ip_data->ip_config_type == NM_DNS_IP_CONFIG_TYPE_VPN
-                && !nm_ip_config_get_never_default(ip_data->ip_config)
-                && nm_ip_config_get_num_searches(ip_data->ip_config) == 0
-                && nm_ip_config_get_num_domains(ip_data->ip_config) == 0)
+                && nm_l3_config_data_get_never_default(ip_data->l3cd, ip_data->addr_family)
+                       == NM_TERNARY_FALSE
+                && !nm_l3_config_data_get_searches(ip_data->l3cd, ip_data->addr_family, &num)
+                && !nm_l3_config_data_get_domains(ip_data->l3cd, ip_data->addr_family, &num))
                 add_wildcard = TRUE;
         }
 
@@ -1437,29 +1445,33 @@ _mgr_configs_data_construct(NMDnsManager *self)
         }
     }
 
-    c_list_for_each_entry (ip_data, head, ip_config_lst) {
-        NMIPConfig * ip_config = ip_data->ip_config;
-        int          priority;
-        const char **domains;
-        guint        n_searches;
-        guint        n_domains;
-        guint        num_dom1;
-        guint        num_dom2;
-        guint        n_domains_allocated;
-        guint        i;
-        gboolean     has_default_route_maybe    = FALSE;
-        gboolean     has_default_route_explicit = FALSE;
-        gboolean     has_default_route_auto     = FALSE;
-
-        if (!nm_ip_config_get_num_nameservers(ip_config))
+    c_list_for_each_entry (ip_data, head, ip_data_lst) {
+        int                priority;
+        const char       **domains;
+        const char *const *strv_searches;
+        const char *const *strv_domains;
+        guint              n_searches;
+        guint              n_domains;
+        guint              num_dom1;
+        guint              num_dom2;
+        guint              n_domains_allocated;
+        guint              i;
+        gboolean           has_default_route_maybe    = FALSE;
+        gboolean           has_default_route_explicit = FALSE;
+        gboolean           has_default_route_auto     = FALSE;
+        guint              num;
+
+        nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &num);
+        if (num == 0)
             continue;
 
-        n_searches = nm_ip_config_get_num_searches(ip_config);
-        n_domains  = nm_ip_config_get_num_domains(ip_config);
+        strv_searches =
+            nm_l3_config_data_get_searches(ip_data->l3cd, ip_data->addr_family, &n_searches);
+        strv_domains =
+            nm_l3_config_data_get_domains(ip_data->l3cd, ip_data->addr_family, &n_domains);
 
-        priority = nm_ip_config_get_dns_priority(ip_config);
+        priority = _dns_config_ip_data_get_dns_priority(ip_data);
 
-        nm_assert(priority != 0);
         nm_assert(prev_priority <= priority);
         prev_priority = priority;
 
@@ -1487,10 +1499,10 @@ _mgr_configs_data_construct(NMDnsManager *self)
         /* searches are preferred over domains */
         if (n_searches > 0) {
             for (i = 0; i < n_searches; i++)
-                domains[num_dom1++] = nm_ip_config_get_search(ip_config, i);
+                domains[num_dom1++] = strv_searches[i];
         } else {
             for (i = 0; i < n_domains; i++)
-                domains[num_dom1++] = nm_ip_config_get_domain(ip_config, i);
+                domains[num_dom1++] = strv_domains[i];
         }
 
         nm_assert(num_dom1 < n_domains_allocated);
@@ -1578,8 +1590,8 @@ _mgr_configs_data_construct(NMDnsManager *self)
 
         nm_assert(!ip_data->domains.search);
         nm_assert(!ip_data->domains.reverse);
-        ip_data->domains.search                     = domains;
-        ip_data->domains.reverse                    = get_ip_rdns_domains(ip_config);
+        ip_data->domains.search  = domains;
+        ip_data->domains.reverse = get_ip_rdns_domains(ip_data->addr_family, ip_data->l3cd);
         ip_data->domains.has_default_route_explicit = has_default_route_explicit;
         ip_data->domains.has_default_route_exclusive =
             has_default_route_explicit || (priority < 0 && has_default_route_auto);
@@ -1609,10 +1621,10 @@ static void
 _mgr_configs_data_clear(NMDnsManager *self)
 {
     NMDnsConfigIPData *ip_data;
-    CList *            head;
+    CList             *head;
 
-    head = _mgr_get_ip_config_lst_head(self);
-    c_list_for_each_entry (ip_data, head, ip_config_lst) {
+    head = _mgr_get_ip_data_lst_head(self);
+    c_list_for_each_entry (ip_data, head, ip_data_lst) {
         nm_clear_g_free(&ip_data->domains.search);
         nm_clear_pointer(&ip_data->domains.reverse, g_strfreev);
         ip_data->domains.has_default_route_explicit  = FALSE;
@@ -1626,23 +1638,25 @@ _mgr_configs_data_clear(NMDnsManager *self)
 static gboolean
 update_dns(NMDnsManager *self, gboolean no_caching, gboolean force_emit, GError **error)
 {
-    NMDnsManagerPrivate *priv                = NM_DNS_MANAGER_GET_PRIVATE(self);
-    const char *         nis_domain          = NULL;
-    gs_strfreev char **  searches            = NULL;
-    gs_strfreev char **  options             = NULL;
-    gs_strfreev char **  nameservers         = NULL;
-    gs_strfreev char **  nis_servers         = NULL;
-    gboolean             caching             = FALSE;
-    gboolean             do_update           = TRUE;
-    gboolean             resolv_conf_updated = FALSE;
-    SpawnResult          result              = SR_SUCCESS;
-    NMConfigData *       data;
-    NMGlobalDnsConfig *  global_config;
+    NMDnsManagerPrivate  *priv                = NM_DNS_MANAGER_GET_PRIVATE(self);
+    const char           *nis_domain          = NULL;
+    gs_strfreev char    **searches            = NULL;
+    gs_strfreev char    **options             = NULL;
+    gs_strfreev char    **nameservers         = NULL;
+    gs_strfreev char    **nis_servers         = NULL;
+    gboolean              caching             = FALSE;
+    gboolean              do_update           = TRUE;
+    gboolean              resolv_conf_updated = FALSE;
+    SpawnResult           result              = SR_SUCCESS;
+    NMConfigData         *data;
+    NMGlobalDnsConfig    *global_config;
     gs_free_error GError *local_error   = NULL;
     GError **const        p_local_error = error ? &local_error : NULL;
 
     nm_assert(!error || !*error);
 
+    priv->config_changed = FALSE;
+
     if (priv->is_stopped) {
         _LOGD("update-dns: not updating resolv.conf (is stopped)");
         return TRUE;
@@ -1680,15 +1694,15 @@ update_dns(NMDnsManager *self, gboolean no_caching, gboolean force_emit, GError
     if (priv->sd_resolve_plugin) {
         nm_dns_plugin_update(priv->sd_resolve_plugin,
                              global_config,
-                             _mgr_get_ip_config_lst_head(self),
+                             _mgr_get_ip_data_lst_head(self),
                              priv->hostname,
                              NULL);
     }
 
     /* Let any plugins do their thing first */
     if (priv->plugin) {
-        NMDnsPlugin * plugin               = priv->plugin;
-        const char *  plugin_name          = nm_dns_plugin_get_name(plugin);
+        NMDnsPlugin          *plugin       = priv->plugin;
+        const char           *plugin_name  = nm_dns_plugin_get_name(plugin);
         gs_free_error GError *plugin_error = NULL;
 
         if (nm_dns_plugin_is_caching(plugin)) {
@@ -1702,7 +1716,7 @@ update_dns(NMDnsManager *self, gboolean no_caching, gboolean force_emit, GError
         _LOGD("update-dns: updating plugin %s", plugin_name);
         if (!nm_dns_plugin_update(plugin,
                                   global_config,
-                                  _mgr_get_ip_config_lst_head(self),
+                                  _mgr_get_ip_data_lst_head(self),
                                   priv->hostname,
                                   &plugin_error)) {
             _LOGW("update-dns: plugin %s update failed: %s", plugin_name, plugin_error->message);
@@ -1834,51 +1848,107 @@ plugin_skip:;
 
 /*****************************************************************************/
 
-static void
-_ip_config_dns_priority_changed(gpointer config, GParamSpec *pspec, NMDnsConfigIPData *ip_data)
-{
-    _ASSERT_dns_config_ip_data(ip_data);
-
-    NM_DNS_MANAGER_GET_PRIVATE(ip_data->data->self)->ip_config_lst_need_sort = TRUE;
-}
-
 gboolean
-nm_dns_manager_set_ip_config(NMDnsManager *    self,
-                             NMIPConfig *      ip_config,
-                             NMDnsIPConfigType ip_config_type)
+nm_dns_manager_set_ip_config(NMDnsManager         *self,
+                             int                   addr_family,
+                             gconstpointer         source_tag,
+                             const NML3ConfigData *l3cd,
+                             NMDnsIPConfigType     ip_config_type,
+                             gboolean              replace_all)
 {
     NMDnsManagerPrivate *priv;
-    NMDnsConfigIPData *  ip_data;
-    NMDnsConfigData *    data;
+    NMDnsConfigData     *data;
     int                  ifindex;
+    gboolean             changed = FALSE;
+    NMDnsConfigIPData   *ip_data = NULL;
+    int                  dns_priority;
+    gboolean             any_removed = FALSE;
 
     g_return_val_if_fail(NM_IS_DNS_MANAGER(self), FALSE);
-    g_return_val_if_fail(NM_IS_IP_CONFIG(ip_config), FALSE);
+    g_return_val_if_fail(!l3cd || NM_IS_L3_CONFIG_DATA(l3cd), FALSE);
+    g_return_val_if_fail(source_tag, FALSE);
+
+    if (addr_family == AF_UNSPEC) {
+        /* Setting AF_UNSPEC is a shortcut for calling this function twice for AF_INET and
+         * AF_INET6. */
+        if (nm_dns_manager_set_ip_config(self,
+                                         AF_INET,
+                                         source_tag,
+                                         l3cd,
+                                         ip_config_type,
+                                         replace_all))
+            changed = TRUE;
+        if (nm_dns_manager_set_ip_config(self,
+                                         AF_INET6,
+                                         source_tag,
+                                         l3cd,
+                                         ip_config_type,
+                                         replace_all))
+            changed = TRUE;
+        return changed;
+    }
 
-    ifindex = nm_ip_config_get_ifindex(ip_config);
-    g_return_val_if_fail(ifindex > 0, FALSE);
+    nm_assert_addr_family(addr_family);
 
     priv = NM_DNS_MANAGER_GET_PRIVATE(self);
 
-    data = g_hash_table_lookup(priv->configs_dict, &ifindex);
-    if (!data)
-        ip_data = NULL;
-    else
-        ip_data = _dns_config_data_find_ip_config(data, ip_config);
+    data = NULL;
+    if (l3cd) {
+        ifindex = nm_l3_config_data_get_ifindex(l3cd);
+        nm_assert(ifindex > 0);
+        data = g_hash_table_lookup(priv->configs_dict, &ifindex);
+    }
+
+    if (data) {
+        NMDnsConfigIPData *ip_data_iter;
+        NMDnsConfigIPData *ip_data_safe;
 
-    if (ip_config_type == NM_DNS_IP_CONFIG_TYPE_REMOVED) {
-        if (!ip_data)
-            return FALSE;
-        /* deleting a config doesn't invalidate the configs' sort order. */
-        _dns_config_ip_data_free(ip_data);
-        if (c_list_is_empty(&data->data_lst_head))
-            g_hash_table_remove(priv->configs_dict, &ifindex);
-        goto changed;
+        c_list_for_each_entry_safe (ip_data_iter, ip_data_safe, &data->data_lst_head, data_lst) {
+            _ASSERT_dns_config_ip_data(ip_data_iter);
+
+            if (ip_data_iter->source_tag != source_tag)
+                continue;
+            if (ip_data_iter->addr_family != addr_family)
+                continue;
+
+            if (ip_config_type != NM_DNS_IP_CONFIG_TYPE_REMOVED && ip_data_iter->l3cd == l3cd) {
+                nm_assert(!ip_data);
+                ip_data = ip_data_iter;
+                continue;
+            }
+
+            if (!replace_all && l3cd && ip_data_iter->l3cd != l3cd)
+                continue;
+
+            if (!l3cd || ip_config_type == NM_DNS_IP_CONFIG_TYPE_REMOVED
+                || nm_l3_config_data_cmp_full(l3cd,
+                                              ip_data_iter->l3cd,
+                                              NM_L3_CONFIG_CMP_FLAGS_DNS
+                                                  | NM_L3_CONFIG_CMP_FLAGS_ROUTES_ID)
+                       != 0) {
+                changed = TRUE;
+            }
+
+            any_removed = TRUE;
+            _dns_config_ip_data_free(ip_data_iter);
+        }
     }
 
+    if (ip_config_type == NM_DNS_IP_CONFIG_TYPE_REMOVED)
+        goto done;
+
+    if (!l3cd)
+        goto done;
+
     if (ip_data && ip_data->ip_config_type == ip_config_type) {
         /* nothing to do. */
-        return FALSE;
+        goto done;
+    }
+
+    dns_priority = _dns_config_ip_data_get_dns_priority1(l3cd, addr_family);
+    if (dns_priority == 0) {
+        /* no DNS priority for this address family. Skip it! */
+        goto done;
     }
 
     if (!data) {
@@ -1894,14 +1964,34 @@ nm_dns_manager_set_ip_config(NMDnsManager *    self,
         priv->configs_lst_need_sort = TRUE;
     }
 
-    if (!ip_data)
-        ip_data = _dns_config_ip_data_new(data, ip_config, ip_config_type);
-    else
+    if (!ip_data) {
+        ip_data = _dns_config_ip_data_new(data, addr_family, source_tag, l3cd, ip_config_type);
+        if (!any_removed)
+            changed = TRUE;
+    } else {
         ip_data->ip_config_type = ip_config_type;
+        changed                 = TRUE;
+    }
+
+    if (changed)
+        priv->ip_data_lst_need_sort = TRUE;
+
+    nm_assert(l3cd);
+    nm_assert(ip_config_type != NM_DNS_IP_CONFIG_TYPE_REMOVED);
+    nm_assert(ip_data->addr_family == addr_family);
+    nm_assert(ip_data->source_tag == source_tag);
+    nm_assert(ip_data->l3cd == l3cd);
+    nm_assert(ip_data->ip_config_type == ip_config_type);
+
+done:
+    if (!changed)
+        return FALSE;
+
+    priv->config_changed = TRUE;
 
-    priv->ip_config_lst_need_sort = TRUE;
+    if (data && c_list_is_empty(&data->data_lst_head))
+        g_hash_table_remove(priv->configs_dict, data);
 
-changed:
     if (!priv->updates_queue) {
         gs_free_error GError *error = NULL;
 
@@ -1924,22 +2014,18 @@ nm_dns_manager_set_initial_hostname(NMDnsManager *self, const char *hostname)
 void
 nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean skip_update)
 {
-    NMDnsManagerPrivate *priv     = NM_DNS_MANAGER_GET_PRIVATE(self);
-    const char *         filtered = NULL;
+    NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
 
     /* Certain hostnames we don't want to include in resolv.conf 'searches' */
     if (hostname && nm_utils_is_specific_hostname(hostname) && !strstr(hostname, ".in-addr.arpa")
         && strchr(hostname, '.')) {
-        filtered = hostname;
-    }
+        /* pass */
+    } else
+        hostname = NULL;
 
-    if ((!priv->hostname && !filtered)
-        || (priv->hostname && filtered && !strcmp(priv->hostname, filtered)))
+    if (!nm_strdup_reset(&priv->hostname, hostname))
         return;
 
-    g_free(priv->hostname);
-    priv->hostname = g_strdup(filtered);
-
     if (skip_update)
         return;
 
@@ -1957,6 +2043,7 @@ nm_dns_manager_begin_updates(NMDnsManager *self, const char *func)
     NMDnsManagerPrivate *priv;
 
     g_return_if_fail(self != NULL);
+
     priv = NM_DNS_MANAGER_GET_PRIVATE(self);
 
     /* Save current hash when starting a new batch */
@@ -1971,9 +2058,8 @@ nm_dns_manager_begin_updates(NMDnsManager *self, const char *func)
 void
 nm_dns_manager_end_updates(NMDnsManager *self, const char *func)
 {
-    NMDnsManagerPrivate *priv;
+    NMDnsManagerPrivate  *priv;
     gs_free_error GError *error = NULL;
-    gboolean              changed;
     guint8 new[HASH_LEN];
 
     g_return_if_fail(self != NULL);
@@ -1982,11 +2068,11 @@ nm_dns_manager_end_updates(NMDnsManager *self, const char *func)
     g_return_if_fail(priv->updates_queue > 0);
 
     compute_hash(self, nm_config_data_get_global_dns_config(nm_config_get_data(priv->config)), new);
-    changed = (memcmp(new, priv->prev_hash, sizeof(new)) != 0) ? TRUE : FALSE;
-    _LOGD("(%s): DNS configuration %s", func, changed ? "changed" : "did not change");
+    priv->config_changed = (memcmp(new, priv->prev_hash, sizeof(new)) != 0) ? TRUE : FALSE;
+    _LOGD("(%s): DNS configuration %s", func, priv->config_changed ? "changed" : "did not change");
 
     priv->updates_queue--;
-    if ((priv->updates_queue > 0) || (changed == FALSE)) {
+    if ((priv->updates_queue > 0) || !priv->config_changed) {
         _LOGD("(%s): no DNS changes to commit (%d)", func, priv->updates_queue);
         return;
     }
@@ -1995,8 +2081,6 @@ nm_dns_manager_end_updates(NMDnsManager *self, const char *func)
     _LOGD("(%s): committing DNS changes (%d)", func, priv->updates_queue);
     if (!update_dns(self, FALSE, FALSE, &error))
         _LOGW("could not commit DNS changes: %s", error->message);
-
-    memset(priv->prev_hash, 0, sizeof(priv->prev_hash));
 }
 
 void
@@ -2116,7 +2200,7 @@ _resolvconf_resolved_managed(void)
         return FALSE;
 
     if (S_ISLNK(st.st_mode)) {
-        gs_free char *     full_path = NULL;
+        gs_free char      *full_path = NULL;
         nm_auto_free char *real_path = NULL;
 
         /* see if resolv.conf is a symlink with a target that is
@@ -2168,9 +2252,9 @@ _resolvconf_resolved_managed(void)
 static void
 init_resolv_conf_mode(NMDnsManager *self, gboolean force_reload_plugin)
 {
-    NMDnsManagerPrivate *         priv = NM_DNS_MANAGER_GET_PRIVATE(self);
+    NMDnsManagerPrivate          *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
     NMDnsManagerResolvConfManager rc_manager;
-    const char *                  mode;
+    const char                   *mode;
     gboolean                      systemd_resolved;
     gboolean                      param_changed            = FALSE;
     gboolean                      plugin_changed           = FALSE;
@@ -2313,12 +2397,14 @@ again:
 }
 
 static void
-config_changed_cb(NMConfig *          config,
-                  NMConfigData *      config_data,
+config_changed_cb(NMConfig           *config,
+                  NMConfigData       *config_data,
                   NMConfigChangeFlags changes,
-                  NMConfigData *      old_data,
-                  NMDnsManager *      self)
+                  NMConfigData       *old_data,
+                  NMDnsManager       *self)
 {
+    NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
+
     if (NM_FLAGS_ANY(changes,
                      NM_CONFIG_CHANGE_DNS_MODE | NM_CONFIG_CHANGE_RC_MANAGER
                          | NM_CONFIG_CHANGE_CAUSE_SIGHUP | NM_CONFIG_CHANGE_CAUSE_DNS_FULL)) {
@@ -2338,6 +2424,7 @@ config_changed_cb(NMConfig *          config,
                          | NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG)) {
         gs_free_error GError *error = NULL;
 
+        priv->config_changed = TRUE;
         if (!update_dns(self, FALSE, TRUE, &error))
             _LOGW("could not commit DNS changes: %s", error->message);
     }
@@ -2355,7 +2442,7 @@ _get_global_config_variant(NMGlobalDnsConfig *global)
     for (i = 0; i < num; i++) {
         GVariantBuilder    conf_builder;
         GVariantBuilder    item_builder;
-        const char *       domain_name;
+        const char        *domain_name;
         const char *const *servers;
 
         g_variant_builder_init(&conf_builder, G_VARIANT_TYPE("a{sv}"));
@@ -2395,12 +2482,12 @@ _get_global_config_variant(NMGlobalDnsConfig *global)
 static GVariant *
 _get_config_variant(NMDnsManager *self)
 {
-    NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
-    NMGlobalDnsConfig *  global_config;
-    gs_free char *       str = NULL;
-    GVariantBuilder      builder;
-    NMDnsConfigIPData *  ip_data;
-    const CList *        head;
+    NMDnsManagerPrivate         *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
+    NMGlobalDnsConfig           *global_config;
+    gs_free char                *str = NULL;
+    GVariantBuilder              builder;
+    NMDnsConfigIPData           *ip_data;
+    const CList                 *head;
     gs_unref_ptrarray GPtrArray *array_domains = NULL;
 
     if (priv->config_variant)
@@ -2415,51 +2502,54 @@ _get_config_variant(NMDnsManager *self)
 
     g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}"));
 
-    head = _mgr_get_ip_config_lst_head(self);
-    c_list_for_each_entry (ip_data, head, ip_config_lst) {
-        const NMIPConfig *ip_config = ip_data->ip_config;
-        GVariantBuilder   entry_builder;
-        GVariantBuilder   strv_builder;
-        guint             i, num;
-        const int         addr_family = nm_ip_config_get_addr_family(ip_config);
-        char              buf[NM_UTILS_INET_ADDRSTRLEN];
-        const NMIPAddr *  addr;
-        const char *      ifname;
-
-        num = nm_ip_config_get_num_nameservers(ip_config);
-        if (!num)
+    head = _mgr_get_ip_data_lst_head(self);
+    c_list_for_each_entry (ip_data, head, ip_data_lst) {
+        GVariantBuilder entry_builder;
+        GVariantBuilder strv_builder;
+        guint           num;
+        guint           num_domains;
+        guint           num_searches;
+        guint           i;
+        char            buf[NM_UTILS_INET_ADDRSTRLEN];
+        const char     *ifname;
+        gconstpointer   nameservers;
+
+        nameservers = nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &num);
+        if (num == 0)
             continue;
 
         g_variant_builder_init(&entry_builder, G_VARIANT_TYPE("a{sv}"));
 
         g_variant_builder_init(&strv_builder, G_VARIANT_TYPE("as"));
         for (i = 0; i < num; i++) {
-            addr = nm_ip_config_get_nameserver(ip_config, i);
-            g_variant_builder_add(&strv_builder, "s", nm_utils_inet_ntop(addr_family, addr, buf));
+            const NMIPAddr *addr;
+
+            addr = nm_ip_addr_from_packed_array(ip_data->addr_family, nameservers, i);
+            g_variant_builder_add(&strv_builder,
+                                  "s",
+                                  nm_utils_inet_ntop(ip_data->addr_family, addr, buf));
         }
         g_variant_builder_add(&entry_builder,
                               "{sv}",
                               "nameservers",
                               g_variant_builder_end(&strv_builder));
 
-        num = nm_ip_config_get_num_domains(ip_config);
-        num += nm_ip_config_get_num_searches(ip_config);
+        nm_l3_config_data_get_domains(ip_data->l3cd, ip_data->addr_family, &num_domains);
+        nm_l3_config_data_get_searches(ip_data->l3cd, ip_data->addr_family, &num_searches);
+        num = num_domains + num_searches;
         if (num > 0) {
             if (!array_domains)
                 array_domains = g_ptr_array_sized_new(num);
             else
                 g_ptr_array_set_size(array_domains, 0);
 
-            add_dns_domains(array_domains, ip_config, TRUE, FALSE);
+            add_dns_domains(array_domains, ip_data->addr_family, ip_data->l3cd, TRUE, FALSE);
             if (array_domains->len) {
-                g_variant_builder_init(&strv_builder, G_VARIANT_TYPE("as"));
-                for (i = 0; i < array_domains->len; i++) {
-                    g_variant_builder_add(&strv_builder, "s", array_domains->pdata[i]);
-                }
                 g_variant_builder_add(&entry_builder,
                                       "{sv}",
                                       "domains",
-                                      g_variant_builder_end(&strv_builder));
+                                      g_variant_new_strv((const char *const *) array_domains->pdata,
+                                                         array_domains->len));
             }
         }
 
@@ -2474,7 +2564,7 @@ _get_config_variant(NMDnsManager *self)
         g_variant_builder_add(&entry_builder,
                               "{sv}",
                               "priority",
-                              g_variant_new_int32(nm_ip_config_get_dns_priority(ip_config)));
+                              g_variant_new_int32(_dns_config_ip_data_get_dns_priority(ip_data)));
 
         g_variant_builder_add(
             &entry_builder,
@@ -2494,7 +2584,7 @@ _get_config_variant(NMDnsManager *self)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMDnsManager *       self = NM_DNS_MANAGER(object);
+    NMDnsManager        *self = NM_DNS_MANAGER(object);
     NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -2521,7 +2611,7 @@ nm_dns_manager_init(NMDnsManager *self)
     _LOGT("creating...");
 
     c_list_init(&priv->configs_lst_head);
-    c_list_init(&priv->ip_config_lst_head);
+    c_list_init(&priv->ip_data_lst_head);
 
     priv->config = g_object_ref(nm_config_get());
 
@@ -2531,9 +2621,7 @@ nm_dns_manager_init(NMDnsManager *self)
                                                (GDestroyNotify) _dns_config_data_free,
                                                NULL);
 
-    /* Set the initial hash */
     compute_hash(self, NULL, NM_DNS_MANAGER_GET_PRIVATE(self)->hash);
-
     g_signal_connect(G_OBJECT(priv->config),
                      NM_CONFIG_SIGNAL_CONFIG_CHANGED,
                      G_CALLBACK(config_changed_cb),
@@ -2544,9 +2632,9 @@ nm_dns_manager_init(NMDnsManager *self)
 static void
 dispose(GObject *object)
 {
-    NMDnsManager *       self = NM_DNS_MANAGER(object);
+    NMDnsManager        *self = NM_DNS_MANAGER(object);
     NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
-    NMDnsConfigIPData *  ip_data, *ip_data_safe;
+    NMDnsConfigIPData   *ip_data, *ip_data_safe;
 
     _LOGT("disposing");
 
@@ -2559,7 +2647,7 @@ dispose(GObject *object)
     g_clear_object(&priv->sd_resolve_plugin);
     _clear_plugin(self);
 
-    c_list_for_each_entry_safe (ip_data, ip_data_safe, &priv->ip_config_lst_head, ip_config_lst)
+    c_list_for_each_entry_safe (ip_data, ip_data_safe, &priv->ip_data_lst_head, ip_data_lst)
         _dns_config_ip_data_free(ip_data);
 
     nm_clear_pointer(&priv->configs_dict, g_hash_table_destroy);
@@ -2577,7 +2665,7 @@ dispose(GObject *object)
 static void
 finalize(GObject *object)
 {
-    NMDnsManager *       self = NM_DNS_MANAGER(object);
+    NMDnsManager        *self = NM_DNS_MANAGER(object);
     NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
 
     g_free(priv->hostname);
@@ -2602,7 +2690,7 @@ static const NMDBusInterfaceInfoExtended interface_info_dns_manager = {
 static void
 nm_dns_manager_class_init(NMDnsManagerClass *klass)
 {
-    GObjectClass *     object_class      = G_OBJECT_CLASS(klass);
+    GObjectClass      *object_class      = G_OBJECT_CLASS(klass);
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
 
     object_class->dispose      = dispose;