summary refs log tree commit diff
path: root/src/dns
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-04-23 18:00:21 +0200
committerMichael Biebl <biebl@debian.org>2018-04-23 18:00:21 +0200
commitf60117b41d5433be1b4a96d82cd11d0c3dce9b63 (patch)
tree2dd55c4ab0fdcbe9cddb26adb4a554b1a45c73c8 /src/dns
parent7e9ff09fcb2366b383b7ebbec80d2f6fff117290 (diff)
New upstream version 1.11.3 upstream/1.11.3
Diffstat (limited to 'src/dns')
-rw-r--r--src/dns/nm-dns-dnsmasq.c344
-rw-r--r--src/dns/nm-dns-manager.c819
-rw-r--r--src/dns/nm-dns-manager.h33
-rw-r--r--src/dns/nm-dns-plugin.c4
-rw-r--r--src/dns/nm-dns-plugin.h4
-rw-r--r--src/dns/nm-dns-systemd-resolved.c289
-rw-r--r--src/dns/nm-dns-unbound.c2
7 files changed, 793 insertions, 702 deletions
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/dns/nm-dns-dnsmasq.c
index e6436c79..d0753078 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/dns/nm-dns-dnsmasq.c
@@ -35,7 +35,7 @@
 #include "nm-utils.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
 #include "NetworkManagerUtils.h"
 
 #define PIDFILE NMRUNDIR "/dnsmasq.pid"
@@ -76,54 +76,41 @@ G_DEFINE_TYPE (NMDnsDnsmasq, nm_dns_dnsmasq, NM_TYPE_DNS_PLUGIN)
 /*****************************************************************************/
 
 static char **
-get_ip4_rdns_domains (NMIP4Config *ip4)
+get_ip_rdns_domains (NMIPConfig *ip_config)
 {
+	int addr_family = nm_ip_config_get_addr_family (ip_config);
 	char **strv;
 	GPtrArray *domains = NULL;
 	NMDedupMultiIter ipconf_iter;
-	const NMPlatformIP4Address *address;
-	const NMPlatformIP4Route *route;
 
-	g_return_val_if_fail (ip4 != NULL, NULL);
+	nm_assert_addr_family (addr_family);
 
 	domains = g_ptr_array_sized_new (5);
 
-	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &address)
-		nm_utils_get_reverse_dns_domains_ip4 (address->address, address->plen, domains);
+	if (addr_family == AF_INET) {
+		NMIP4Config *ip4 = (gpointer) ip_config;
+		const NMPlatformIP4Address *address;
+		const NMPlatformIP4Route *route;
 
-	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route) {
-		if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
-			nm_utils_get_reverse_dns_domains_ip4 (route->network, route->plen, domains);
-	}
-
-	/* Terminating NULL so we can use g_strfreev() to free it */
-	g_ptr_array_add (domains, NULL);
-
-	/* Free the array and return NULL if the only element was the ending NULL */
-	strv = (char **) g_ptr_array_free (domains, (domains->len == 1));
-
-	return _nm_utils_strv_cleanup (strv, FALSE, FALSE, TRUE);
-}
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &address)
+			nm_utils_get_reverse_dns_domains_ip4 (address->address, address->plen, domains);
 
-static char **
-get_ip6_rdns_domains (NMIP6Config *ip6)
-{
-	char **strv;
-	GPtrArray *domains = NULL;
-	NMDedupMultiIter ipconf_iter;
-	const NMPlatformIP6Address *address;
-	const NMPlatformIP6Route *route;
-
-	g_return_val_if_fail (ip6 != NULL, NULL);
-
-	domains = g_ptr_array_sized_new (5);
+		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route) {
+			if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
+				nm_utils_get_reverse_dns_domains_ip4 (route->network, route->plen, domains);
+		}
+	} else {
+		NMIP6Config *ip6 = (gpointer) ip_config;
+		const NMPlatformIP6Address *address;
+		const NMPlatformIP6Route *route;
 
-	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &address)
-		nm_utils_get_reverse_dns_domains_ip6 (&address->address, address->plen, domains);
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &address)
+			nm_utils_get_reverse_dns_domains_ip6 (&address->address, address->plen, domains);
 
-	nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route) {
-		if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
-			nm_utils_get_reverse_dns_domains_ip6 (&route->network, route->plen, domains);
+		nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route) {
+			if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
+				nm_utils_get_reverse_dns_domains_ip6 (&route->network, route->plen, domains);
+		}
 	}
 
 	/* Terminating NULL so we can use g_strfreev() to free it */
@@ -155,96 +142,43 @@ add_dnsmasq_nameserver (NMDnsDnsmasq *self,
 	g_variant_builder_close (servers);
 }
 
-static gboolean
-add_ip4_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP4Config *ip4,
-                const char *iface, gboolean split)
-{
-	char buf[INET_ADDRSTRLEN + 1 + IFNAMSIZ];
-	char buf2[INET_ADDRSTRLEN];
-	in_addr_t addr;
-	int nnameservers, i_nameserver, n, i;
-	gboolean added = FALSE;
-
-	g_return_val_if_fail (iface, FALSE);
-	nnameservers = nm_ip4_config_get_num_nameservers (ip4);
-
-	if (split) {
-		char **domains, **iter;
+#define IP_ADDR_TO_STRING_BUFLEN (NM_UTILS_INET_ADDRSTRLEN + 1 + IFNAMSIZ)
 
-		if (nnameservers == 0)
-			return FALSE;
-
-		for (i_nameserver = 0; i_nameserver < nnameservers; i_nameserver++) {
-			addr = nm_ip4_config_get_nameserver (ip4, i_nameserver);
-			g_snprintf (buf, sizeof (buf), "%s@%s",
-			            nm_utils_inet4_ntop (addr, buf2), iface);
-
-			/* searches are preferred over domains */
-			n = nm_ip4_config_get_num_searches (ip4);
-			for (i = 0; i < n; i++) {
-				add_dnsmasq_nameserver (self,
-				                        servers,
-				                        buf,
-				                        nm_ip4_config_get_search (ip4, i));
-				added = TRUE;
-			}
-
-			if (n == 0) {
-				/* If not searches, use any domains */
-				n = nm_ip4_config_get_num_domains (ip4);
-				for (i = 0; i < n; i++) {
-					add_dnsmasq_nameserver (self,
-					                        servers,
-					                        buf,
-					                        nm_ip4_config_get_domain (ip4, i));
-					added = TRUE;
-				}
-			}
+static const char *
+ip_addr_to_string (int addr_family, gconstpointer addr, const char *iface, char *out_buf)
+{
+	int n_written;
+	char buf2[NM_UTILS_INET_ADDRSTRLEN];
+	const char *separator;
 
-			/* Ensure reverse-DNS works by directing queries for in-addr.arpa
-			 * domains to the split domain's nameserver.
-			 */
-			domains = get_ip4_rdns_domains (ip4);
-			if (domains) {
-				for (iter = domains; iter && *iter; iter++)
-					add_dnsmasq_nameserver (self, servers, buf, *iter);
-				g_strfreev (domains);
-			}
-		}
-	}
+	nm_assert_addr_family (addr_family);
+	nm_assert (addr);
+	nm_assert (out_buf);
 
-	/* If no searches or domains, just add the nameservers */
-	if (!added) {
-		for (i = 0; i < nnameservers; i++) {
-			addr = nm_ip4_config_get_nameserver (ip4, i);
-			g_snprintf (buf, sizeof (buf), "%s@%s",
-			            nm_utils_inet4_ntop (addr, buf2), iface);
-			add_dnsmasq_nameserver (self, servers, buf, NULL);
-		}
+	if (addr_family == AF_INET) {
+		nm_utils_inet_ntop (addr_family, addr, buf2);
+		separator = "@";
+	} else {
+		if (IN6_IS_ADDR_V4MAPPED (addr))
+			nm_utils_inet4_ntop (((const struct in6_addr *) addr)->s6_addr32[3], buf2);
+		else
+			nm_utils_inet6_ntop (addr, buf2);
+		/* Need to scope link-local addresses with %<zone-id>. Before dnsmasq 2.58,
+		 * only '@' was supported as delimiter. Since 2.58, '@' and '%' are
+		 * supported. Due to a bug, since 2.73 only '%' works properly as "server"
+		 * address.
+		 */
+		separator = IN6_IS_ADDR_LINKLOCAL (addr) ? "%" : "@";
 	}
 
-	return TRUE;
-}
-
-static char *
-ip6_addr_to_string (const struct in6_addr *addr, const char *iface)
-{
-	char buf[NM_UTILS_INET_ADDRSTRLEN];
-
-	if (IN6_IS_ADDR_V4MAPPED (addr))
-		nm_utils_inet4_ntop (addr->s6_addr32[3], buf);
-	else
-		nm_utils_inet6_ntop (addr, buf);
-
-	/* Need to scope link-local addresses with %<zone-id>. Before dnsmasq 2.58,
-	 * only '@' was supported as delimiter. Since 2.58, '@' and '%' are
-	 * supported. Due to a bug, since 2.73 only '%' works properly as "server"
-	 * address.
-	 */
-	return g_strdup_printf ("%s%c%s",
-	                        buf,
-	                        IN6_IS_ADDR_LINKLOCAL (addr) ? '%' : '@',
-	                        iface);
+	n_written = g_snprintf (out_buf,
+	                        IP_ADDR_TO_STRING_BUFLEN,
+	                        "%s%s%s",
+	                        buf2,
+	                        iface ? separator : "",
+	                        iface ?: "");
+	nm_assert (n_written < IP_ADDR_TO_STRING_BUFLEN);
+	return out_buf;
 }
 
 static void
@@ -271,103 +205,100 @@ add_global_config (NMDnsDnsmasq *self, GVariantBuilder *dnsmasq_servers, const N
 	}
 }
 
-static gboolean
-add_ip6_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP6Config *ip6,
-                const char *iface, gboolean split)
+static void
+add_ip_config (NMDnsDnsmasq *self,
+               GVariantBuilder *servers,
+               int ifindex,
+               NMIPConfig *ip_config,
+               gboolean split)
 {
-	const struct in6_addr *addr;
-	char *buf = NULL;
-	int nnameservers, i_nameserver, n, i;
+	int addr_family;
+	gconstpointer addr;
 	gboolean added = FALSE;
+	guint nnameservers, i_nameserver, n, i;
+	char ip_addr_to_string_buf[IP_ADDR_TO_STRING_BUFLEN];
+	char **domains, **iter;
+	gboolean iface_resolved = FALSE;
+	const char *iface = NULL, *domain;
 
-	g_return_val_if_fail (iface, FALSE);
-	nnameservers = nm_ip6_config_get_num_nameservers (ip6);
+	addr_family = nm_ip_config_get_addr_family (ip_config);
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	if (split) {
-		char **domains, **iter;
+	nm_assert (ifindex > 0);
+	nm_assert (ifindex == nm_ip_config_get_ifindex (ip_config));
+
+	nnameservers = nm_ip_config_get_num_nameservers (ip_config);
 
+	if (split) {
 		if (nnameservers == 0)
-			return FALSE;
-
-		for (i_nameserver = 0; i_nameserver < nnameservers; i_nameserver++) {
-			addr = nm_ip6_config_get_nameserver (ip6, i_nameserver);
-			buf = ip6_addr_to_string (addr, iface);
-
-			/* searches are preferred over domains */
-			n = nm_ip6_config_get_num_searches (ip6);
-			for (i = 0; i < n; i++) {
-				add_dnsmasq_nameserver (self,
-				                        servers,
-				                        buf,
-				                        nm_ip6_config_get_search (ip6, i));
-				added = TRUE;
-			}
+			return;
 
-			if (n == 0) {
-				/* If not searches, use any domains */
-				n = nm_ip6_config_get_num_domains (ip6);
+		if (!iface_resolved) {
+			iface = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+			iface_resolved = TRUE;
+		}
+
+		if (iface) {
+			for (i_nameserver = 0; i_nameserver < nnameservers; i_nameserver++) {
+				addr = nm_ip_config_get_nameserver (ip_config, i_nameserver);
+
+				ip_addr_to_string (addr_family, addr, iface, ip_addr_to_string_buf);
+
+				/* searches are preferred over domains */
+				n = nm_ip_config_get_num_searches (ip_config);
 				for (i = 0; i < n; i++) {
+					domain = nm_utils_parse_dns_domain (nm_ip_config_get_search (ip_config, i), NULL);
 					add_dnsmasq_nameserver (self,
 					                        servers,
-					                        buf,
-					                        nm_ip6_config_get_domain (ip6, i));
+					                        ip_addr_to_string_buf,
+					                        domain);
 					added = TRUE;
 				}
-			}
 
-			/* Ensure reverse-DNS works by directing queries for ip6.arpa
-			 * domains to the split domain's nameserver.
-			 */
-			domains = get_ip6_rdns_domains (ip6);
-			if (domains) {
-				for (iter = domains; iter && *iter; iter++)
-					add_dnsmasq_nameserver (self, servers, buf, *iter);
-				g_strfreev (domains);
-			}
+				if (n == 0) {
+					/* If not searches, use any domains */
+					n = nm_ip_config_get_num_domains (ip_config);
+					domain = nm_utils_parse_dns_domain (nm_ip_config_get_domain (ip_config, i), NULL);
+					for (i = 0; i < n; i++) {
+						add_dnsmasq_nameserver (self,
+						                        servers,
+						                        ip_addr_to_string_buf,
+						                        domain);
+						added = TRUE;
+					}
+				}
 
-			g_free (buf);
+				/* Ensure reverse-DNS works by directing queries for in-addr4.arpa/ip6.arpa
+				 * domains to the split domain's nameserver.
+				 */
+				domains = get_ip_rdns_domains (ip_config);
+				if (domains) {
+					for (iter = domains; *iter; iter++)
+						add_dnsmasq_nameserver (self, servers, ip_addr_to_string_buf, *iter);
+					g_strfreev (domains);
+				}
+			}
 		}
 	}
 
 	/* If no searches or domains, just add the nameservers */
 	if (!added) {
-		for (i = 0; i < nnameservers; i++) {
-			addr = nm_ip6_config_get_nameserver (ip6, i);
-			buf = ip6_addr_to_string (addr, iface);
-			if (buf) {
-				add_dnsmasq_nameserver (self, servers, buf, NULL);
-				g_free (buf);
+		if (!iface_resolved)
+			iface = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+		if (iface) {
+			for (i = 0; i < nnameservers; i++) {
+				addr = nm_ip_config_get_nameserver (ip_config, i);
+				ip_addr_to_string (addr_family, addr, iface, ip_addr_to_string_buf);
+				add_dnsmasq_nameserver (self, servers, ip_addr_to_string_buf, NULL);
 			}
 		}
 	}
-
-	return TRUE;
-}
-
-static gboolean
-add_ip_config_data (NMDnsDnsmasq *self, GVariantBuilder *servers, const NMDnsIPConfigData *data)
-{
-	if (NM_IS_IP4_CONFIG (data->config)) {
-		return add_ip4_config (self,
-		                       servers,
-		                       (NMIP4Config *) data->config,
-		                       data->iface,
-		                       data->type == NM_DNS_IP_CONFIG_TYPE_VPN);
-	} else if (NM_IS_IP6_CONFIG (data->config)) {
-		return add_ip6_config (self,
-		                       servers,
-		                       (NMIP6Config *) data->config,
-		                       data->iface,
-		                       data->type == NM_DNS_IP_CONFIG_TYPE_VPN);
-	} else
-		g_return_val_if_reached (FALSE);
 }
 
 static void
 dnsmasq_update_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
 {
 	NMDnsDnsmasq *self;
-	NMDnsDnsmasqPrivate *priv;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *response = NULL;
 
@@ -376,7 +307,6 @@ dnsmasq_update_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
 		return;
 
 	self = NM_DNS_DNSMASQ (user_data);
-	priv = NM_DNS_DNSMASQ_GET_PRIVATE (self);
 
 	if (!response)
 		_LOGW ("dnsmasq update failed: %s", error->message);
@@ -478,7 +408,7 @@ start_dnsmasq (NMDnsDnsmasq *self)
 	const char *argv[15];
 	GPid pid = 0;
 	guint idx = 0;
-	NMBusManager *dbus_mgr;
+	NMDBusManager *dbus_mgr;
 	GDBusConnection *connection;
 
 	if (priv->running) {
@@ -530,10 +460,10 @@ start_dnsmasq (NMDnsDnsmasq *self)
 		return;
 	}
 
-	dbus_mgr = nm_bus_manager_get ();
+	dbus_mgr = nm_dbus_manager_get ();
 	g_return_if_fail (dbus_mgr);
 
-	connection = nm_bus_manager_get_connection (dbus_mgr);
+	connection = nm_dbus_manager_get_connection (dbus_mgr);
 	g_return_if_fail (connection);
 
 	priv->dnsmasq_cancellable = g_cancellable_new ();
@@ -550,15 +480,16 @@ start_dnsmasq (NMDnsDnsmasq *self)
 
 static gboolean
 update (NMDnsPlugin *plugin,
-        const GPtrArray *configs,
         const NMGlobalDnsConfig *global_config,
+        const CList *ip_config_lst_head,
         const char *hostname)
 {
 	NMDnsDnsmasq *self = NM_DNS_DNSMASQ (plugin);
 	NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE (self);
 	GVariantBuilder servers;
-	guint i;
-	int prio, first_prio;
+	int prio, first_prio = 0;
+	const NMDnsIPConfigData *ip_data;
+	gboolean is_first = TRUE;
 
 	start_dnsmasq (self);
 
@@ -567,15 +498,18 @@ update (NMDnsPlugin *plugin,
 	if (global_config)
 		add_global_config (self, &servers, global_config);
 	else {
-		for (i = 0; i < configs->len; i++) {
-			const NMDnsIPConfigData *data = configs->pdata[i];
-
-			prio = nm_ip_config_get_dns_priority (data->config);
-			if (i == 0)
+		c_list_for_each_entry (ip_data, ip_config_lst_head, ip_config_lst) {
+			prio = nm_ip_config_get_dns_priority (ip_data->ip_config);
+			if (is_first) {
+				is_first = FALSE;
 				first_prio = prio;
-			else if (first_prio < 0 && first_prio != prio)
+			} else if (first_prio < 0 && first_prio != prio)
 				break;
-			add_ip_config_data (self, &servers, data);
+			add_ip_config (self,
+			               &servers,
+			               ip_data->data->ifindex,
+			               ip_data->ip_config,
+			               ip_data->ip_config_type == NM_DNS_IP_CONFIG_TYPE_VPN);
 		}
 	}
 
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index dc545470..10a16e57 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -46,6 +46,7 @@
 #include "nm-ip6-config.h"
 #include "NetworkManagerUtils.h"
 #include "nm-config.h"
+#include "nm-dbus-object.h"
 #include "devices/nm-device.h"
 #include "nm-manager.h"
 
@@ -54,8 +55,6 @@
 #include "nm-dns-systemd-resolved.h"
 #include "nm-dns-unbound.h"
 
-#include "introspection/org.freedesktop.NetworkManager.DnsManager.h"
-
 #define HASH_LEN 20
 
 #ifndef RESOLVCONF_PATH
@@ -70,6 +69,24 @@
 #define PLUGIN_RATELIMIT_BURST       5
 #define PLUGIN_RATELIMIT_DELAY       300
 
+/*****************************************************************************/
+
+typedef enum {
+	SR_SUCCESS,
+	SR_NOTFOUND,
+	SR_ERROR
+} SpawnResult;
+
+typedef struct {
+	GPtrArray *nameservers;
+	GPtrArray *searches;
+	GPtrArray *options;
+	const char *nis_domain;
+	GPtrArray *nis_servers;
+} NMResolvConfData;
+
+/*****************************************************************************/
+
 enum {
 	CONFIG_CHANGED,
 
@@ -84,44 +101,16 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDnsManager,
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
-typedef enum {
-	SR_SUCCESS,
-	SR_NOTFOUND,
-	SR_ERROR
-} SpawnResult;
-
-NM_DEFINE_SINGLETON_GETTER (NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER);
-
-/*****************************************************************************/
-
-#define _NMLOG_PREFIX_NAME                "dns-mgr"
-#define _NMLOG_DOMAIN                     LOGD_DNS
-#define _NMLOG(level, ...) \
-    G_STMT_START { \
-        const NMLogLevel __level = (level); \
-        \
-        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
-            char __prefix[20]; \
-            const NMDnsManager *const __self = (self); \
-            \
-            _nm_log (__level, _NMLOG_DOMAIN, 0, NULL, NULL, \
-                     "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                     _NMLOG_PREFIX_NAME, \
-                     ((!__self || __self == singleton_instance) \
-                        ? "" \
-                        : nm_sprintf_buf (__prefix, "[%p]", __self)) \
-                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
-        } \
-    } G_STMT_END
-
-/*****************************************************************************/
-
 typedef struct {
-	GPtrArray *configs;
+	GHashTable *configs;
+	CList ip_config_lst_head;
 	GVariant *config_variant;
-	NMDnsIPConfigData *best_conf4, *best_conf6;
 
-	bool need_sort:1;
+	NMDnsIPConfigData *best_ip_config_4;
+	NMDnsIPConfigData *best_ip_config_6;
+
+	bool ip_config_lst_need_sort:1;
+
 	bool dns_touched:1;
 	bool is_stopped:1;
 
@@ -145,20 +134,52 @@ typedef struct {
 } NMDnsManagerPrivate;
 
 struct _NMDnsManager {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	NMDnsManagerPrivate _priv;
 };
 
 struct _NMDnsManagerClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 };
 
-G_DEFINE_TYPE (NMDnsManager, nm_dns_manager, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMDnsManager, nm_dns_manager, NM_TYPE_DBUS_OBJECT)
 
 #define NM_DNS_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDnsManager, NM_IS_DNS_MANAGER)
 
+NM_DEFINE_SINGLETON_GETTER (NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER);
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME                "dns-mgr"
+#define _NMLOG_DOMAIN                     LOGD_DNS
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        \
+        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
+            char __prefix[20]; \
+            const NMDnsManager *const __self = (self); \
+            \
+            _nm_log (__level, _NMLOG_DOMAIN, 0, NULL, NULL, \
+                     "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     ((!__self || __self == singleton_instance) \
+                        ? "" \
+                        : nm_sprintf_buf (__prefix, "[%p]", __self)) \
+                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static void _ip_config_dns_priority_changed (gpointer config,
+                                             GParamSpec *pspec,
+                                             NMDnsIPConfigData *ip_data);
+
+/*****************************************************************************/
+
 static gboolean
-domain_is_valid (const gchar *domain, gboolean check_public_suffix)
+domain_is_valid (const char *domain, gboolean check_public_suffix)
 {
 	if (*domain == '\0')
 		return FALSE;
@@ -169,15 +190,13 @@ domain_is_valid (const gchar *domain, gboolean check_public_suffix)
 	return TRUE;
 }
 
-/*****************************************************************************/
+static gboolean
+domain_is_routing (const char *domain)
+{
+	return domain[0] == '~';
+}
 
-typedef struct {
-	GPtrArray *nameservers;
-	GPtrArray *searches;
-	GPtrArray *options;
-	const char *nis_domain;
-	GPtrArray *nis_servers;
-} NMResolvConfData;
+/*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_rc_manager_to_string, NMDnsManagerResolvConfManager,
 	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
@@ -192,44 +211,109 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_rc_manager_to_string, NMDnsManagerResolvConf
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_config_type_to_string, NMDnsIPConfigType,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("<unknown>"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_REMOVED, "removed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_DEFAULT, "default"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE, "best"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_VPN, "vpn"),
 );
 
-static NMDnsIPConfigData *
-ip_config_data_new (gpointer config, NMDnsIPConfigType type, const char *iface)
+/*****************************************************************************/
+
+static void
+_ASSERT_config_data (const NMDnsConfigData *data)
 {
-	NMDnsIPConfigData *data;
+	nm_assert (data);
+	nm_assert (NM_IS_DNS_MANAGER (data->self));
+	nm_assert (data->ifindex > 0);
+}
 
-	data = g_slice_new0 (NMDnsIPConfigData);
-	data->config = g_object_ref (config);
-	data->iface = g_strdup (iface);
-	data->type = type;
+static void
+_ASSERT_ip_config_data (const NMDnsIPConfigData *ip_data)
+{
+	nm_assert (ip_data);
+	_ASSERT_config_data (ip_data->data);
+	nm_assert (NM_IS_IP_CONFIG (ip_data->ip_config));
+	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));
+}
 
-	return data;
+static NMDnsIPConfigData *
+_ip_config_data_new (NMDnsConfigData *data,
+                     NMIPConfig *ip_config,
+                     NMDnsIPConfigType ip_config_type)
+{
+	NMDnsIPConfigData *ip_data;
+
+	_ASSERT_config_data (data);
+	nm_assert (NM_IS_IP_CONFIG (ip_config));
+	nm_assert (ip_config_type != NM_DNS_IP_CONFIG_TYPE_REMOVED);
+
+	ip_data = g_slice_new0 (NMDnsIPConfigData);
+	ip_data->data = data;
+	ip_data->ip_config = g_object_ref (ip_config);
+	ip_data->ip_config_type = ip_config_type;
+	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);
+
+	g_signal_connect (ip_config,
+	                  NM_IS_IP4_CONFIG (ip_config)
+	                    ? "notify::" NM_IP4_CONFIG_DNS_PRIORITY
+	                    : "notify::" NM_IP6_CONFIG_DNS_PRIORITY,
+	                  (GCallback) _ip_config_dns_priority_changed, ip_data);
+
+	_ASSERT_ip_config_data (ip_data);
+	return ip_data;
 }
 
 static void
-ip_config_data_destroy (gpointer ptr)
+_ip_config_data_free (NMDnsIPConfigData *ip_data)
 {
-	NMDnsIPConfigData *data = ptr;
+	_ASSERT_ip_config_data (ip_data);
 
-	if (!data)
-		return;
+	c_list_unlink_stale (&ip_data->data_lst);
+	c_list_unlink_stale (&ip_data->ip_config_lst);
+
+	g_signal_handlers_disconnect_by_func (ip_data->ip_config,
+	                                      _ip_config_dns_priority_changed,
+	                                      ip_data);
+
+	g_object_unref (ip_data->ip_config);
+	g_slice_free (NMDnsIPConfigData, ip_data);
+}
+
+static NMDnsIPConfigData *
+_config_data_find_ip_config (NMDnsConfigData *data,
+                             NMIPConfig *ip_config)
+{
+	NMDnsIPConfigData *ip_data;
+
+	_ASSERT_config_data (data);
+
+	c_list_for_each_entry (ip_data, &data->data_lst_head, data_lst) {
+		_ASSERT_ip_config_data (ip_data);
+
+		if (ip_data->ip_config == ip_config)
+			return ip_data;
+	}
+	return NULL;
+}
+
+static void
+_config_data_free (NMDnsConfigData *data)
+{
+	_ASSERT_config_data (data);
 
-	g_object_unref (data->config);
-	g_free (data->iface);
-	g_slice_free (NMDnsIPConfigData, data);
+	nm_assert (c_list_is_empty (&data->data_lst_head));
+	g_slice_free (NMDnsConfigData, data);
 }
 
 static gint
-ip_config_data_compare (const NMDnsIPConfigData *a, const NMDnsIPConfigData *b)
+_ip_config_data_cmp (const NMDnsIPConfigData *a, const NMDnsIPConfigData *b)
 {
 	int a_prio, b_prio;
 
-	a_prio = nm_ip_config_get_dns_priority (a->config);
-	b_prio = nm_ip_config_get_dns_priority (b->config);
+	a_prio = nm_ip_config_get_dns_priority (a->ip_config);
+	b_prio = nm_ip_config_get_dns_priority (b->ip_config);
 
 	/* Configurations with lower priority value first */
 	if (a_prio < b_prio)
@@ -238,24 +322,40 @@ ip_config_data_compare (const NMDnsIPConfigData *a, const NMDnsIPConfigData *b)
 		return 1;
 
 	/* Sort also according to type */
-	if (a->type > b->type)
+	if (a->ip_config_type > b->ip_config_type)
 		return -1;
-	else if (a->type < b->type)
+	else if (a->ip_config_type < b->ip_config_type)
 		return 1;
 
 	return 0;
 }
 
 static gint
-ip_config_data_ptr_compare (gconstpointer a, gconstpointer b)
+_ip_config_lst_cmp (const CList *a,
+                    const CList *b,
+                    const void *user_data)
 {
-	const NMDnsIPConfigData *const *ptr_a = a, *const *ptr_b = b;
+	return _ip_config_data_cmp (c_list_entry (a, NMDnsIPConfigData, ip_config_lst),
+	                            c_list_entry (b, NMDnsIPConfigData, ip_config_lst));
+}
 
-	return ip_config_data_compare (*ptr_a, *ptr_b);
+static CList *
+_ip_config_lst_head (NMDnsManager *self)
+{
+	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+
+	if (priv->ip_config_lst_need_sort) {
+		priv->ip_config_lst_need_sort = FALSE;
+		c_list_sort (&priv->ip_config_lst_head, _ip_config_lst_cmp, NULL);
+	}
+
+	return &priv->ip_config_lst_head;
 }
 
+/*****************************************************************************/
+
 static void
-add_string_item (GPtrArray *array, const char *str)
+add_string_item (GPtrArray *array, const char *str, gboolean dup)
 {
 	int i;
 
@@ -271,7 +371,7 @@ add_string_item (GPtrArray *array, const char *str)
 	}
 
 	/* No dupes, add the new item */
-	g_ptr_array_add (array, g_strdup (str));
+	g_ptr_array_add (array, dup ? g_strdup (str): (gpointer) str);
 }
 
 static void
@@ -282,24 +382,55 @@ 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)
+{
+	guint num_domains, num_searches, i;
+	const char *str;
+
+	num_domains = nm_ip_config_get_num_domains (ip_config);
+	num_searches = nm_ip_config_get_num_searches (ip_config);
+
+	for (i = 0; i < num_searches; i++) {
+		str = nm_ip_config_get_search (ip_config, 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) {
+		for (i = 0; i < num_domains; i++) {
+			str = nm_ip_config_get_domain (ip_config, 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);
+		}
+	}
+}
+
+static void
 merge_one_ip_config (NMResolvConfData *rc,
-                     const NMIPConfig *config,
-                     const char *iface)
+                     int ifindex,
+                     const NMIPConfig *ip_config)
 {
 	int addr_family;
-	guint num, num_domains, num_searches, i;
+	guint num, i;
 	char buf[NM_UTILS_INET_ADDRSTRLEN + 50];
-	const char *str;
 
-	addr_family = nm_ip_config_get_addr_family (config);
+	addr_family = nm_ip_config_get_addr_family (ip_config);
 
 	nm_assert_addr_family (addr_family);
+	nm_assert (ifindex > 0);
+	nm_assert (ifindex == nm_ip_config_get_ifindex (ip_config));
 
-	num = nm_ip_config_get_num_nameservers (config);
+	num = nm_ip_config_get_num_nameservers (ip_config);
 	for (i = 0; i < num; i++) {
 		const NMIPAddr *addr;
 
-		addr = nm_ip_config_get_nameserver (config, i);
+		addr = nm_ip_config_get_nameserver (ip_config, i);
 		if (addr_family == AF_INET)
 			nm_utils_inet_ntop (addr_family, addr, buf);
 		else if (IN6_IS_ADDR_V4MAPPED (addr))
@@ -307,49 +438,42 @@ merge_one_ip_config (NMResolvConfData *rc,
 		else {
 			nm_utils_inet6_ntop (&addr->addr6, buf);
 			if (IN6_IS_ADDR_LINKLOCAL (addr)) {
-				g_strlcat (buf, "%", sizeof (buf));
-				g_strlcat (buf, iface, sizeof (buf));
+				const char *ifname;
+
+				ifname = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+				if (ifname) {
+					g_strlcat (buf, "%", sizeof (buf));
+					g_strlcat (buf, ifname, sizeof (buf));
+				}
 			}
 		}
 
-		add_string_item (rc->nameservers, buf);
+		add_string_item (rc->nameservers, buf, TRUE);
 	}
 
-	num_domains = nm_ip_config_get_num_domains (config);
-	num_searches = nm_ip_config_get_num_searches (config);
-	for (i = 0; i < num_searches; i++) {
-		str = nm_ip_config_get_search (config, i);
-		if (domain_is_valid (str, FALSE))
-			add_string_item (rc->searches, str);
-	}
-	if (num_domains > 1 || !num_searches) {
-		for (i = 0; i < num_domains; i++) {
-			str = nm_ip_config_get_domain (config, i);
-			if (domain_is_valid (str, FALSE))
-				add_string_item (rc->searches, str);
-		}
-	}
+	add_dns_domains (rc->searches, ip_config, FALSE, TRUE);
 
-	num = nm_ip_config_get_num_dns_options (config);
+	num = nm_ip_config_get_num_dns_options (ip_config);
 	for (i = 0; i < num; i++) {
 		add_dns_option_item (rc->options,
-		                     nm_ip_config_get_dns_option (config, i));
+		                     nm_ip_config_get_dns_option (ip_config, i));
 	}
 
 	if (addr_family == AF_INET) {
-		const NMIP4Config *config4 = (const NMIP4Config *) config;
+		const NMIP4Config *ip4_config = (const NMIP4Config *) ip_config;
 
 		/* NIS stuff */
-		num = nm_ip4_config_get_num_nis_servers (config4);
+		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 (config4, i), buf));
+			                 nm_utils_inet4_ntop (nm_ip4_config_get_nis_server (ip4_config, i), buf),
+			                 TRUE);
 		}
 
-		if (nm_ip4_config_get_nis_domain (config4)) {
+		if (nm_ip4_config_get_nis_domain (ip4_config)) {
 			/* FIXME: handle multiple domains */
 			if (!rc->nis_domain)
-				rc->nis_domain = nm_ip4_config_get_nis_domain (config4);
+				rc->nis_domain = nm_ip4_config_get_nis_domain (ip4_config);
 		}
 	}
 }
@@ -378,58 +502,59 @@ run_netconfig (NMDnsManager *self, GError **error, gint *stdin_fd)
 }
 
 static void
-write_to_netconfig (NMDnsManager *self, gint fd, const char *key, const char *value)
+netconfig_construct_str (NMDnsManager *self, GString *str, const char *key, const char *value)
 {
-	char *str;
-	int x;
+	if (value) {
+		_LOGD ("writing to netconfig: %s='%s'", key, value);
+		g_string_append_printf (str, "%s='%s'\n", key, value);
+	}
+}
 
-	str = g_strdup_printf ("%s='%s'\n", key, value);
-	_LOGD ("writing to netconfig: %s", str);
-	x = write (fd, str, strlen (str));
-	g_free (str);
+static void
+netconfig_construct_strv (NMDnsManager *self, GString *str, const char *key, const char *const*values)
+{
+	if (values) {
+		gs_free char *value = NULL;
+
+		value = g_strjoinv (" ", (char **) values);
+		netconfig_construct_str (self, str, key, value);
+	}
 }
 
 static SpawnResult
 dispatch_netconfig (NMDnsManager *self,
-                    char **searches,
-                    char **nameservers,
+                    const char *const*searches,
+                    const char *const*nameservers,
                     const char *nis_domain,
-                    char **nis_servers,
+                    const char *const*nis_servers,
                     GError **error)
 {
-	char *str;
 	GPid pid;
 	gint fd;
 	int status;
+	gssize l;
+	nm_auto_free_gstring GString *str = NULL;
 
 	pid = run_netconfig (self, error, &fd);
 	if (pid <= 0)
 		return SR_NOTFOUND;
 
+	str = g_string_new ("");
+
 	/* NM is writing already-merged DNS information to netconfig, so it
 	 * does not apply to a specific network interface.
 	 */
-	write_to_netconfig (self, fd, "INTERFACE", "NetworkManager");
+	netconfig_construct_str (self, str, "INTERFACE", "NetworkManager");
+	netconfig_construct_strv (self, str, "DNSSEARCH", searches);
+	netconfig_construct_strv (self, str, "DNSSERVERS", nameservers);
+	netconfig_construct_str (self, str, "NISDOMAIN", nis_domain);
+	netconfig_construct_strv (self, str, "NISSERVERS", nis_servers);
 
-	if (searches) {
-		str = g_strjoinv (" ", searches);
-		write_to_netconfig (self, fd, "DNSSEARCH", str);
-		g_free (str);
-	}
-
-	if (nameservers) {
-		str = g_strjoinv (" ", nameservers);
-		write_to_netconfig (self, fd, "DNSSERVERS", str);
-		g_free (str);
-	}
-
-	if (nis_domain)
-		write_to_netconfig (self, fd, "NISDOMAIN", nis_domain);
-
-	if (nis_servers) {
-		str = g_strjoinv (" ", nis_servers);
-		write_to_netconfig (self, fd, "NISSERVERS", str);
-		g_free (str);
+again:
+	l = write (fd, str->str, str->len);
+	if (l == -1)  {
+		if (errno == EINTR)
+			goto again;
 	}
 
 	nm_close (fd);
@@ -743,7 +868,7 @@ update_resolv_conf (NMDnsManager *self,
 
 	if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) {
 		_LOGT ("update-resolv-conf: write internal file %s succeeded (rc-manager=%s)",
-		       rc_path, _rc_manager_to_string (rc_manager));
+		       MY_RESOLV_CONF, _rc_manager_to_string (rc_manager));
 		return write_file_result;
 	}
 
@@ -817,25 +942,21 @@ update_resolv_conf (NMDnsManager *self,
 static void
 compute_hash (NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[HASH_LEN])
 {
-	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 	GChecksum *sum;
 	gsize len = HASH_LEN;
-	guint i;
+	NMDnsIPConfigData *ip_data;
 
 	sum = g_checksum_new (G_CHECKSUM_SHA1);
-	g_assert (len == g_checksum_type_get_length (G_CHECKSUM_SHA1));
+	nm_assert (len == g_checksum_type_get_length (G_CHECKSUM_SHA1));
 
 	if (global)
 		nm_global_dns_config_update_checksum (global, sum);
 	else {
-		for (i = 0; i < priv->configs->len; i++) {
-			NMDnsIPConfigData *data = priv->configs->pdata[i];
+		const CList *head;
 
-			if (NM_IS_IP4_CONFIG (data->config))
-				nm_ip4_config_hash ((NMIP4Config *) data->config, sum, TRUE);
-			else if (NM_IS_IP6_CONFIG (data->config))
-				nm_ip6_config_hash ((NMIP6Config *) data->config, sum, TRUE);
-		}
+		head = _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);
 	}
 
 	g_checksum_get_digest (sum, buffer, &len);
@@ -849,27 +970,36 @@ merge_global_dns_config (NMResolvConfData *rc, NMGlobalDnsConfig *global_conf)
 	const char *const *searches;
 	const char *const *options;
 	const char *const *servers;
-	gint i;
+	guint i;
 
 	if (!global_conf)
 		return FALSE;
 
 	searches = nm_global_dns_config_get_searches (global_conf);
-	options = nm_global_dns_config_get_options (global_conf);
-
-	for (i = 0; searches && searches[i]; i++) {
-		if (domain_is_valid (searches[i], FALSE))
-			add_string_item (rc->searches, searches[i]);
+	if (searches) {
+		for (i = 0; searches[i]; i++) {
+			if (domain_is_routing (searches[i]))
+				continue;
+			if (!domain_is_valid (searches[i], FALSE))
+				continue;
+			add_string_item (rc->searches, searches[i], TRUE);
+		}
 	}
 
-	for (i = 0; options && options[i]; i++)
-		add_string_item (rc->options, options[i]);
+	options = nm_global_dns_config_get_options (global_conf);
+	if (options) {
+		for (i = 0; options[i]; i++)
+			add_string_item (rc->options, options[i], TRUE);
+	}
 
 	default_domain = nm_global_dns_config_lookup_domain (global_conf, "*");
-	g_assert (default_domain);
+	nm_assert (default_domain);
+
 	servers = nm_global_dns_domain_get_servers (default_domain);
-	for (i = 0; servers && servers[i]; i++)
-		add_string_item (rc->nameservers, servers[i]);
+	if (servers) {
+		for (i = 0; servers[i]; i++)
+			add_string_item (rc->nameservers, servers[i], TRUE);
+	}
 
 	return TRUE;
 }
@@ -909,17 +1039,16 @@ _ptrarray_to_strv (GPtrArray *parray)
 }
 
 static void
-_collect_resolv_conf_data (NMDnsManager *self, /* only for logging context, no other side-effects */
+_collect_resolv_conf_data (NMDnsManager *self,
                            NMGlobalDnsConfig *global_config,
-                           const GPtrArray *configs,
-                           const char *hostname,
                            char ***out_searches,
                            char ***out_options,
                            char ***out_nameservers,
                            char ***out_nis_servers,
                            const char **out_nis_domain)
 {
-	guint i, j, num, len;
+	NMDnsManagerPrivate *priv;
+	guint i, num, len;
 	NMResolvConfData rc = {
 		.nameservers = g_ptr_array_new (),
 		.searches = g_ptr_array_new (),
@@ -928,37 +1057,44 @@ _collect_resolv_conf_data (NMDnsManager *self, /* only for logging context, no o
 		.nis_servers = g_ptr_array_new (),
 	};
 
+	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+
 	if (global_config)
 		merge_global_dns_config (&rc, global_config);
 	else {
 		nm_auto_free_gstring GString *tmp_gstring = NULL;
 		int prio, first_prio = 0;
-		NMDnsIPConfigData *current;
+		const NMDnsIPConfigData *ip_data;
+		const CList *head;
+		gboolean is_first = TRUE;
 
-		for (i = 0, j = 0; i < configs->len; i++) {
+		head = _ip_config_lst_head (self);
+		c_list_for_each_entry (ip_data, head, ip_config_lst) {
 			gboolean skip = FALSE;
 
-			current = configs->pdata[i];
+			_ASSERT_ip_config_data (ip_data);
 
-			prio = nm_ip_config_get_dns_priority (current->config);
+			prio = nm_ip_config_get_dns_priority (ip_data->ip_config);
 
-			if (i == 0)
+			if (is_first) {
+				is_first = FALSE;
 				first_prio = prio;
-			else if (first_prio < 0 && first_prio != prio)
+			} else if (   first_prio < 0
+			           && first_prio != prio)
 				skip = TRUE;
 
-			if (nm_ip_config_get_num_nameservers (current->config)) {
-				_LOGT ("config: %8d %-7s v%c %-16s %s: %s",
+			if (nm_ip_config_get_num_nameservers (ip_data->ip_config)) {
+				_LOGT ("config: %8d %-7s v%c %-5d %s: %s",
 				       prio,
-				       _config_type_to_string (current->type),
-				       nm_utils_addr_family_to_char (nm_ip_config_get_addr_family (current->config)),
-				       current->iface,
+				       _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)),
+				       ip_data->data->ifindex,
 				       skip ? "<SKIP>" : "",
-				       get_nameserver_list (current->config, &tmp_gstring));
+				       get_nameserver_list (ip_data->ip_config, &tmp_gstring));
 			}
 
 			if (!skip)
-				merge_one_ip_config (&rc, current->config, current->iface);
+				merge_one_ip_config (&rc, ip_data->data->ifindex, ip_data->ip_config);
 		}
 	}
 
@@ -969,16 +1105,16 @@ _collect_resolv_conf_data (NMDnsManager *self, /* only for logging context, no o
 	 * (eg, "example.com"), then use the hostname itself as the search (since the user is
 	 * unlikely to want "com" as a search domain).
 	 */
-	if (hostname) {
-		const char *hostdomain = strchr (hostname, '.');
+	if (priv->hostname) {
+		const char *hostdomain = strchr (priv->hostname, '.');
 
 		if (   hostdomain
-		    && !nm_utils_ipaddr_valid (AF_UNSPEC, hostname)) {
+		    && !nm_utils_ipaddr_valid (AF_UNSPEC, priv->hostname)) {
 			hostdomain++;
 			if (domain_is_valid (hostdomain, TRUE))
-				add_string_item (rc.searches, hostdomain);
-			else if (domain_is_valid (hostname, TRUE))
-				add_string_item (rc.searches, hostname);
+				add_string_item (rc.searches, hostdomain, TRUE);
+			else if (domain_is_valid (priv->hostname, TRUE))
+				add_string_item (rc.searches, priv->hostname, TRUE);
 		}
 	}
 
@@ -1040,16 +1176,12 @@ update_dns (NMDnsManager *self,
 	data = nm_config_get_data (priv->config);
 	global_config = nm_config_data_get_global_dns_config (data);
 
-	if (priv->need_sort) {
-		g_ptr_array_sort (priv->configs, ip_config_data_ptr_compare);
-		priv->need_sort = FALSE;
-	}
-
 	/* Update hash with config we're applying */
 	compute_hash (self, global_config, priv->hash);
 
-	_collect_resolv_conf_data (self, global_config, priv->configs, priv->hostname,
-	                           &searches, &options, &nameservers, &nis_servers, &nis_domain);
+	_collect_resolv_conf_data (self, global_config,
+	                           &searches, &options, &nameservers,
+	                           &nis_servers, &nis_domain);
 
 	/* Let any plugins do their thing first */
 	if (priv->plugin) {
@@ -1067,8 +1199,8 @@ update_dns (NMDnsManager *self,
 
 		_LOGD ("update-dns: updating plugin %s", plugin_name);
 		if (!nm_dns_plugin_update (plugin,
-		                           priv->configs,
 		                           global_config,
+		                           _ip_config_lst_head (self),
 		                           priv->hostname)) {
 			_LOGW ("update-dns: plugin %s update failed", plugin_name);
 
@@ -1114,8 +1246,12 @@ update_dns (NMDnsManager *self,
 			result = dispatch_resolvconf (self, searches, nameservers, options, error);
 			break;
 		case NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG:
-			result = dispatch_netconfig (self, searches, nameservers, nis_domain,
-			                             nis_servers, error);
+			result = dispatch_netconfig (self,
+			                             (const char *const*) searches,
+			                             (const char *const*) nameservers,
+			                             nis_domain,
+			                             (const char *const*) nis_servers,
+			                             error);
 			break;
 		default:
 			g_assert_not_reached ();
@@ -1205,82 +1341,96 @@ plugin_child_quit (NMDnsPlugin *plugin, int exit_status, gpointer user_data)
 }
 
 static void
-ip_config_dns_priority_changed (gpointer config,
-                                GParamSpec *pspec,
-                                NMDnsManager *self)
+_ip_config_dns_priority_changed (gpointer config,
+                                 GParamSpec *pspec,
+                                 NMDnsIPConfigData *ip_data)
 {
-	NM_DNS_MANAGER_GET_PRIVATE (self)->need_sort = TRUE;
-}
+	_ASSERT_ip_config_data (ip_data);
 
-static void
-forget_data (NMDnsManager *self, NMDnsIPConfigData *data)
-{
-	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-
-	if (data == priv->best_conf4)
-		priv->best_conf4 = NULL;
-	else if (data == priv->best_conf6)
-		priv->best_conf6 = NULL;
-
-	g_signal_handlers_disconnect_by_func (data->config, ip_config_dns_priority_changed, self);
+	NM_DNS_MANAGER_GET_PRIVATE (ip_data->data->self)->ip_config_lst_need_sort = TRUE;
 }
 
 gboolean
-nm_dns_manager_add_ip_config (NMDnsManager *self,
-                              const char *iface,
-                              gpointer config,
-                              NMDnsIPConfigType cfg_type)
+nm_dns_manager_set_ip_config (NMDnsManager *self,
+                              NMIPConfig *ip_config,
+                              NMDnsIPConfigType ip_config_type)
 {
 	NMDnsManagerPrivate *priv;
 	GError *error = NULL;
-	NMDnsIPConfigData *data;
-	gboolean v4 = NM_IS_IP4_CONFIG (config);
-	guint i;
+	NMDnsIPConfigData *ip_data;
+	NMDnsConfigData *data;
+	int ifindex;
+	NMDnsIPConfigData **p_best;
 
 	g_return_val_if_fail (NM_IS_DNS_MANAGER (self), FALSE);
-	g_return_val_if_fail (config, FALSE);
-	g_return_val_if_fail (iface && iface[0], FALSE);
-	nm_assert (NM_IS_IP_CONFIG (config));
+	g_return_val_if_fail (NM_IS_IP_CONFIG (ip_config), FALSE);
+
+	ifindex = nm_ip_config_get_ifindex (ip_config);
+	g_return_val_if_fail (ifindex > 0, FALSE);
 
 	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 
-	for (i = 0; i < priv->configs->len; i++) {
-		data = priv->configs->pdata[i];
-		if (data->config == config) {
-			if (   nm_streq (data->iface, iface)
-			    && data->type == cfg_type)
-				return FALSE;
-			else {
-				forget_data (self, data);
-				g_ptr_array_remove_index_fast (priv->configs, i);
-				break;
-			}
-		}
+	data = g_hash_table_lookup (priv->configs, GINT_TO_POINTER (ifindex));
+	if (!data)
+		ip_data = NULL;
+	else
+		ip_data = _config_data_find_ip_config (data, ip_config);
+
+	if (ip_config_type == NM_DNS_IP_CONFIG_TYPE_REMOVED) {
+		if (!ip_data)
+			return FALSE;
+		if (priv->best_ip_config_4 == ip_data)
+			priv->best_ip_config_4 = NULL;
+		if (priv->best_ip_config_6 == ip_data)
+			priv->best_ip_config_6 = NULL;
+		/* deleting a config doesn't invalidate the configs' sort order. */
+		_ip_config_data_free (ip_data);
+		if (c_list_is_empty (&data->data_lst_head))
+			g_hash_table_remove (priv->configs, GINT_TO_POINTER (ifindex));
+		goto changed;
+	}
+
+	if (   ip_data
+	    && ip_data->ip_config_type == ip_config_type) {
+		/* nothing to do. */
+		return FALSE;
 	}
 
-	data = ip_config_data_new (config, cfg_type, iface);
-	g_ptr_array_add (priv->configs, data);
-	g_signal_connect (config,
-	                  v4 ?
-	                    "notify::" NM_IP4_CONFIG_DNS_PRIORITY :
-	                    "notify::" NM_IP6_CONFIG_DNS_PRIORITY,
-	                  (GCallback) ip_config_dns_priority_changed, self);
-	priv->need_sort = TRUE;
+	if (!data) {
+		data = g_slice_new0 (NMDnsConfigData);
+		data->ifindex = ifindex;
+		data->self = self;
+		c_list_init (&data->data_lst_head);
+		_ASSERT_config_data (data);
+		g_hash_table_insert (priv->configs, GINT_TO_POINTER (ifindex), data);
+	}
+
+	if (!ip_data)
+		ip_data = _ip_config_data_new (data, ip_config, ip_config_type);
+	else
+		ip_data->ip_config_type = ip_config_type;
+
+	priv->ip_config_lst_need_sort = TRUE;
+
+	p_best = NM_IS_IP4_CONFIG (ip_config)
+	         ? &priv->best_ip_config_4
+	         : &priv->best_ip_config_6;
 
-	if (cfg_type == NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE) {
+	if (ip_config_type == NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE) {
 		/* Only one best-device per IP version is allowed */
-		if (v4) {
-			if (priv->best_conf4)
-				priv->best_conf4->type = NM_DNS_IP_CONFIG_TYPE_DEFAULT;
-			priv->best_conf4 = data;
-		} else {
-			if (priv->best_conf6)
-				priv->best_conf6->type = NM_DNS_IP_CONFIG_TYPE_DEFAULT;
-			priv->best_conf6 = data;
+		if (*p_best != ip_data) {
+			if (*p_best)
+				(*p_best)->ip_config_type = NM_DNS_IP_CONFIG_TYPE_DEFAULT;
+			*p_best = ip_data;
 		}
+	} else {
+		if (*p_best == ip_data)
+			*p_best = NULL;
 	}
 
-	if (!priv->updates_queue && !update_dns (self, FALSE, &error)) {
+changed:
+	if (   !priv->updates_queue
+	    && !update_dns (self, FALSE, &error)) {
 		_LOGW ("could not commit DNS changes: %s", error->message);
 		g_clear_error (&error);
 	}
@@ -1288,38 +1438,6 @@ nm_dns_manager_add_ip_config (NMDnsManager *self,
 	return TRUE;
 }
 
-gboolean
-nm_dns_manager_remove_ip_config (NMDnsManager *self, gpointer config)
-{
-	NMDnsManagerPrivate *priv;
-	GError *error = NULL;
-	NMDnsIPConfigData *data;
-	guint i;
-
-	g_return_val_if_fail (NM_IS_DNS_MANAGER (self), FALSE);
-	g_return_val_if_fail (config, FALSE);
-	nm_assert (NM_IS_IP_CONFIG (config));
-
-	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-
-	for (i = 0; i < priv->configs->len; i++) {
-		data = priv->configs->pdata[i];
-
-		if (data->config == config) {
-			forget_data (self, data);
-			g_ptr_array_remove_index (priv->configs, i);
-
-			if (!priv->updates_queue && !update_dns (self, FALSE, &error)) {
-				_LOGW ("could not commit DNS changes: %s", error->message);
-				g_clear_error (&error);
-			}
-
-			return TRUE;
-		}
-	}
-	return FALSE;
-}
-
 void
 nm_dns_manager_set_initial_hostname (NMDnsManager *self,
                                      const char *hostname)
@@ -1362,23 +1480,6 @@ nm_dns_manager_set_hostname (NMDnsManager *self,
 	}
 }
 
-gboolean
-nm_dns_manager_get_resolv_conf_explicit (NMDnsManager *self)
-{
-	NMDnsManagerPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_DNS_MANAGER (self), FALSE);
-
-	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-
-	if (   NM_IN_SET (priv->rc_manager, NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED,
-	                                    NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE)
-	    || priv->plugin)
-		return FALSE;
-
-	return TRUE;
-}
-
 void
 nm_dns_manager_begin_updates (NMDnsManager *self, const char *func)
 {
@@ -1409,11 +1510,6 @@ nm_dns_manager_end_updates (NMDnsManager *self, const char *func)
 	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 	g_return_if_fail (priv->updates_queue > 0);
 
-	if (priv->need_sort) {
-		g_ptr_array_sort (priv->configs, ip_config_data_ptr_compare);
-		priv->need_sort = FALSE;
-	}
-
 	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");
@@ -1810,14 +1906,14 @@ _get_config_variant (NMDnsManager *self)
 	NMGlobalDnsConfig *global_config;
 	gs_free char *str = NULL;
 	GVariantBuilder builder;
-	NMConfigData *data;
-	guint i, j;
+	NMDnsIPConfigData *ip_data;
+	const CList *head;
+	gs_unref_ptrarray GPtrArray *array_domains = NULL;
 
 	if (priv->config_variant)
 		return priv->config_variant;
 
-	data = nm_config_get_data (priv->config);
-	global_config = nm_config_data_get_global_dns_config (data);
+	global_config = nm_config_data_get_global_dns_config (nm_config_get_data (priv->config));
 	if (global_config) {
 		priv->config_variant = _get_global_config_variant (global_config);
 		_LOGT ("current configuration: %s", (str = g_variant_print (priv->config_variant, TRUE)));
@@ -1826,25 +1922,26 @@ _get_config_variant (NMDnsManager *self)
 
 	g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
 
-	for (i = 0; i < priv->configs->len; i++) {
-		NMDnsIPConfigData *current = priv->configs->pdata[i];
-		const NMIPConfig *config = current->config;
+	head = _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 num;
-		const int addr_family = nm_ip_config_get_addr_family (config);
+		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 (config);
+		num = nm_ip_config_get_num_nameservers (ip_config);
 		if (!num)
 			continue;
 
 		g_variant_builder_init (&entry_builder, G_VARIANT_TYPE ("a{sv}"));
 
 		g_variant_builder_init (&strv_builder, G_VARIANT_TYPE ("as"));
-		for (j = 0; j < num; j++) {
-			addr = nm_ip_config_get_nameserver (config, j);
+		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));
@@ -1854,36 +1951,47 @@ _get_config_variant (NMDnsManager *self)
 		                       "nameservers",
 		                       g_variant_builder_end (&strv_builder));
 
-		num = nm_ip_config_get_num_domains (config);
+
+		num = nm_ip_config_get_num_domains (ip_config);
+		num += nm_ip_config_get_num_searches (ip_config);
 		if (num > 0) {
-			g_variant_builder_init (&strv_builder, G_VARIANT_TYPE ("as"));
-			for (j = 0; j < num; j++) {
-				g_variant_builder_add (&strv_builder,
-				                       "s",
-				                       nm_ip_config_get_domain (config, j));
+			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);
+			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_builder_add (&entry_builder,
-			                       "{sv}",
-			                       "domains",
-			                       g_variant_builder_end (&strv_builder));
 		}
 
-		if (current->iface) {
+		ifname = nm_platform_link_get_name (NM_PLATFORM_GET, ip_data->data->ifindex);
+		if (ifname) {
 			g_variant_builder_add (&entry_builder,
 			                       "{sv}",
 			                       "interface",
-			                       g_variant_new_string (current->iface));
+			                       g_variant_new_string (ifname));
 		}
 
 		g_variant_builder_add (&entry_builder,
 		                       "{sv}",
 		                       "priority",
-		                       g_variant_new_int32 (nm_ip_config_get_dns_priority (config)));
+		                       g_variant_new_int32 (nm_ip_config_get_dns_priority (ip_config)));
 
 		g_variant_builder_add (&entry_builder,
 		                       "{sv}",
 		                       "vpn",
-		                       g_variant_new_boolean (current->type == NM_DNS_IP_CONFIG_TYPE_VPN));
+		                       g_variant_new_boolean (ip_data->ip_config_type == NM_DNS_IP_CONFIG_TYPE_VPN));
 
 		g_variant_builder_add (&builder, "a{sv}", &entry_builder);
 	}
@@ -1924,8 +2032,12 @@ nm_dns_manager_init (NMDnsManager *self)
 
 	_LOGT ("creating...");
 
+	c_list_init (&priv->ip_config_lst_head);
+
 	priv->config = g_object_ref (nm_config_get ());
-	priv->configs = g_ptr_array_new_full (8, ip_config_data_destroy);
+
+	priv->configs = g_hash_table_new_full (nm_direct_hash, NULL,
+	                                       NULL, (GDestroyNotify) _config_data_free);
 
 	/* Set the initial hash */
 	compute_hash (self, NULL, NM_DNS_MANAGER_GET_PRIVATE (self)->hash);
@@ -1942,33 +2054,33 @@ dispose (GObject *object)
 {
 	NMDnsManager *self = NM_DNS_MANAGER (object);
 	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-	NMDnsIPConfigData *data;
-	guint i;
+	NMDnsIPConfigData *ip_data, *ip_data_safe;
 
 	_LOGT ("disposing");
 
 	if (!priv->is_stopped)
 		nm_dns_manager_stop (self);
 
+	if (priv->config)
+		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
+
 	_clear_plugin (self);
 
-	if (priv->config) {
-		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
-		g_clear_object (&priv->config);
-	}
+	priv->best_ip_config_4 = NULL;
+	priv->best_ip_config_6 = NULL;
 
-	if (priv->configs) {
-		for (i = 0; i < priv->configs->len; i++) {
-			data = priv->configs->pdata[i];
-			forget_data (self, data);
-		}
-		g_ptr_array_free (priv->configs, TRUE);
-		priv->configs = NULL;
-	}
+	c_list_for_each_entry_safe (ip_data, ip_data_safe, &priv->ip_config_lst_head, ip_config_lst)
+		_ip_config_data_free (ip_data);
+
+	g_clear_pointer (&priv->configs, g_hash_table_destroy);
 
 	nm_clear_g_source (&priv->plugin_ratelimit.timer);
 
+	g_clear_object (&priv->config);
+
 	G_OBJECT_CLASS (nm_dns_manager_parent_class)->dispose (object);
+
+	g_clear_pointer (&priv->config_variant, g_variant_unref);
 }
 
 static void
@@ -1983,18 +2095,30 @@ finalize (GObject *object)
 	G_OBJECT_CLASS (nm_dns_manager_parent_class)->finalize (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_dns_manager = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DNS_MANAGER,
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",          "s",      NM_DNS_MANAGER_MODE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("RcManager",     "s",      NM_DNS_MANAGER_RC_MANAGER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Configuration", "aa{sv}", NM_DNS_MANAGER_CONFIGURATION),
+		),
+	),
+};
+
 static void
 nm_dns_manager_class_init (NMDnsManagerClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 	object_class->get_property = get_property;
 
-	exported_object_class->export_path = NM_DBUS_PATH "/DnsManager";
-	exported_object_class->export_on_construction = TRUE;
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC (NM_DBUS_PATH "/DnsManager");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_dns_manager);
+	dbus_object_class->export_on_construction = TRUE;
 
 	obj_properties[PROP_MODE] =
 	    g_param_spec_string (NM_DNS_MANAGER_MODE, "", "",
@@ -2024,9 +2148,4 @@ nm_dns_manager_class_init (NMDnsManagerClass *klass)
 	                  0, NULL, NULL,
 	                  g_cclosure_marshal_VOID__VOID,
 	                  G_TYPE_NONE, 0);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DNS_MANAGER_SKELETON,
-	                                        NULL);
 }
-
diff --git a/src/dns/nm-dns-manager.h b/src/dns/nm-dns-manager.h
index b38ea701..56889367 100644
--- a/src/dns/nm-dns-manager.h
+++ b/src/dns/nm-dns-manager.h
@@ -26,11 +26,14 @@
 
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
+#include "nm-setting-connection.h"
 
 typedef enum {
+	NM_DNS_IP_CONFIG_TYPE_REMOVED = -1,
+
 	NM_DNS_IP_CONFIG_TYPE_DEFAULT = 0,
 	NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE,
-	NM_DNS_IP_CONFIG_TYPE_VPN
+	NM_DNS_IP_CONFIG_TYPE_VPN,
 } NMDnsIPConfigType;
 
 enum {
@@ -38,12 +41,23 @@ enum {
 	NM_DNS_PRIORITY_DEFAULT_VPN     = 50,
 };
 
+struct _NMDnsConfigData;
+struct _NMDnsManager;
+
 typedef struct {
-	gpointer config;
-	NMDnsIPConfigType type;
-	char *iface;
+	struct _NMDnsConfigData *data;
+	NMIPConfig *ip_config;
+	CList data_lst;
+	CList ip_config_lst;
+	NMDnsIPConfigType ip_config_type;
 } NMDnsIPConfigData;
 
+typedef struct _NMDnsConfigData {
+	struct _NMDnsManager *self;
+	CList data_lst_head;
+	int ifindex;
+} NMDnsConfigData;
+
 #define NM_TYPE_DNS_MANAGER (nm_dns_manager_get_type ())
 #define NM_DNS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NM_TYPE_DNS_MANAGER, NMDnsManager))
 #define NM_DNS_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NM_TYPE_DNS_MANAGER, NMDnsManagerClass))
@@ -70,12 +84,9 @@ NMDnsManager * nm_dns_manager_get (void);
 void nm_dns_manager_begin_updates (NMDnsManager *self, const char *func);
 void nm_dns_manager_end_updates (NMDnsManager *self, const char *func);
 
-gboolean nm_dns_manager_add_ip_config (NMDnsManager *self,
-                                       const char *iface,
-                                       gpointer config,
-                                       NMDnsIPConfigType cfg_type);
-
-gboolean nm_dns_manager_remove_ip_config (NMDnsManager *self, gpointer config);
+gboolean nm_dns_manager_set_ip_config (NMDnsManager *self,
+                                       NMIPConfig *ip_config,
+                                       NMDnsIPConfigType ip_config_type);
 
 void nm_dns_manager_set_initial_hostname (NMDnsManager *self,
                                           const char *hostname);
@@ -112,8 +123,6 @@ typedef enum {
 	NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG,
 } NMDnsManagerResolvConfManager;
 
-gboolean nm_dns_manager_get_resolv_conf_explicit (NMDnsManager *self);
-
 void nm_dns_manager_stop (NMDnsManager *self);
 
 #endif /* __NETWORKMANAGER_DNS_MANAGER_H__ */
diff --git a/src/dns/nm-dns-plugin.c b/src/dns/nm-dns-plugin.c
index 5805b7d8..d9400e3e 100644
--- a/src/dns/nm-dns-plugin.c
+++ b/src/dns/nm-dns-plugin.c
@@ -77,15 +77,15 @@ G_DEFINE_TYPE_EXTENDED (NMDnsPlugin, nm_dns_plugin, G_TYPE_OBJECT, G_TYPE_FLAG_A
 
 gboolean
 nm_dns_plugin_update (NMDnsPlugin *self,
-                      const GPtrArray *configs,
                       const NMGlobalDnsConfig *global_config,
+                      const CList *ip_config_lst_head,
                       const char *hostname)
 {
 	g_return_val_if_fail (NM_DNS_PLUGIN_GET_CLASS (self)->update != NULL, FALSE);
 
 	return NM_DNS_PLUGIN_GET_CLASS (self)->update (self,
-	                                               configs,
 	                                               global_config,
+	                                               ip_config_lst_head,
 	                                               hostname);
 }
 
diff --git a/src/dns/nm-dns-plugin.h b/src/dns/nm-dns-plugin.h
index 996695c0..80b77d95 100644
--- a/src/dns/nm-dns-plugin.h
+++ b/src/dns/nm-dns-plugin.h
@@ -50,8 +50,8 @@ typedef struct {
 	 * configuration.
 	 */
 	gboolean (*update) (NMDnsPlugin *self,
-	                    const GPtrArray *configs,
 	                    const NMGlobalDnsConfig *global_config,
+	                    const CList *ip_config_lst_head,
 	                    const char *hostname);
 
 	/* Subclasses should override and return TRUE if they start a local
@@ -80,8 +80,8 @@ gboolean nm_dns_plugin_is_caching (NMDnsPlugin *self);
 const char *nm_dns_plugin_get_name (NMDnsPlugin *self);
 
 gboolean nm_dns_plugin_update (NMDnsPlugin *self,
-                               const GPtrArray *configs,
                                const NMGlobalDnsConfig *global_config,
+                               const CList *ip_config_lst_head,
                                const char *hostname);
 
 void nm_dns_plugin_stop (NMDnsPlugin *self);
diff --git a/src/dns/nm-dns-systemd-resolved.c b/src/dns/nm-dns-systemd-resolved.c
index 6ab2ea18..7da27e5f 100644
--- a/src/dns/nm-dns-systemd-resolved.c
+++ b/src/dns/nm-dns-systemd-resolved.c
@@ -31,13 +31,15 @@
 #include <sys/stat.h>
 #include <linux/if.h>
 
+#include "nm-utils/nm-c-list.h"
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
 #include "nm-utils.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
 #include "nm-manager.h"
+#include "nm-setting-connection.h"
 #include "devices/nm-device.h"
 #include "NetworkManagerUtils.h"
 
@@ -48,17 +50,23 @@
 
 typedef struct {
 	int ifindex;
-	GList *configs;
+	CList configs_lst_head;
 } InterfaceConfig;
 
+typedef struct {
+	CList request_queue_lst;
+	const char *operation;
+	GVariant *argument;
+} RequestItem;
+
 /*****************************************************************************/
 
 typedef struct {
 	GDBusProxy *resolve;
 	GCancellable *init_cancellable;
 	GCancellable *update_cancellable;
-	GQueue dns_updates;
-	GQueue domain_updates;
+	GCancellable *mdns_cancellable;
+	CList request_queue_lst_head;
 } NMDnsSystemdResolvedPrivate;
 
 struct _NMDnsSystemdResolved {
@@ -82,129 +90,95 @@ G_DEFINE_TYPE (NMDnsSystemdResolved, nm_dns_systemd_resolved, NM_TYPE_DNS_PLUGIN
 /*****************************************************************************/
 
 static void
-call_done (GObject *source, GAsyncResult *r, gpointer user_data)
+_request_item_free (RequestItem *request_item)
 {
-	GVariant *v;
-	GError *error = NULL;
-	NMDnsSystemdResolved *self = (NMDnsSystemdResolved *) user_data;
-
-	v = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), r, &error);
+	c_list_unlink_stale (&request_item->request_queue_lst);
+	g_variant_unref (request_item->argument);
+	g_slice_free (RequestItem, request_item);
+}
 
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
+static void
+_request_item_append (CList *request_queue_lst_head,
+                      const char *operation,
+                      GVariant *argument)
+{
+	RequestItem *request_item;
 
-	if (error != NULL) {
-		_LOGW ("Failed: %s\n", error->message);
-		g_error_free (error);
-	}
+	request_item = g_slice_new (RequestItem);
+	request_item->operation = operation;
+	request_item->argument = g_variant_ref_sink (argument);
+	c_list_link_tail (request_queue_lst_head, &request_item->request_queue_lst);
 }
 
+/*****************************************************************************/
+
 static void
-add_interface_configuration (NMDnsSystemdResolved *self,
-                             GArray *interfaces,
-                             const NMDnsIPConfigData *data,
-                             gboolean skip)
+_interface_config_free (InterfaceConfig *config)
 {
-	int i;
-	InterfaceConfig *ic = NULL;
-	int ifindex;
+	nm_c_list_elem_free_all (&config->configs_lst_head, NULL);
+	g_slice_free (InterfaceConfig, config);
+}
 
-	if (NM_IS_IP4_CONFIG (data->config))
-		ifindex = nm_ip4_config_get_ifindex (data->config);
-	else if (NM_IS_IP6_CONFIG  (data->config))
-		ifindex = nm_ip6_config_get_ifindex (data->config);
-	else
-		g_return_if_reached ();
-
-	for (i = 0; i < interfaces->len; i++) {
-		InterfaceConfig *tic = &g_array_index (interfaces, InterfaceConfig, i);
-		if (ifindex == tic->ifindex) {
-			ic = tic;
-			break;
-		}
-	}
+static void
+call_done (GObject *source, GAsyncResult *r, gpointer user_data)
+{
+	GVariant *v;
+	GError *error = NULL;
+	NMDnsSystemdResolved *self = (NMDnsSystemdResolved *) user_data;
 
-	if (!ic) {
-		g_array_set_size (interfaces, interfaces->len + 1);
-		ic = &g_array_index (interfaces, InterfaceConfig,
-		                     interfaces->len - 1);
-		ic->ifindex = ifindex;
+	v = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), r, &error);
+	if (!v) {
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			return;
+		_LOGW ("Failed: %s\n", error->message);
+		g_error_free (error);
 	}
-
-	if (!skip)
-		ic->configs = g_list_append (ic->configs, data->config);
 }
 
 static void
 update_add_ip_config (NMDnsSystemdResolved *self,
                       GVariantBuilder *dns,
                       GVariantBuilder *domains,
-                      gpointer config)
+                      NMIPConfig *config)
 {
 	int addr_family;
 	gsize addr_size;
 	guint i, n;
-	gboolean route_only;
-
-	if (NM_IS_IP4_CONFIG (config))
-		addr_family = AF_INET;
-	else if (NM_IS_IP6_CONFIG (config))
-		addr_family = AF_INET6;
-	else
-		g_return_if_reached ();
+	gboolean is_routing;
+	const char *domain;
 
+	addr_family = nm_ip_config_get_addr_family (config);
 	addr_size = nm_utils_addr_family_to_size (addr_family);
 
-	n =   addr_family == AF_INET
-	    ? nm_ip4_config_get_num_nameservers (config)
-	    : nm_ip6_config_get_num_nameservers (config);
+	n = nm_ip_config_get_num_nameservers (config);
 	for (i = 0 ; i < n; i++) {
-		in_addr_t ns4;
-		gconstpointer ns;
-
-		if (addr_family == AF_INET) {
-			ns4 = nm_ip4_config_get_nameserver (config, i);
-			ns = &ns4;
-		} else
-			ns = nm_ip6_config_get_nameserver (config, i);
-
 		g_variant_builder_open (dns, G_VARIANT_TYPE ("(iay)"));
 		g_variant_builder_add (dns, "i", addr_family);
 		g_variant_builder_add_value (dns,
 		                             g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-		                                                        ns,
+		                                                        nm_ip_config_get_nameserver (config, i),
 		                                                        addr_size,
 		                                                        1));
 		g_variant_builder_close (dns);
 	}
 
-	/* If this link is never the default (e.g. only used for resources on this
-	 * network) add a routing domain. */
-	route_only =   addr_family == AF_INET
-	             ? !nm_ip4_config_best_default_route_get (config)
-	             : !nm_ip6_config_best_default_route_get (config);
-
-	n =   addr_family == AF_INET
-	    ? nm_ip4_config_get_num_searches (config)
-	    : nm_ip6_config_get_num_searches (config);
+	n = nm_ip_config_get_num_searches (config);
 	if (n  > 0) {
 		for (i = 0; i < n; i++) {
+			domain = nm_utils_parse_dns_domain (nm_ip_config_get_search (config, i),
+			                                    &is_routing);
 			g_variant_builder_add (domains, "(sb)",
-			                       addr_family == AF_INET
-			                         ? nm_ip4_config_get_search (config, i)
-			                         : nm_ip6_config_get_search (config, i),
-			                       route_only);
+			                       domain,
+			                       is_routing);
 		}
 	} else {
-		n =   addr_family == AF_INET
-		    ? nm_ip4_config_get_num_domains (config)
-		    : nm_ip6_config_get_num_domains (config);
+		n = nm_ip_config_get_num_domains (config);
 		for (i = 0; i < n; i++) {
+			domain = nm_utils_parse_dns_domain (nm_ip_config_get_domain (config, i),
+			                                    &is_routing);
 			g_variant_builder_add (domains, "(sb)",
-			                       addr_family == AF_INET
-			                         ? nm_ip4_config_get_domain (config, i)
-			                         : nm_ip6_config_get_domain (config, i),
-			                       route_only);
+			                       domain,
+			                       is_routing);
 		}
 	}
 }
@@ -213,13 +187,13 @@ static void
 free_pending_updates (NMDnsSystemdResolved *self)
 {
 	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
-	GVariant *v;
+	RequestItem *request_item, *request_item_safe;
 
-	while ((v = g_queue_pop_head (&priv->dns_updates)) != NULL)
-		g_variant_unref (v);
-
-	while ((v = g_queue_pop_head (&priv->domain_updates)) != NULL)
-		g_variant_unref (v);
+	c_list_for_each_entry_safe (request_item,
+	                            request_item_safe,
+	                            &priv->request_queue_lst_head,
+	                            request_queue_lst)
+		_request_item_free (request_item);
 }
 
 static void
@@ -227,7 +201,9 @@ prepare_one_interface (NMDnsSystemdResolved *self, InterfaceConfig *ic)
 {
 	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
 	GVariantBuilder dns, domains;
-	GList *l;
+	NMCListElem *elem;
+	NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+	const char *mdns_arg = NULL;
 
 	g_variant_builder_init (&dns, G_VARIANT_TYPE ("(ia(iay))"));
 	g_variant_builder_add (&dns, "i", ic->ifindex);
@@ -237,23 +213,50 @@ prepare_one_interface (NMDnsSystemdResolved *self, InterfaceConfig *ic)
 	g_variant_builder_add (&domains, "i", ic->ifindex);
 	g_variant_builder_open (&domains, G_VARIANT_TYPE ("a(sb)"));
 
-	for (l = ic->configs; l; l = l->next)
-		update_add_ip_config (self, &dns, &domains, l->data);
+	c_list_for_each_entry (elem, &ic->configs_lst_head, lst) {
+		NMIPConfig *ip_config = elem->data;
+
+		update_add_ip_config (self, &dns, &domains, ip_config);
+
+		if (NM_IS_IP4_CONFIG (ip_config))
+			mdns = NM_MAX (mdns, nm_ip4_config_mdns_get (NM_IP4_CONFIG (ip_config)));
+	}
 
 	g_variant_builder_close (&dns);
 	g_variant_builder_close (&domains);
 
-	g_queue_push_tail (&priv->dns_updates,
-	                   g_variant_ref_sink (g_variant_builder_end (&dns)));
-	g_queue_push_tail (&priv->domain_updates,
-	                   g_variant_ref_sink (g_variant_builder_end (&domains)));
+	switch (mdns) {
+	case NM_SETTING_CONNECTION_MDNS_NO:
+		mdns_arg = "no";
+		break;
+	case NM_SETTING_CONNECTION_MDNS_RESOLVE:
+		mdns_arg = "resolve";
+		break;
+	case NM_SETTING_CONNECTION_MDNS_YES:
+		mdns_arg = "yes";
+		break;
+	case NM_SETTING_CONNECTION_MDNS_DEFAULT:
+		mdns_arg = "";
+		break;
+	}
+	nm_assert (mdns_arg);
+
+	_request_item_append (&priv->request_queue_lst_head,
+	                      "SetLinkDNS",
+	                      g_variant_builder_end (&dns));
+	_request_item_append (&priv->request_queue_lst_head,
+	                      "SetLinkDomains",
+	                      g_variant_builder_end (&domains));
+	_request_item_append (&priv->request_queue_lst_head,
+	                      "SetLinkMulticastDNS",
+	                      g_variant_new ("(is)", ic->ifindex, mdns_arg ?: ""));
 }
 
 static void
 send_updates (NMDnsSystemdResolved *self)
 {
 	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
-	GVariant *v;
+	RequestItem *request_item, *request_item_safe;
 
 	nm_clear_g_cancellable (&priv->update_cancellable);
 
@@ -262,55 +265,81 @@ send_updates (NMDnsSystemdResolved *self)
 
 	priv->update_cancellable = g_cancellable_new ();
 
-	while ((v = g_queue_pop_head (&priv->dns_updates)) != NULL) {
-		g_dbus_proxy_call (priv->resolve, "SetLinkDNS", v,
+	c_list_for_each_entry_safe (request_item,
+	                            request_item_safe,
+	                            &priv->request_queue_lst_head,
+	                            request_queue_lst) {
+		g_dbus_proxy_call (priv->resolve,
+		                   request_item->operation,
+		                   request_item->argument,
 		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1, priv->update_cancellable, call_done, self);
-		g_variant_unref (v);
-	}
-
-	while ((v = g_queue_pop_head (&priv->domain_updates)) != NULL) {
-		g_dbus_proxy_call (priv->resolve, "SetLinkDomains", v,
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1, priv->update_cancellable, call_done, self);
-		g_variant_unref (v);
+		                   -1,
+		                   priv->update_cancellable,
+		                   call_done,
+		                   self);
+		_request_item_free (request_item);
 	}
 }
 
 static gboolean
 update (NMDnsPlugin *plugin,
-        const GPtrArray *configs,
         const NMGlobalDnsConfig *global_config,
+        const CList *ip_config_lst_head,
         const char *hostname)
 {
 	NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED (plugin);
-	GArray *interfaces = g_array_new (TRUE, TRUE, sizeof (InterfaceConfig));
+	gs_unref_hashtable GHashTable *interfaces = NULL;
+	gs_free gpointer *interfaces_keys = NULL;
+	guint interfaces_len;
 	guint i;
 	int prio, first_prio = 0;
+	NMDnsIPConfigData *ip_data;
+	gboolean is_first = TRUE;
+
+	interfaces = g_hash_table_new_full (nm_direct_hash, NULL,
+	                                    NULL, (GDestroyNotify) _interface_config_free);
 
-	for (i = 0; i < configs->len; i++) {
-		const NMDnsIPConfigData *data = configs->pdata[i];
+	c_list_for_each_entry (ip_data, ip_config_lst_head, ip_config_lst) {
 		gboolean skip = FALSE;
+		InterfaceConfig *ic = NULL;
+		int ifindex;
 
-		prio = nm_ip_config_get_dns_priority (data->config);
-		if (i == 0)
+		prio = nm_ip_config_get_dns_priority (ip_data->ip_config);
+		if (is_first) {
+			is_first = FALSE;
 			first_prio = prio;
-		else if (first_prio < 0 && first_prio != prio)
+		} else if (first_prio < 0 && first_prio != prio)
 			skip = TRUE;
-		add_interface_configuration (self, interfaces, data, skip);
+
+		ifindex = ip_data->data->ifindex;
+		nm_assert (ifindex == nm_ip_config_get_ifindex (ip_data->ip_config));
+
+		ic = g_hash_table_lookup (interfaces, GINT_TO_POINTER (ifindex));
+		if (!ic) {
+			ic = g_slice_new (InterfaceConfig);
+			ic->ifindex = ifindex;
+			c_list_init (&ic->configs_lst_head);
+			g_hash_table_insert (interfaces, GINT_TO_POINTER (ifindex), ic);
+		}
+
+		if (!skip) {
+			c_list_link_tail (&ic->configs_lst_head,
+			                  &nm_c_list_elem_new_stale (ip_data->ip_config)->lst);
+		}
 	}
 
 	free_pending_updates (self);
 
-	for (i = 0; i < interfaces->len; i++) {
-		InterfaceConfig *ic = &g_array_index (interfaces, InterfaceConfig, i);
+	interfaces_keys = nm_utils_hash_keys_to_array (interfaces,
+	                                               nm_cmp_int2ptr_p_with_data,
+	                                               NULL,
+	                                               &interfaces_len);
+	for (i = 0; i < interfaces_len; i++) {
+		InterfaceConfig *ic = g_hash_table_lookup (interfaces, GINT_TO_POINTER (interfaces_keys[i]));
 
 		prepare_one_interface (self, ic);
-		g_list_free (ic->configs);
 	}
 
-	g_array_free (interfaces, TRUE);
-
 	send_updates (self);
 
 	return TRUE;
@@ -363,16 +392,15 @@ static void
 nm_dns_systemd_resolved_init (NMDnsSystemdResolved *self)
 {
 	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
-	NMBusManager *dbus_mgr;
+	NMDBusManager *dbus_mgr;
 	GDBusConnection *connection;
 
-	g_queue_init (&priv->dns_updates);
-	g_queue_init (&priv->domain_updates);
+	c_list_init (&priv->request_queue_lst_head);
 
-	dbus_mgr = nm_bus_manager_get ();
+	dbus_mgr = nm_dbus_manager_get ();
 	g_return_if_fail (dbus_mgr);
 
-	connection = nm_bus_manager_get_connection (dbus_mgr);
+	connection = nm_dbus_manager_get_connection (dbus_mgr);
 	g_return_if_fail (connection);
 
 	priv->init_cancellable = g_cancellable_new ();
@@ -404,6 +432,7 @@ dispose (GObject *object)
 	g_clear_object (&priv->resolve);
 	nm_clear_g_cancellable (&priv->init_cancellable);
 	nm_clear_g_cancellable (&priv->update_cancellable);
+	nm_clear_g_cancellable (&priv->mdns_cancellable);
 
 	G_OBJECT_CLASS (nm_dns_systemd_resolved_parent_class)->dispose (object);
 }
diff --git a/src/dns/nm-dns-unbound.c b/src/dns/nm-dns-unbound.c
index 0b80055f..e06128aa 100644
--- a/src/dns/nm-dns-unbound.c
+++ b/src/dns/nm-dns-unbound.c
@@ -39,8 +39,8 @@ G_DEFINE_TYPE (NMDnsUnbound, nm_dns_unbound, NM_TYPE_DNS_PLUGIN)
 
 static gboolean
 update (NMDnsPlugin *plugin,
-        const GPtrArray *configs,
         const NMGlobalDnsConfig *global_config,
+        const CList *ip_config_lst_head,
         const char *hostname)
 {
 	char *argv[] = { DNSSEC_TRIGGER_SCRIPT, "--async", "--update", NULL };