about summary refs log tree commit diff
path: root/src/dns-manager/nm-dns-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dns-manager/nm-dns-manager.c')
-rw-r--r--src/dns-manager/nm-dns-manager.c325
1 files changed, 171 insertions, 154 deletions
diff --git a/src/dns-manager/nm-dns-manager.c b/src/dns-manager/nm-dns-manager.c
index fb426637..466aa9a8 100644
--- a/src/dns-manager/nm-dns-manager.c
+++ b/src/dns-manager/nm-dns-manager.c
@@ -23,17 +23,16 @@
 
 #include "config.h"
 
-#include <limits.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
 #include <errno.h>
-#include <arpa/inet.h>
-#include <sys/types.h>
-#include <sys/wait.h> 
+#include <fcntl.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
 #include <unistd.h>
-#include <glib.h>
 
+#include <linux/fs.h>
+
+#include <glib.h>
 #include <glib/gi18n.h>
 
 #include "nm-dns-manager.h"
@@ -42,15 +41,27 @@
 #include "nm-logging.h"
 #include "NetworkManagerUtils.h"
 #include "nm-posix-signals.h"
+#include "nm-config.h"
 
 #include "nm-dns-plugin.h"
 #include "nm-dns-dnsmasq.h"
+#include "nm-dns-unbound.h"
+
+#if HAVE_LIBSOUP
+#include <libsoup/soup.h>
 
-#ifndef RESOLV_CONF
-#define RESOLV_CONF "/etc/resolv.conf"
+#ifdef SOUP_CHECK_VERSION
+#if SOUP_CHECK_VERSION (2, 40, 0)
+#define DOMAIN_IS_VALID(domain) (*(domain) && !soup_tld_domain_is_public_suffix (domain))
+#endif
+#endif
 #endif
 
-G_DEFINE_TYPE(NMDnsManager, nm_dns_manager, G_TYPE_OBJECT)
+#ifndef DOMAIN_IS_VALID
+#define DOMAIN_IS_VALID(domain) (*(domain))
+#endif
+
+G_DEFINE_TYPE (NMDnsManager, nm_dns_manager, G_TYPE_OBJECT)
 
 #define NM_DNS_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
                                        NM_TYPE_DNS_MANAGER, \
@@ -59,8 +70,6 @@ G_DEFINE_TYPE(NMDnsManager, nm_dns_manager, G_TYPE_OBJECT)
 #define HASH_LEN 20
 
 typedef struct {
-	gboolean disposed;
-
 	NMIP4Config *ip4_vpn_config;
 	NMIP4Config *ip4_device_config;
 	NMIP6Config *ip6_vpn_config;
@@ -72,7 +81,8 @@ typedef struct {
 	guint8 hash[HASH_LEN];  /* SHA1 hash of current DNS config */
 	guint8 prev_hash[HASH_LEN];  /* Hash when begin_updates() was called */
 
-	GSList *plugins;
+	NMDnsManagerResolvConfMode resolv_conf_mode;
+	NMDnsPlugin *plugin;
 
 	gboolean dns_touched;
 } NMDnsManagerPrivate;
@@ -88,7 +98,6 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
 	GPtrArray *nameservers;
-	const char *domain;
 	GPtrArray *searches;
 	const char *nis_domain;
 	GPtrArray *nis_servers;
@@ -121,10 +130,10 @@ merge_one_ip4_config (NMResolvConfData *rc, NMIP4Config *src)
 
 	num = nm_ip4_config_get_num_nameservers (src);
 	for (i = 0; i < num; i++) {
-		struct in_addr addr;
+		guint32 addr;
 		char buf[INET_ADDRSTRLEN];
 
-		addr.s_addr = nm_ip4_config_get_nameserver (src, i);
+		addr = nm_ip4_config_get_nameserver (src, i);
 		if (inet_ntop (AF_INET, &addr, buf, INET_ADDRSTRLEN) > 0)
 			add_string_item (rc->nameservers, buf);
 	}
@@ -134,22 +143,28 @@ merge_one_ip4_config (NMResolvConfData *rc, NMIP4Config *src)
 		const char *domain;
 
 		domain = nm_ip4_config_get_domain (src, i);
-		if (!rc->domain)
-			rc->domain = domain;
+		if (!DOMAIN_IS_VALID (domain))
+			continue;
 		add_string_item (rc->searches, domain);
 	}
 
 	num = nm_ip4_config_get_num_searches (src);
-	for (i = 0; i < num; i++)
-		add_string_item (rc->searches, nm_ip4_config_get_search (src, i));
+	for (i = 0; i < num; i++) {
+		const char *search;
+
+		search = nm_ip4_config_get_search (src, i);
+		if (!DOMAIN_IS_VALID (search))
+			continue;
+		add_string_item (rc->searches, search);
+	}
 
 	/* NIS stuff */
 	num = nm_ip4_config_get_num_nis_servers (src);
 	for (i = 0; i < num; i++) {
-		struct in_addr addr;
+		guint32 addr;
 		char buf[INET_ADDRSTRLEN];
 
-		addr.s_addr = nm_ip4_config_get_nis_server (src, i);
+		addr = nm_ip4_config_get_nis_server (src, i);
 		if (inet_ntop (AF_INET, &addr, buf, INET_ADDRSTRLEN) > 0)
 			add_string_item (rc->nis_servers, buf);
 	}
@@ -168,7 +183,6 @@ merge_one_ip6_config (NMResolvConfData *rc, NMIP6Config *src)
 	const char *iface;
 
 	iface = g_object_get_data (G_OBJECT (src), IP_CONFIG_IFACE_TAG);
-	g_assert (iface);
 
 	num = nm_ip6_config_get_num_nameservers (src);
 	for (i = 0; i < num; i++) {
@@ -184,7 +198,7 @@ merge_one_ip6_config (NMResolvConfData *rc, NMIP6Config *src)
 				add_string_item (rc->nameservers, buf);
 		} else {
 			if (inet_ntop (AF_INET6, addr, buf, INET6_ADDRSTRLEN) > 0) {
-				if (IN6_IS_ADDR_LINKLOCAL (addr) && strchr (buf, '%') == NULL) {
+				if (iface && IN6_IS_ADDR_LINKLOCAL (addr)) {
 					tmp = g_strdup_printf ("%s%%%s", buf, iface);
 					add_string_item (rc->nameservers, tmp);
 					g_free (tmp);
@@ -199,14 +213,20 @@ merge_one_ip6_config (NMResolvConfData *rc, NMIP6Config *src)
 		const char *domain;
 
 		domain = nm_ip6_config_get_domain (src, i);
-		if (!rc->domain)
-			rc->domain = domain;
+		if (!DOMAIN_IS_VALID (domain))
+			continue;
 		add_string_item (rc->searches, domain);
 	}
 
 	num = nm_ip6_config_get_num_searches (src);
-	for (i = 0; i < num; i++)
-		add_string_item (rc->searches, nm_ip6_config_get_search (src, i));
+	for (i = 0; i < num; i++) {
+		const char *search;
+
+		search = nm_ip6_config_get_search (src, i);
+		if (!DOMAIN_IS_VALID (search))
+			continue;
+		add_string_item (rc->searches, search);
+	}
 }
 
 
@@ -264,8 +284,7 @@ write_to_netconfig (gint fd, const char *key, const char *value)
 }
 
 static gboolean
-dispatch_netconfig (const char *domain,
-                    char **searches,
+dispatch_netconfig (char **searches,
                     char **nameservers,
                     const char *nis_domain,
                     char **nis_servers,
@@ -288,12 +307,6 @@ dispatch_netconfig (const char *domain,
 	if (searches) {
 		str = g_strjoinv (" ", searches);
 
-		if (domain) {
-			tmp = g_strconcat (domain, " ", str, NULL);
-			g_free (str);
-			str = tmp;
-		}
-
 		write_to_netconfig (fd, "DNSSEARCH", str);
 		g_free (str);
 	}
@@ -333,12 +346,11 @@ dispatch_netconfig (const char *domain,
 
 
 static gboolean
-write_resolv_conf (FILE *f, const char *domain,
+write_resolv_conf (FILE *f,
                    char **searches,
                    char **nameservers,
                    GError **error)
 {
-	char *domain_str = NULL;
 	char *searches_str = NULL;
 	char *nameservers_str = NULL;
 	int i;
@@ -349,14 +361,11 @@ write_resolv_conf (FILE *f, const char *domain,
 		g_set_error (error,
 		             NM_DNS_MANAGER_ERROR,
 		             NM_DNS_MANAGER_ERROR_SYSTEM,
-		             "Could not write " RESOLV_CONF ": %s\n",
+		             "Could not write " _PATH_RESCONF ": %s\n",
 		             g_strerror (errno));
 		return FALSE;
 	}
 
-	if (domain)
-		domain_str = g_strconcat ("domain ", domain, "\n", NULL);
-
 	if (searches) {
 		char *tmp_str;
 
@@ -387,13 +396,11 @@ write_resolv_conf (FILE *f, const char *domain,
 
 	nameservers_str = g_string_free (str, FALSE);
 
-	if (fprintf (f, "%s%s%s",
-	             domain_str ? domain_str : "",
+	if (fprintf (f, "%s%s",
 	             searches_str ? searches_str : "",
 	             strlen (nameservers_str) ? nameservers_str : "") != -1)
 		retval = TRUE;
 
-	g_free (domain_str);
 	g_free (searches_str);
 	g_free (nameservers_str);
 
@@ -402,8 +409,7 @@ write_resolv_conf (FILE *f, const char *domain,
 
 #ifdef RESOLVCONF_PATH
 static gboolean
-dispatch_resolvconf (const char *domain,
-                     char **searches,
+dispatch_resolvconf (char **searches,
                      char **nameservers,
                      GError **error)
 {
@@ -414,7 +420,7 @@ dispatch_resolvconf (const char *domain,
 	if (! g_file_test (RESOLVCONF_PATH, G_FILE_TEST_IS_EXECUTABLE))
 		return FALSE;
 
-	if (domain || searches || nameservers) {
+	if (searches || nameservers) {
 		cmd = g_strconcat (RESOLVCONF_PATH, " -a ", "NetworkManager", NULL);
 		nm_log_info (LOGD_DNS, "Writing DNS information to %s", RESOLVCONF_PATH);
 		if ((f = popen (cmd, "w")) == NULL)
@@ -425,7 +431,7 @@ dispatch_resolvconf (const char *domain,
 			             RESOLVCONF_PATH,
 			             g_strerror (errno));
 		else {
-			retval = write_resolv_conf (f, domain, searches, nameservers, error);
+			retval = write_resolv_conf (f, searches, nameservers, error);
 			retval &= (pclose (f) == 0);
 		}
 	} else {
@@ -442,8 +448,7 @@ dispatch_resolvconf (const char *domain,
 #endif
 
 static gboolean
-update_resolv_conf (const char *domain,
-                    char **searches,
+update_resolv_conf (char **searches,
                     char **nameservers,
                     GError **error)
 {
@@ -457,9 +462,9 @@ update_resolv_conf (const char *domain,
 	g_return_val_if_fail (error != NULL, FALSE);
 
 	/* Find the real path of resolv.conf; it could be a symlink to something */
-	resolv_conf_realpath = realpath (RESOLV_CONF, NULL);
+	resolv_conf_realpath = realpath (_PATH_RESCONF, NULL);
 	if (!resolv_conf_realpath)
-		resolv_conf_realpath = strdup (RESOLV_CONF);
+		resolv_conf_realpath = strdup (_PATH_RESCONF);
 
 	/* Build up the real path for the temp resolv.conf that we're about to
 	 * write out.
@@ -474,24 +479,24 @@ update_resolv_conf (const char *domain,
 	if ((f = fopen (tmp_resolv_conf_realpath, "w")) == NULL) {
 		do_rename = 0;
 		old_errno = errno;
-		if ((f = fopen (RESOLV_CONF, "w")) == NULL) {
+		if ((f = fopen (_PATH_RESCONF, "w")) == NULL) {
 			g_set_error (error,
 			             NM_DNS_MANAGER_ERROR,
 			             NM_DNS_MANAGER_ERROR_SYSTEM,
 			             "Could not open %s: %s\nCould not open %s: %s\n",
 			             tmp_resolv_conf_realpath,
 			             g_strerror (old_errno),
-			             RESOLV_CONF,
+			             _PATH_RESCONF,
 			             g_strerror (errno));
 			goto out;
 		}
 		/* Update tmp_resolv_conf_realpath so the error message on fclose()
 		 * failure will be correct.
 		 */
-		strcpy (tmp_resolv_conf_realpath, RESOLV_CONF);
+		strcpy (tmp_resolv_conf_realpath, _PATH_RESCONF);
 	}
 
-	write_resolv_conf (f, domain, searches, nameservers, error);
+	write_resolv_conf (f, searches, nameservers, error);
 
 	if (fclose (f) < 0) {
 		if (*error == NULL) {
@@ -515,7 +520,7 @@ update_resolv_conf (const char *domain,
 			g_set_error (error,
 			             NM_DNS_MANAGER_ERROR,
 			             NM_DNS_MANAGER_ERROR_SYSTEM,
-			             "Could not replace " RESOLV_CONF ": %s\n",
+			             "Could not replace " _PATH_RESCONF ": %s\n",
 			             g_strerror (errno));
 		}
 	}
@@ -573,7 +578,6 @@ update_dns (NMDnsManager *self,
 	NMDnsManagerPrivate *priv;
 	NMResolvConfData rc;
 	GSList *iter, *vpn_configs = NULL, *dev_configs = NULL, *other_configs = NULL;
-	const char *domain = NULL;
 	const char *nis_domain = NULL;
 	char **searches = NULL;
 	char **nameservers = NULL;
@@ -586,6 +590,9 @@ update_dns (NMDnsManager *self,
 
 	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 
+	if (priv->resolv_conf_mode == NM_DNS_MANAGER_RESOLV_CONF_UNMANAGED)
+		return TRUE;
+
 	priv->dns_touched = TRUE;
 
 	nm_log_dbg (LOGD_DNS, "updating resolv.conf");
@@ -594,7 +601,6 @@ update_dns (NMDnsManager *self,
 	compute_hash (self, priv->hash);
 
 	rc.nameservers = g_ptr_array_new ();
-	rc.domain = NULL;
 	rc.searches = g_ptr_array_new ();
 	rc.nis_domain = NULL;
 	rc.nis_servers = g_ptr_array_new ();
@@ -628,22 +634,25 @@ update_dns (NMDnsManager *self,
 			g_assert_not_reached ();
 	}
 
-	/* Add the current domain name (from the hostname) to the searches list;
-	 * see rh #600407.  The bug report is that when the hostname is set to
-	 * something like 'dcbw.foobar.com' (ie an FQDN) that pinging 'dcbw' doesn't
-	 * work because the resolver doesn't have anything to append to 'dcbw' when
-	 * looking it up.
+	/* If the hostname is a FQDN ("dcbw.example.com"), then add the domain part of it
+	 * ("example.com") to the searches list, to ensure that we can still resolve its
+	 * non-FQ form ("dcbw") too. (Also, if there are no other search domains specified,
+	 * this makes a good default.) However, if the hostname is the top level of a domain
+	 * (eg, "example.com"), then use the hostname itself as the search (since the user is
+	 * unlikely to want "com" as a search domain).
 	 */
 	if (priv->hostname) {
-		const char *hostsearch = strchr (priv->hostname, '.');
-
-		/* +1 to get rid of the dot */
-		if (hostsearch && strlen (hostsearch + 1))
-			add_string_item (rc.searches, hostsearch + 1);
+		const char *hostdomain = strchr (priv->hostname, '.');
+
+		if (hostdomain) {
+			hostdomain++;
+			if (DOMAIN_IS_VALID (hostdomain))
+				add_string_item (rc.searches, hostdomain);
+			else if (DOMAIN_IS_VALID (priv->hostname))
+				add_string_item (rc.searches, priv->hostname);
+		}
 	}
 
-	domain = rc.domain;
-
 	/* Per 'man resolv.conf', the search list is limited to 6 domains
 	 * totalling 256 characters.
 	 */
@@ -697,15 +706,15 @@ update_dns (NMDnsManager *self,
 	}
 
 	/* Let any plugins do their thing first */
-	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
-		NMDnsPlugin *plugin = NM_DNS_PLUGIN (iter->data);
+	if (priv->plugin) {
+		NMDnsPlugin *plugin = priv->plugin;
 		const char *plugin_name = nm_dns_plugin_get_name (plugin);
 
 		if (nm_dns_plugin_is_caching (plugin)) {
 			if (no_caching) {
 				nm_log_dbg (LOGD_DNS, "DNS: plugin %s ignored (caching disabled)",
 				            plugin_name);
-				continue;
+				goto skip;
 			}
 			caching = TRUE;
 		}
@@ -723,7 +732,11 @@ update_dns (NMDnsManager *self,
 			 */
 			caching = FALSE;
 		}
+
+	skip:
+		;
 	}
+
 	g_slist_free (vpn_configs);
 	g_slist_free (dev_configs);
 	g_slist_free (other_configs);
@@ -740,18 +753,18 @@ update_dns (NMDnsManager *self,
 	}
 
 #ifdef RESOLVCONF_PATH
-	success = dispatch_resolvconf (domain, searches, nameservers, error);
+	success = dispatch_resolvconf (searches, nameservers, error);
 #endif
 
 #ifdef NETCONFIG_PATH
 	if (success == FALSE) {
-		success = dispatch_netconfig (domain, searches, nameservers,
+		success = dispatch_netconfig (searches, nameservers,
 		                              nis_domain, nis_servers, error);
 	}
 #endif
 
 	if (success == FALSE)
-		success = update_resolv_conf (domain, searches, nameservers, error);
+		success = update_resolv_conf (searches, nameservers, error);
 
 	/* signal that resolv.conf was changed */
 	if (success)
@@ -796,7 +809,6 @@ nm_dns_manager_add_ip4_config (NMDnsManager *mgr,
 	GError *error = NULL;
 
 	g_return_val_if_fail (mgr != NULL, FALSE);
-	g_return_val_if_fail (iface != NULL, FALSE);
 	g_return_val_if_fail (config != NULL, FALSE);
 
 	priv = NM_DNS_MANAGER_GET_PRIVATE (mgr);
@@ -874,7 +886,6 @@ nm_dns_manager_add_ip6_config (NMDnsManager *mgr,
 	GError *error = NULL;
 
 	g_return_val_if_fail (mgr != NULL, FALSE);
-	g_return_val_if_fail (iface != NULL, FALSE);
 	g_return_val_if_fail (config != NULL, FALSE);
 
 	priv = NM_DNS_MANAGER_GET_PRIVATE (mgr);
@@ -943,8 +954,17 @@ nm_dns_manager_remove_ip6_config (NMDnsManager *mgr, NMIP6Config *config)
 }
 
 void
+nm_dns_manager_set_initial_hostname (NMDnsManager *mgr,
+                                     const char *hostname)
+{
+	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (mgr);
+
+	priv->hostname = g_strdup (hostname);
+}
+
+void
 nm_dns_manager_set_hostname (NMDnsManager *mgr,
-                               const char *hostname)
+                             const char *hostname)
 {
 	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (mgr);
 	GError *error = NULL;
@@ -966,10 +986,6 @@ nm_dns_manager_set_hostname (NMDnsManager *mgr,
 	g_free (priv->hostname);
 	priv->hostname = g_strdup (filtered);
 
-	/* Passing the last interface here is completely bogus, but SUSE's netconfig
-	 * wants one.  But hostname changes are system-wide and *not* tied to a
-	 * specific interface, so netconfig can't really handle this.  Fake it.
-	 */
 	if (!priv->updates_queue && !update_dns (mgr, FALSE, &error)) {
 		nm_log_warn (LOGD_DNS, "could not commit DNS changes: (%d) %s",
 		             error ? error->code : -1,
@@ -978,6 +994,12 @@ nm_dns_manager_set_hostname (NMDnsManager *mgr,
 	}
 }
 
+NMDnsManagerResolvConfMode
+nm_dns_manager_get_resolv_conf_mode (NMDnsManager *mgr)
+{
+	return NM_DNS_MANAGER_GET_PRIVATE (mgr)->resolv_conf_mode;
+}
+
 void
 nm_dns_manager_begin_updates (NMDnsManager *mgr, const char *func)
 {
@@ -1030,62 +1052,17 @@ nm_dns_manager_end_updates (NMDnsManager *mgr, const char *func)
 	memset (priv->prev_hash, 0, sizeof (priv->prev_hash));
 }
 
-static void
-load_plugins (NMDnsManager *self, const char **plugins)
-{
-	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-	NMDnsPlugin *plugin;
-	const char **iter;
-	gboolean have_caching = FALSE;
-
-	if (plugins && *plugins) {
-		/* Create each configured plugin */
-		for (iter = plugins; iter && *iter; iter++) {
-			if (!strcasecmp (*iter, "dnsmasq"))
-				plugin = NM_DNS_PLUGIN (nm_dns_dnsmasq_new ());
-			else {
-				nm_log_warn (LOGD_DNS, "Unknown DNS plugin '%s'", *iter);\
-				continue;
-			}
-			g_assert (plugin);
-
-			/* Only one caching DNS plugin is allowed */
-			if (nm_dns_plugin_is_caching (plugin)) {
-				if (have_caching) {
-					nm_log_warn (LOGD_DNS,
-					             "Ignoring plugin %s; only one caching DNS "
-					             "plugin is allowed.",
-					             *iter);
-					g_object_unref (plugin);
-					continue;
-				}
-				have_caching = TRUE;
-			}
-
-			nm_log_info (LOGD_DNS, "DNS: loaded plugin %s", nm_dns_plugin_get_name (plugin));
-			priv->plugins = g_slist_append (priv->plugins, plugin);
-			g_signal_connect (plugin, NM_DNS_PLUGIN_FAILED,
-			                  G_CALLBACK (plugin_failed),
-			                  self);
-		}
-	} else {
-		/* Create default plugins */
-	}
-}
-
 /******************************************************************/
 
 NMDnsManager *
-nm_dns_manager_get (const char **plugins)
+nm_dns_manager_get (void)
 {
 	static NMDnsManager * singleton = NULL;
 
 	if (!singleton) {
 		singleton = NM_DNS_MANAGER (g_object_new (NM_TYPE_DNS_MANAGER, NULL));
 		g_assert (singleton);
-		load_plugins (singleton, plugins);
-	} else
-		g_object_ref (singleton);
+	}
 
 	return singleton;
 }
@@ -1101,10 +1078,56 @@ nm_dns_manager_error_quark (void)
 }
 
 static void
+init_resolv_conf_mode (NMDnsManager *self)
+{
+	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+	const char *mode;
+	int fd, flags;
+
+	fd = open (_PATH_RESCONF, O_RDONLY);
+	if (fd != -1) {
+		if (ioctl (fd, FS_IOC_GETFLAGS, &flags) == -1)
+			flags = 0;
+		close (fd);
+
+		if (flags & FS_IMMUTABLE_FL) {
+			nm_log_info (LOGD_DNS, "DNS: " _PATH_RESCONF " is immutable; not managing");
+			priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_UNMANAGED;
+			return;
+		}
+	}
+
+	mode = nm_config_get_dns_mode (nm_config_get ());
+	if (!g_strcmp0 (mode, "none")) {
+		priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_UNMANAGED;
+		nm_log_info (LOGD_DNS, "DNS: not managing " _PATH_RESCONF);
+	} else if (!g_strcmp0 (mode, "dnsmasq")) {
+		priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_PROXY;
+		priv->plugin = nm_dns_dnsmasq_new ();
+	} else if (!g_strcmp0 (mode, "unbound")) {
+		priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_PROXY;
+		priv->plugin = nm_dns_unbound_new ();
+	} else {
+		priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_EXPLICIT;
+		if (mode && g_strcmp0 (mode, "default") != 0)
+			nm_log_warn (LOGD_DNS, "Unknown DNS mode '%s'", mode);
+	}
+}
+
+static void
 nm_dns_manager_init (NMDnsManager *self)
 {
+	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+
 	/* Set the initial hash */
 	compute_hash (self, NM_DNS_MANAGER_GET_PRIVATE (self)->hash);
+
+	init_resolv_conf_mode (self);
+
+	if (priv->plugin) {
+		nm_log_info (LOGD_DNS, "DNS: loaded plugin %s", nm_dns_plugin_get_name (priv->plugin));
+		g_signal_connect (priv->plugin, NM_DNS_PLUGIN_FAILED, G_CALLBACK (plugin_failed), self);
+	}
 }
 
 static void
@@ -1114,30 +1137,24 @@ dispose (GObject *object)
 	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 	GError *error = NULL;
 
-	if (priv->disposed == FALSE) {
-		priv->disposed = TRUE;
-
-		g_slist_foreach (priv->plugins, (GFunc) g_object_unref, NULL);
-		g_slist_free (priv->plugins);
-		priv->plugins = NULL;
-
-		/* If we're quitting leave a valid resolv.conf in place, not one
-		 * pointing to 127.0.0.1 if any plugins were active.  Thus update
-		 * DNS after disposing of all plugins.  But if we haven't done any
-		 * DNS updates yet, there's no reason to touch resolv.conf on shutdown.
-		 */
-		if (priv->dns_touched && !update_dns (self, TRUE, &error)) {
-			nm_log_warn (LOGD_DNS, "could not commit DNS changes on shutdown: (%d) %s",
-			             error ? error->code : -1,
-			             error && error->message ? error->message : "(unknown)");
-			g_clear_error (&error);
-		}
+	g_clear_object (&priv->plugin);
 
-		g_slist_foreach (priv->configs, (GFunc) g_object_unref, NULL);
-		g_slist_free (priv->configs);
-		priv->configs = NULL;
+	/* If we're quitting, leave a valid resolv.conf in place, not one
+	 * pointing to 127.0.0.1 if any plugins were active.  Thus update
+	 * DNS after disposing of all plugins.  But if we haven't done any
+	 * DNS updates yet, there's no reason to touch resolv.conf on shutdown.
+	 */
+	if (priv->dns_touched && !update_dns (self, TRUE, &error)) {
+		nm_log_warn (LOGD_DNS, "could not commit DNS changes on shutdown: (%d) %s",
+		             error ? error->code : -1,
+		             error && error->message ? error->message : "(unknown)");
+		g_clear_error (&error);
+		priv->dns_touched = FALSE;
 	}
 
+	g_slist_free_full (priv->configs, g_object_unref);
+	priv->configs = NULL;
+
 	G_OBJECT_CLASS (nm_dns_manager_parent_class)->dispose (object);
 }