about summary refs log tree commit diff
path: root/src/dns
diff options
context:
space:
mode:
Diffstat (limited to 'src/dns')
-rw-r--r--src/dns/nm-dns-dnsmasq.c13
-rw-r--r--src/dns/nm-dns-manager.c314
-rw-r--r--src/dns/nm-dns-manager.h8
3 files changed, 218 insertions, 117 deletions
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/dns/nm-dns-dnsmasq.c
index b48c6b87..b54df730 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/dns/nm-dns-dnsmasq.c
@@ -254,9 +254,16 @@ name_owner_changed (GObject    *object,
 		priv->running = TRUE;
 		send_dnsmasq_update (self);
 	} else {
-		_LOGI ("dnsmasq disappeared");
-		priv->running = FALSE;
-		g_signal_emit_by_name (self, NM_DNS_PLUGIN_FAILED);
+		if (priv->running) {
+			_LOGI ("dnsmasq disappeared");
+			priv->running = FALSE;
+			g_signal_emit_by_name (self, NM_DNS_PLUGIN_FAILED);
+		} else {
+			/* The only reason for which (!priv->running) here
+			 * is that the dnsmasq process quit. We don't care
+			 * of that here, the manager handles child restarts
+			 * by itself. */
+		}
 	}
 }
 
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index 6a59b41c..2a30a540 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -55,7 +55,7 @@
 #include "nm-dns-systemd-resolved.h"
 #include "nm-dns-unbound.h"
 
-#define HASH_LEN 20
+#define HASH_LEN   NM_UTILS_CHECKSUM_LENGTH_SHA1
 
 #ifndef RESOLVCONF_PATH
 #define RESOLVCONF_PATH "/sbin/resolvconf"
@@ -122,6 +122,7 @@ typedef struct {
 
 	NMDnsManagerResolvConfManager rc_manager;
 	char *mode;
+	NMDnsPlugin *sd_resolve_plugin;
 	NMDnsPlugin *plugin;
 
 	NMConfig *config;
@@ -311,37 +312,23 @@ _config_data_free (NMDnsConfigData *data)
 }
 
 static int
-_ip_config_data_cmp (const NMDnsIPConfigData *a, const NMDnsIPConfigData *b)
+_ip_config_lst_cmp (const CList *a_lst,
+                    const CList *b_lst,
+                    const void *user_data)
 {
-	int a_prio, b_prio;
-
-	a_prio = nm_ip_config_get_dns_priority (a->ip_config);
-	b_prio = nm_ip_config_get_dns_priority (b->ip_config);
+	const NMDnsIPConfigData *a = c_list_entry (a_lst, NMDnsIPConfigData, ip_config_lst);
+	const NMDnsIPConfigData *b = c_list_entry (b_lst, NMDnsIPConfigData, ip_config_lst);
 
 	/* Configurations with lower priority value first */
-	if (a_prio < b_prio)
-		return -1;
-	else if (a_prio > b_prio)
-		return 1;
+	NM_CMP_DIRECT (nm_ip_config_get_dns_priority (a->ip_config),
+	               nm_ip_config_get_dns_priority (b->ip_config));
 
-	/* Sort also according to type */
-	if (a->ip_config_type > b->ip_config_type)
-		return -1;
-	else if (a->ip_config_type < b->ip_config_type)
-		return 1;
+	/* Sort according to type (descendingly) */
+	NM_CMP_FIELD (b, a, ip_config_type);
 
 	return 0;
 }
 
-static int
-_ip_config_lst_cmp (const CList *a,
-                    const CList *b,
-                    const void *user_data)
-{
-	return _ip_config_data_cmp (c_list_entry (a, NMDnsIPConfigData, ip_config_lst),
-	                            c_list_entry (b, NMDnsIPConfigData, ip_config_lst));
-}
-
 static CList *
 _ip_config_lst_head (NMDnsManager *self)
 {
@@ -357,6 +344,21 @@ _ip_config_lst_head (NMDnsManager *self)
 
 /*****************************************************************************/
 
+gboolean
+nm_dns_manager_has_systemd_resolved (NMDnsManager *self)
+{
+	NMDnsManagerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DNS_MANAGER (self), FALSE);
+
+	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+
+	return    priv->sd_resolve_plugin
+	       || NM_IS_DNS_SYSTEMD_RESOLVED (priv->plugin);
+}
+
+/*****************************************************************************/
+
 static void
 add_string_item (GPtrArray *array, const char *str, gboolean dup)
 {
@@ -582,53 +584,85 @@ again:
 }
 
 static char *
-create_resolv_conf (char **searches,
-                    char **nameservers,
-                    char **options)
+create_resolv_conf (const char *const*searches,
+                    const char *const*nameservers,
+                    const char *const*options)
 {
-	gs_free char *searches_str = NULL;
-	gs_free char *nameservers_str = NULL;
-	gs_free char *options_str = NULL;
-	char *tmp_str;
 	GString *str;
-	int i;
+	gsize i;
 
-	if (searches) {
-		tmp_str = g_strjoinv (" ", searches);
-		searches_str = g_strconcat ("search ", tmp_str, "\n", NULL);
-		g_free (tmp_str);
-	}
+	str = g_string_new_len (NULL, 245);
 
-	if (options) {
-		tmp_str = g_strjoinv (" ", options);
-		options_str = g_strconcat ("options ", tmp_str, "\n", NULL);
-		g_free (tmp_str);
-	}
+	g_string_append (str, "# Generated by NetworkManager\n");
 
-	if (nameservers) {
-		int num = g_strv_length (nameservers);
+	if (searches && searches[0]) {
+		gsize search_base_idx;
 
-		str = g_string_new ("");
-		for (i = 0; i < num; i++) {
-			if (i == 3) {
-				g_string_append (str, "# ");
-				g_string_append (str, "NOTE: the libc resolver may not support more than 3 nameservers.");
-				g_string_append (str, "\n# ");
-				g_string_append (str, "The nameservers listed below may not be recognized.");
-				g_string_append_c (str, '\n');
+		g_string_append (str, "search");
+		search_base_idx = str->len;
+
+		for (i = 0; searches[i]; i++) {
+			const char *s = searches[i];
+			gsize l = strlen (s);
+
+			if (   l == 0
+			    || NM_STRCHAR_ANY (s, ch, NM_IN_SET (ch, ' ', '\t', '\n'))) {
+				/* there should be no such characters in the search entry. Also,
+				 * because glibc parser would treat them as line/word separator.
+				 *
+				 * Skip the value silently. */
+				continue;
+			}
+
+			if (search_base_idx > 0) {
+				if (str->len - search_base_idx + 1 + l > 254) {
+					/* this entry crosses the 256 character boundery. Older glibc versions
+					 * would truncate the entry at this point.
+					 *
+					 * Fill the line with spaces to cross the 256 char boundary and continue
+					 * afterwards. This way, the truncation happens between two search entries. */
+					while (str->len - search_base_idx < 257)
+						g_string_append_c (str, ' ');
+					search_base_idx = 0;
+				}
 			}
 
+			g_string_append_c (str, ' ');
+			g_string_append_len (str, s, l);
+		}
+		g_string_append_c (str, '\n');
+	}
+
+	if (nameservers && nameservers[0]) {
+		for (i = 0; nameservers[i]; i++) {
+			if (i == 3) {
+				g_string_append (str, "# NOTE: the libc resolver may not support more than 3 nameservers.\n");
+				g_string_append (str, "# The nameservers listed below may not be recognized.\n");
+			}
 			g_string_append (str, "nameserver ");
 			g_string_append (str, nameservers[i]);
 			g_string_append_c (str, '\n');
 		}
-		nameservers_str = g_string_free (str, FALSE);
 	}
 
-	return g_strdup_printf ("# Generated by NetworkManager\n%s%s%s",
-	                        searches_str ?: "",
-	                        nameservers_str ?: "",
-	                        options_str ?: "");
+	if (options && options[0]) {
+		g_string_append (str, "options");
+		for (i = 0; options[i]; i++) {
+			g_string_append_c (str, ' ');
+			g_string_append (str, options[i]);
+		}
+		g_string_append_c (str, '\n');
+	}
+
+	return g_string_free (str, FALSE);
+}
+
+char *
+nmtst_dns_create_resolv_conf (const char *const*searches,
+                              const char *const*nameservers,
+                              const char *const*options)
+{
+	return create_resolv_conf (searches, nameservers, options);
 }
 
 static gboolean
@@ -654,9 +688,9 @@ write_resolv_conf_contents (FILE *f,
 
 static gboolean
 write_resolv_conf (FILE *f,
-                   char **searches,
-                   char **nameservers,
-                   char **options,
+                   const char *const*searches,
+                   const char *const*nameservers,
+                   const char *const*options,
                    GError **error)
 {
 	gs_free char *content = NULL;
@@ -718,7 +752,11 @@ dispatch_resolvconf (NMDnsManager *self,
 		return SR_ERROR;
 	}
 
-	success = write_resolv_conf (f, searches, nameservers, options, error);
+	success = write_resolv_conf (f,
+	                             NM_CAST_STRV_CC (searches),
+	                             NM_CAST_STRV_CC (nameservers),
+	                             NM_CAST_STRV_CC (options),
+	                             error);
 	err = pclose (f);
 	if (err < 0) {
 		errnosv = errno;
@@ -751,15 +789,42 @@ _read_link_cached (const char *path, gboolean *is_cached, char **cached)
 	return (*cached = g_file_read_link (path, NULL));
 }
 
-#define MY_RESOLV_CONF NMRUNDIR "/resolv.conf"
-#define MY_RESOLV_CONF_TMP MY_RESOLV_CONF ".tmp"
-#define RESOLV_CONF_TMP "/etc/.resolv.conf.NetworkManager"
+#define MY_RESOLV_CONF             NMRUNDIR"/resolv.conf"
+#define MY_RESOLV_CONF_TMP         MY_RESOLV_CONF".tmp"
+#define RESOLV_CONF_TMP            "/etc/.resolv.conf.NetworkManager"
+
+#define NO_STUB_RESOLV_CONF        NMRUNDIR "/no-stub-resolv.conf"
+
+static void
+update_resolv_conf_no_stub (NMDnsManager *self,
+                            const char *const*searches,
+                            const char *const*nameservers,
+                            const char *const*options)
+{
+	gs_free char *content = NULL;
+	GError *local = NULL;
+
+	content = create_resolv_conf (searches, nameservers, options);
+
+	if (!g_file_set_contents (NO_STUB_RESOLV_CONF,
+	                          content,
+	                          -1,
+	                          &local)) {
+		_LOGD ("update-resolv-no-stub: failure to write file: %s",
+		       local->message);
+		g_error_free (local);
+		return;
+	}
+
+	_LOGT ("update-resolv-no-stub: '%s' successfully written",
+	       NO_STUB_RESOLV_CONF);
+}
 
 static SpawnResult
 update_resolv_conf (NMDnsManager *self,
-                    char **searches,
-                    char **nameservers,
-                    char **options,
+                    const char *const*searches,
+                    const char *const*nameservers,
+                    const char *const*options,
                     GError **error,
                     NMDnsManagerResolvConfManager rc_manager)
 {
@@ -771,22 +836,6 @@ update_resolv_conf (NMDnsManager *self,
 	gboolean resconf_link_cached = FALSE;
 	gs_free char *resconf_link = NULL;
 
-	/* If we are not managing /etc/resolv.conf and it points to
-	 * MY_RESOLV_CONF, don't write the private DNS configuration to
-	 * MY_RESOLV_CONF otherwise we would overwrite the changes done by
-	 * some external application.
-	 *
-	 * This is the only situation, where we don't try to update our
-	 * internal resolv.conf file. */
-	if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED) {
-		if (nm_streq0 (_read_link_cached (_PATH_RESCONF, &resconf_link_cached, &resconf_link),
-		               MY_RESOLV_CONF)) {
-			_LOGD ("update-resolv-conf: not updating " _PATH_RESCONF
-			       " since it points to " MY_RESOLV_CONF);
-			return SR_SUCCESS;
-		}
-	}
-
 	content = create_resolv_conf (searches, nameservers, options);
 
 	if (   rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE
@@ -958,12 +1007,11 @@ update_resolv_conf (NMDnsManager *self,
 static void
 compute_hash (NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[HASH_LEN])
 {
-	GChecksum *sum;
-	gsize len = HASH_LEN;
+	nm_auto_free_checksum GChecksum *sum = NULL;
 	NMDnsIPConfigData *ip_data;
 
 	sum = g_checksum_new (G_CHECKSUM_SHA1);
-	nm_assert (len == g_checksum_type_get_length (G_CHECKSUM_SHA1));
+	nm_assert (HASH_LEN == g_checksum_type_get_length (G_CHECKSUM_SHA1));
 
 	if (global)
 		nm_global_dns_config_update_checksum (global, sum);
@@ -977,8 +1025,7 @@ compute_hash (NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer
 			nm_ip_config_hash (ip_data->ip_config, sum, TRUE);
 	}
 
-	g_checksum_get_digest (sum, buffer, &len);
-	g_checksum_free (sum);
+	nm_utils_checksum_get_digest_len (sum, buffer, HASH_LEN);
 }
 
 static gboolean
@@ -1066,7 +1113,6 @@ _collect_resolv_conf_data (NMDnsManager *self,
                            const char **out_nis_domain)
 {
 	NMDnsManagerPrivate *priv;
-	guint i, num, len;
 	NMResolvConfData rc = {
 		.nameservers = g_ptr_array_new (),
 		.searches = g_ptr_array_new (),
@@ -1136,17 +1182,6 @@ _collect_resolv_conf_data (NMDnsManager *self,
 		}
 	}
 
-	/* Per 'man resolv.conf', the search list is limited to 6 domains
-	 * totalling 256 characters.
-	 */
-	num = MIN (rc.searches->len, 6u);
-	for (i = 0, len = 0; i < num; i++) {
-		len += strlen (rc.searches->pdata[i]) + 1; /* +1 for spaces */
-		if (len > 256)
-			break;
-	}
-	g_ptr_array_set_size (rc.searches, i);
-
 	*out_searches = _ptrarray_to_strv (rc.searches);
 	*out_options = _ptrarray_to_strv (rc.options);
 	*out_nameservers = _ptrarray_to_strv (rc.nameservers);
@@ -1392,6 +1427,16 @@ update_dns (NMDnsManager *self,
 	                           &searches, &options, &nameservers,
 	                           &nis_servers, &nis_domain);
 
+	if (priv->plugin || priv->sd_resolve_plugin)
+		rebuild_domain_lists (self);
+
+	if (priv->sd_resolve_plugin) {
+		nm_dns_plugin_update (priv->sd_resolve_plugin,
+		                      global_config,
+		                      _ip_config_lst_head (self),
+		                      priv->hostname);
+	}
+
 	/* Let any plugins do their thing first */
 	if (priv->plugin) {
 		NMDnsPlugin *plugin = priv->plugin;
@@ -1407,7 +1452,6 @@ update_dns (NMDnsManager *self,
 		}
 
 		_LOGD ("update-dns: updating plugin %s", plugin_name);
-		rebuild_domain_lists (self);
 		if (!nm_dns_plugin_update (plugin,
 		                           global_config,
 		                           _ip_config_lst_head (self),
@@ -1419,15 +1463,21 @@ update_dns (NMDnsManager *self,
 			 */
 			caching = FALSE;
 		}
-		/* Clear the generated search list as it points to
-		 * strings owned by IP configurations and we can't
-		 * guarantee they stay alive. */
-		clear_domain_lists (self);
 
 	skip:
 		;
 	}
 
+	/* Clear the generated search list as it points to
+	 * strings owned by IP configurations and we can't
+	 * guarantee they stay alive. */
+	clear_domain_lists (self);
+
+	update_resolv_conf_no_stub (self,
+	                            NM_CAST_STRV_CC (searches),
+	                            NM_CAST_STRV_CC (nameservers),
+	                            NM_CAST_STRV_CC (options));
+
 	/* If caching was successful, we only send 127.0.0.1 to /etc/resolv.conf
 	 * to ensure that the glibc resolver doesn't try to round-robin nameservers,
 	 * but only uses the local caching nameserver.
@@ -1449,7 +1499,12 @@ update_dns (NMDnsManager *self,
 		switch (priv->rc_manager) {
 		case NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK:
 		case NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE:
-			result = update_resolv_conf (self, searches, nameservers, options, error, priv->rc_manager);
+			result = update_resolv_conf (self,
+			                             NM_CAST_STRV_CC (searches),
+			                             NM_CAST_STRV_CC (nameservers),
+			                             NM_CAST_STRV_CC (options),
+			                             error,
+			                             priv->rc_manager);
 			resolv_conf_updated = TRUE;
 			/* If we have ended with no nameservers avoid updating again resolv.conf
 			 * on stop, as some external changes may be applied to it in the meanwhile */
@@ -1474,15 +1529,26 @@ update_dns (NMDnsManager *self,
 		if (result == SR_NOTFOUND) {
 			_LOGD ("update-dns: program not available, writing to resolv.conf");
 			g_clear_error (error);
-			result = update_resolv_conf (self, searches, nameservers, options, error, NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK);
+			result = update_resolv_conf (self,
+			                             NM_CAST_STRV_CC (searches),
+			                             NM_CAST_STRV_CC (nameservers),
+			                             NM_CAST_STRV_CC (options),
+			                             error,
+			                             NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK);
 			resolv_conf_updated = TRUE;
 		}
 	}
 
 	/* Unless we've already done it, update private resolv.conf in NMRUNDIR
 	   ignoring any errors */
-	if (!resolv_conf_updated)
-		update_resolv_conf (self, searches, nameservers, options, NULL, NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED);
+	if (!resolv_conf_updated) {
+		update_resolv_conf (self,
+		                    NM_CAST_STRV_CC (searches),
+		                    NM_CAST_STRV_CC (nameservers),
+		                    NM_CAST_STRV_CC (options),
+		                    NULL,
+		                    NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED);
+	}
 
 	/* signal that resolv.conf was changed */
 	if (update && result == SR_SUCCESS)
@@ -1542,6 +1608,7 @@ plugin_child_quit (NMDnsPlugin *plugin, int exit_status, gpointer user_data)
 	} else {
 		priv->plugin_ratelimit.num_restarts++;
 		if (priv->plugin_ratelimit.num_restarts > PLUGIN_RATELIMIT_BURST) {
+			plugin_failed (plugin, self);
 			_LOGW ("plugin %s child respawning too fast, delaying update for %u seconds",
 			        nm_dns_plugin_get_name (plugin), PLUGIN_RATELIMIT_DELAY);
 			priv->plugin_ratelimit.timer = g_timeout_add_seconds (PLUGIN_RATELIMIT_DELAY,
@@ -1924,9 +1991,13 @@ init_resolv_conf_mode (NMDnsManager *self, gboolean force_reload_plugin)
 	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 	NMDnsManagerResolvConfManager rc_manager;
 	const char *mode;
-	gboolean param_changed = FALSE, plugin_changed = FALSE;
+	gboolean systemd_resolved;
+	gboolean param_changed = FALSE;
+	gboolean plugin_changed = FALSE;
+	gboolean systemd_resolved_changed = FALSE;
 
 	mode = nm_config_data_get_dns_mode (nm_config_get_data (priv->config));
+	systemd_resolved = nm_config_data_get_systemd_resolved (nm_config_get_data (priv->config));
 
 	if (nm_streq0 (mode, "none"))
 		rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED;
@@ -1972,6 +2043,7 @@ again:
 			plugin_changed = TRUE;
 		}
 		mode = "systemd-resolved";
+		systemd_resolved = FALSE;
 	} else if (nm_streq0 (mode, "dnsmasq")) {
 		if (force_reload_plugin || !NM_IS_DNS_DNSMASQ (priv->plugin)) {
 			_clear_plugin (self);
@@ -1994,7 +2066,18 @@ again:
 			plugin_changed = TRUE;
 	}
 
-	if (plugin_changed && priv->plugin) {
+	/* The systemd-resolved plugin is special. We typically always want to keep
+	 * systemd-resolved up to date even if the configured plugin is different. */
+	if (systemd_resolved) {
+		if (!priv->sd_resolve_plugin) {
+			priv->sd_resolve_plugin = nm_dns_systemd_resolved_new ();
+			systemd_resolved_changed = TRUE;
+		}
+	} else if (nm_clear_g_object (&priv->sd_resolve_plugin))
+		systemd_resolved_changed = TRUE;
+
+	if (   plugin_changed
+	    && priv->plugin) {
 		g_signal_connect (priv->plugin, NM_DNS_PLUGIN_FAILED, G_CALLBACK (plugin_failed), self);
 		g_signal_connect (priv->plugin, NM_DNS_PLUGIN_CHILD_QUIT, G_CALLBACK (plugin_child_quit), self);
 	}
@@ -2014,9 +2097,11 @@ again:
 		_notify (self, PROP_RC_MANAGER);
 	}
 
-	if (param_changed || plugin_changed) {
-		_LOGI ("init: dns=%s, rc-manager=%s%s%s%s",
-		       mode, _rc_manager_to_string (rc_manager),
+	if (param_changed || plugin_changed || systemd_resolved_changed) {
+		_LOGI ("init: dns=%s%s rc-manager=%s%s%s%s",
+		       mode,
+		       (systemd_resolved ? ",systemd-resolved" : ""),
+		       _rc_manager_to_string (rc_manager),
 		       NM_PRINT_FMT_QUOTED (priv->plugin, ", plugin=",
 		                            nm_dns_plugin_get_name (priv->plugin), "", ""));
 	}
@@ -2277,6 +2362,7 @@ dispose (GObject *object)
 	if (priv->config)
 		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
 
+	g_clear_object (&priv->sd_resolve_plugin);
 	_clear_plugin (self);
 
 	priv->best_ip_config_4 = NULL;
diff --git a/src/dns/nm-dns-manager.h b/src/dns/nm-dns-manager.h
index ed1974a5..7f6ed3ed 100644
--- a/src/dns/nm-dns-manager.h
+++ b/src/dns/nm-dns-manager.h
@@ -129,4 +129,12 @@ typedef enum {
 
 void nm_dns_manager_stop (NMDnsManager *self);
 
+gboolean nm_dns_manager_has_systemd_resolved (NMDnsManager *self);
+
+/*****************************************************************************/
+
+char *nmtst_dns_create_resolv_conf (const char *const*searches,
+                                    const char *const*nameservers,
+                                    const char *const*options);
+
 #endif /* __NETWORKMANAGER_DNS_MANAGER_H__ */