summary refs log tree commit diff
path: root/src/dns-manager/nm-dns-dnsmasq.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dns-manager/nm-dns-dnsmasq.c')
-rw-r--r--src/dns-manager/nm-dns-dnsmasq.c286
1 files changed, 199 insertions, 87 deletions
diff --git a/src/dns-manager/nm-dns-dnsmasq.c b/src/dns-manager/nm-dns-dnsmasq.c
index 4cee1608..69b26068 100644
--- a/src/dns-manager/nm-dns-dnsmasq.c
+++ b/src/dns-manager/nm-dns-dnsmasq.c
@@ -26,12 +26,14 @@
 #include <sys/wait.h>
 #include <arpa/inet.h>
 #include <sys/stat.h>
+#include <linux/if.h>
 
 #include "nm-dns-dnsmasq.h"
+#include "nm-core-internal.h"
+#include "nm-platform.h"
 #include "nm-utils.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
-#include "nm-dns-utils.h"
 #include "nm-bus-manager.h"
 #include "NetworkManagerUtils.h"
 
@@ -48,6 +50,7 @@ G_DEFINE_TYPE (NMDnsDnsmasq, nm_dns_dnsmasq, NM_TYPE_DNS_PLUGIN)
 typedef struct {
 	GDBusProxy *dnsmasq;
 	GCancellable *dnsmasq_cancellable;
+	GCancellable *update_cancellable;
 	gboolean running;
 
 	GVariant *set_server_ex_args;
@@ -68,6 +71,70 @@ typedef struct {
 
 /*****************************************************************************/
 
+static char **
+get_ip4_rdns_domains (NMIP4Config *ip4)
+{
+	char **strv;
+	GPtrArray *domains = NULL;
+	int i;
+
+	g_return_val_if_fail (ip4 != NULL, NULL);
+
+	domains = g_ptr_array_sized_new (5);
+
+	for (i = 0; i < nm_ip4_config_get_num_addresses (ip4); i++) {
+		const NMPlatformIP4Address *address = nm_ip4_config_get_address (ip4, i);
+
+		nm_utils_get_reverse_dns_domains_ip4 (address->address, address->plen, domains);
+	}
+
+	for (i = 0; i < nm_ip4_config_get_num_routes (ip4); i++) {
+		const NMPlatformIP4Route *route = nm_ip4_config_get_route (ip4, i);
+
+		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);
+}
+
+static char **
+get_ip6_rdns_domains (NMIP6Config *ip6)
+{
+	char **strv;
+	GPtrArray *domains = NULL;
+	int i;
+
+	g_return_val_if_fail (ip6 != NULL, NULL);
+
+	domains = g_ptr_array_sized_new (5);
+
+	for (i = 0; i < nm_ip6_config_get_num_addresses (ip6); i++) {
+		const NMPlatformIP6Address *address = nm_ip6_config_get_address (ip6, i);
+
+		nm_utils_get_reverse_dns_domains_ip6 (&address->address, address->plen, domains);
+	}
+
+	for (i = 0; i < nm_ip6_config_get_num_routes (ip6); i++) {
+		const NMPlatformIP6Route *route = nm_ip6_config_get_route (ip6, i);
+
+		nm_utils_get_reverse_dns_domains_ip6 (&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);
+}
+
 static void
 add_dnsmasq_nameserver (NMDnsDnsmasq *self,
                         GVariantBuilder *servers,
@@ -89,13 +156,16 @@ add_dnsmasq_nameserver (NMDnsDnsmasq *self,
 }
 
 static gboolean
-add_ip4_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP4Config *ip4, gboolean split)
+add_ip4_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP4Config *ip4,
+                const char *iface, gboolean split)
 {
-	char buf[INET_ADDRSTRLEN];
+	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) {
@@ -106,7 +176,8 @@ add_ip4_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP4Config *ip4,
 
 		for (i_nameserver = 0; i_nameserver < nnameservers; i_nameserver++) {
 			addr = nm_ip4_config_get_nameserver (ip4, i_nameserver);
-			nm_utils_inet4_ntop (addr, buf);
+			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);
@@ -133,22 +204,22 @@ add_ip4_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP4Config *ip4,
 			/* Ensure reverse-DNS works by directing queries for in-addr.arpa
 			 * domains to the split domain's nameserver.
 			 */
-			domains = nm_dns_utils_get_ip4_rdns_domains (ip4);
+			domains = get_ip4_rdns_domains (ip4);
 			if (domains) {
 				for (iter = domains; iter && *iter; iter++)
 					add_dnsmasq_nameserver (self, servers, buf, *iter);
 				g_strfreev (domains);
-				added = TRUE;
 			}
 		}
 	}
 
-	/* If no searches or domains, just add the namservers */
+	/* 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);
-			add_dnsmasq_nameserver (self, servers,
-			                        nm_utils_inet4_ntop (addr, NULL), NULL);
+			g_snprintf (buf, sizeof (buf), "%s@%s",
+			            nm_utils_inet4_ntop (addr, buf2), iface);
+			add_dnsmasq_nameserver (self, servers, buf, NULL);
 		}
 	}
 
@@ -158,23 +229,22 @@ add_ip4_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP4Config *ip4,
 static char *
 ip6_addr_to_string (const struct in6_addr *addr, const char *iface)
 {
-	char *buf;
+	char buf[NM_UTILS_INET_ADDRSTRLEN];
 
-	if (IN6_IS_ADDR_V4MAPPED (addr)) {
-		buf = g_malloc (INET_ADDRSTRLEN);
+	if (IN6_IS_ADDR_V4MAPPED (addr))
 		nm_utils_inet4_ntop (addr->s6_addr32[3], buf);
-	} else if (!iface || !iface[0] || !IN6_IS_ADDR_LINKLOCAL (addr)) {
-		buf = g_malloc (INET6_ADDRSTRLEN);
+	else
 		nm_utils_inet6_ntop (addr, buf);
-	} else {
-		/* Need to scope the address 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.
-		 */
-		buf = g_strconcat (nm_utils_inet6_ntop (addr, NULL), "%", iface, NULL);
-	}
-	return 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);
 }
 
 static void
@@ -202,20 +272,20 @@ add_global_config (NMDnsDnsmasq *self, GVariantBuilder *dnsmasq_servers, const N
 }
 
 static gboolean
-add_ip6_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP6Config *ip6, gboolean split)
+add_ip6_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP6Config *ip6,
+                const char *iface, gboolean split)
 {
 	const struct in6_addr *addr;
 	char *buf = NULL;
 	int nnameservers, i_nameserver, n, i;
 	gboolean added = FALSE;
-	const char *iface;
 
+	g_return_val_if_fail (iface, FALSE);
 	nnameservers = nm_ip6_config_get_num_nameservers (ip6);
 
-	iface = g_object_get_data (G_OBJECT (ip6), IP_CONFIG_IFACE_TAG);
-	g_assert (iface);
-
 	if (split) {
+		char **domains, **iter;
+
 		if (nnameservers == 0)
 			return FALSE;
 
@@ -245,11 +315,21 @@ add_ip6_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP6Config *ip6,
 				}
 			}
 
+			/* 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);
+			}
+
 			g_free (buf);
 		}
 	}
 
-	/* If no searches or domains, just add the namservers */
+	/* 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);
@@ -264,19 +344,71 @@ add_ip6_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP6Config *ip6,
 	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 (GObject *source, GAsyncResult *res, gpointer user_data)
+dnsmasq_clear_cache_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
 {
-	NMDnsDnsmasq *self = NM_DNS_DNSMASQ (user_data);
-	NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE (self);
+	NMDnsDnsmasq *self;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *response = NULL;
 
-	response = g_dbus_proxy_call_finish (priv->dnsmasq, res, &error);
+	response = g_dbus_proxy_call_finish (proxy, res, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_DNS_DNSMASQ (user_data);
+
 	if (!response)
-		_LOGW ("dnsmasq update failed: %s", error->message);
+		_LOGW ("dnsmasq cache clear failed: %s", error->message);
 	else
-		_LOGD ("dnsmasq update successful");
+		_LOGD ("dnsmasq update successful, cache cleared");
+}
+
+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;
+
+	response = g_dbus_proxy_call_finish (proxy, res, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_DNS_DNSMASQ (user_data);
+	priv = NM_DNS_DNSMASQ_GET_PRIVATE (self);
+
+	if (!response)
+		_LOGW ("dnsmasq update failed: %s", error->message);
+	else {
+		g_dbus_proxy_call (priv->dnsmasq,
+		                   "ClearCache",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->update_cancellable,
+		                   (GAsyncReadyCallback) dnsmasq_clear_cache_done,
+		                   self);
+	}
 }
 
 static void
@@ -290,12 +422,15 @@ send_dnsmasq_update (NMDnsDnsmasq *self)
 	if (priv->running) {
 		_LOGD ("trying to update dnsmasq nameservers");
 
+		nm_clear_g_cancellable (&priv->update_cancellable);
+		priv->update_cancellable = g_cancellable_new ();
+
 		g_dbus_proxy_call (priv->dnsmasq,
 		                   "SetServersEx",
 		                   priv->set_server_ex_args,
 		                   G_DBUS_CALL_FLAGS_NONE,
 		                   -1,
-		                   NULL,
+		                   priv->update_cancellable,
 		                   (GAsyncReadyCallback) dnsmasq_update_done,
 		                   self);
 		g_clear_pointer (&priv->set_server_ex_args, g_variant_unref);
@@ -373,11 +508,16 @@ start_dnsmasq (NMDnsDnsmasq *self)
 	NMBusManager *dbus_mgr;
 	GDBusConnection *connection;
 
+	if (priv->running) {
+		/* the dnsmasq process is running. Nothing to do. */
+		return;
+	}
 
-	if (   priv->running
-	    || priv->dnsmasq
-	    || priv->dnsmasq_cancellable)
+	if (nm_dns_plugin_child_pid ((NMDnsPlugin *) self) > 0) {
+		/* if we already have a child process spawned, don't do
+		 * it again. */
 		return;
+	}
 
 	dm_binary = nm_utils_find_helper ("dnsmasq", DNSMASQ_PATH, NULL);
 	if (!dm_binary) {
@@ -409,6 +549,13 @@ start_dnsmasq (NMDnsDnsmasq *self)
 	if (!pid)
 		return;
 
+	if (   priv->dnsmasq
+	    || priv->dnsmasq_cancellable) {
+		/* we already have a proxy or are about to create it.
+		 * We are done. */
+		return;
+	}
+
 	dbus_mgr = nm_bus_manager_get ();
 	g_return_if_fail (dbus_mgr);
 
@@ -429,15 +576,12 @@ start_dnsmasq (NMDnsDnsmasq *self)
 
 static gboolean
 update (NMDnsPlugin *plugin,
-        const GSList *vpn_configs,
-        const GSList *dev_configs,
-        const GSList *other_configs,
+        const NMDnsIPConfigData **configs,
         const NMGlobalDnsConfig *global_config,
         const char *hostname)
 {
 	NMDnsDnsmasq *self = NM_DNS_DNSMASQ (plugin);
 	NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE (self);
-	const GSList *iter;
 	GVariantBuilder servers;
 
 	start_dnsmasq (self);
@@ -447,28 +591,9 @@ update (NMDnsPlugin *plugin,
 	if (global_config)
 		add_global_config (self, &servers, global_config);
 	else {
-		/* Use split DNS for VPN configs */
-		for (iter = vpn_configs; iter; iter = g_slist_next (iter)) {
-			if (NM_IS_IP4_CONFIG (iter->data))
-				add_ip4_config (self, &servers, iter->data, TRUE);
-			else if (NM_IS_IP6_CONFIG (iter->data))
-				add_ip6_config (self, &servers, iter->data, TRUE);
-		}
-
-		/* Now add interface configs without split DNS */
-		for (iter = dev_configs; iter; iter = g_slist_next (iter)) {
-			if (NM_IS_IP4_CONFIG (iter->data))
-				add_ip4_config (self, &servers, iter->data, FALSE);
-			else if (NM_IS_IP6_CONFIG (iter->data))
-				add_ip6_config (self, &servers, iter->data, FALSE);
-		}
-
-		/* And any other random configs */
-		for (iter = other_configs; iter; iter = g_slist_next (iter)) {
-			if (NM_IS_IP4_CONFIG (iter->data))
-				add_ip4_config (self, &servers, iter->data, FALSE);
-			else if (NM_IS_IP6_CONFIG (iter->data))
-				add_ip6_config (self, &servers, iter->data, FALSE);
+		while (*configs) {
+			add_ip_config_data (self, &servers, *configs);
+			configs++;
 		}
 	}
 
@@ -482,39 +607,23 @@ update (NMDnsPlugin *plugin,
 
 /****************************************************************/
 
-static const char *
-dm_exit_code_to_msg (int status)
-{
-	if (status == 1)
-		return "Configuration problem";
-	else if (status == 2)
-		return "Network access problem (address in use; permissions; etc)";
-	else if (status == 3)
-		return "Filesystem problem (missing file/directory; permissions; etc)";
-	else if (status == 4)
-		return "Memory allocation failure";
-	else if (status == 5)
-		return "Other problem";
-	else if (status >= 11)
-		return "Lease-script 'init' process failure";
-	return "Unknown error";
-}
-
 static void
 child_quit (NMDnsPlugin *plugin, gint status)
 {
 	NMDnsDnsmasq *self = NM_DNS_DNSMASQ (plugin);
+	NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE (self);
 	gboolean failed = TRUE;
 	int err;
 
 	if (WIFEXITED (status)) {
 		err = WEXITSTATUS (status);
 		if (err) {
-			_LOGW ("dnsmasq exited with error: %s (%d)",
-			       dm_exit_code_to_msg (err),
-			       err);
-		} else
+			_LOGW ("dnsmasq exited with error: %s",
+			       nm_utils_dnsmasq_status_to_string (err, NULL, 0));
+		} else {
+			_LOGD ("dnsmasq exited normally");
 			failed = FALSE;
+		}
 	} else if (WIFSTOPPED (status))
 		_LOGW ("dnsmasq stopped unexpectedly with signal %d", WSTOPSIG (status));
 	else if (WIFSIGNALED (status))
@@ -522,6 +631,8 @@ child_quit (NMDnsPlugin *plugin, gint status)
 	else
 		_LOGW ("dnsmasq died from an unknown cause");
 
+	priv->running = FALSE;
+
 	if (failed)
 		g_signal_emit_by_name (self, NM_DNS_PLUGIN_FAILED);
 }
@@ -559,6 +670,7 @@ dispose (GObject *object)
 	NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE (object);
 
 	nm_clear_g_cancellable (&priv->dnsmasq_cancellable);
+	nm_clear_g_cancellable (&priv->update_cancellable);
 
 	g_clear_object (&priv->dnsmasq);