about summary refs log tree commit diff
path: root/src/dnsmasq-manager/nm-dnsmasq-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dnsmasq-manager/nm-dnsmasq-manager.c')
-rw-r--r--src/dnsmasq-manager/nm-dnsmasq-manager.c495
1 files changed, 495 insertions, 0 deletions
diff --git a/src/dnsmasq-manager/nm-dnsmasq-manager.c b/src/dnsmasq-manager/nm-dnsmasq-manager.c
new file mode 100644
index 00000000..e6c67a1e
--- /dev/null
+++ b/src/dnsmasq-manager/nm-dnsmasq-manager.c
@@ -0,0 +1,495 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2008 - 2010 Red Hat, Inc.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+
+#include "nm-dnsmasq-manager.h"
+#include "nm-logging.h"
+#include "nm-glib-compat.h"
+
+typedef struct {
+	char *iface;
+	char *pidfile;
+	GPid pid;
+	guint32 dm_watch_id;
+} NMDnsMasqManagerPrivate;
+
+#define NM_DNSMASQ_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DNSMASQ_MANAGER, NMDnsMasqManagerPrivate))
+
+G_DEFINE_TYPE (NMDnsMasqManager, nm_dnsmasq_manager, G_TYPE_OBJECT)
+
+enum {
+	STATE_CHANGED,
+
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef enum {
+	NM_DNSMASQ_MANAGER_ERROR_NOT_FOUND,
+} NMDnsMasqManagerError;
+
+GQuark
+nm_dnsmasq_manager_error_quark (void)
+{
+	static GQuark quark;
+
+	if (!quark)
+		quark = g_quark_from_static_string ("nm_dnsmasq_manager_error");
+
+	return quark;
+}
+
+static void
+nm_dnsmasq_manager_init (NMDnsMasqManager *manager)
+{
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDnsMasqManagerPrivate *priv = NM_DNSMASQ_MANAGER_GET_PRIVATE (object);
+
+	nm_dnsmasq_manager_stop (NM_DNSMASQ_MANAGER (object));
+
+	g_free (priv->iface);
+	g_free (priv->pidfile);
+
+	G_OBJECT_CLASS (nm_dnsmasq_manager_parent_class)->finalize (object);
+}
+
+static void
+nm_dnsmasq_manager_class_init (NMDnsMasqManagerClass *manager_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (manager_class);
+
+	g_type_class_add_private (manager_class, sizeof (NMDnsMasqManagerPrivate));
+
+	object_class->finalize = finalize;
+
+	/* signals */
+	signals[STATE_CHANGED] =
+		g_signal_new ("state-changed",
+				    G_OBJECT_CLASS_TYPE (object_class),
+				    G_SIGNAL_RUN_FIRST,
+				    G_STRUCT_OFFSET (NMDnsMasqManagerClass, state_changed),
+				    NULL, NULL,
+				    g_cclosure_marshal_VOID__UINT,
+				    G_TYPE_NONE, 1,
+				    G_TYPE_UINT);
+}
+
+NMDnsMasqManager *
+nm_dnsmasq_manager_new (const char *iface)
+{
+	NMDnsMasqManager *manager;
+	NMDnsMasqManagerPrivate *priv;
+
+	manager = (NMDnsMasqManager *) g_object_new (NM_TYPE_DNSMASQ_MANAGER, NULL);
+	if (!manager)
+		return NULL;
+
+	priv = NM_DNSMASQ_MANAGER_GET_PRIVATE (manager);
+	priv->iface = g_strdup (iface);
+	priv->pidfile = g_strdup_printf (LOCALSTATEDIR "/run/nm-dnsmasq-%s.pid", iface);
+
+	return manager;
+}
+
+typedef struct {
+	GPtrArray *array;
+	GStringChunk *chunk;
+} NMCmdLine;
+
+static NMCmdLine *
+nm_cmd_line_new (void)
+{
+	NMCmdLine *cmd;
+
+	cmd = g_slice_new (NMCmdLine);
+	cmd->array = g_ptr_array_new ();
+	cmd->chunk = g_string_chunk_new (1024);
+
+	return cmd;
+}
+
+static void
+nm_cmd_line_destroy (NMCmdLine *cmd)
+{
+	g_ptr_array_free (cmd->array, TRUE);
+	g_string_chunk_free (cmd->chunk);
+	g_slice_free (NMCmdLine, cmd);
+}
+
+static char *
+nm_cmd_line_to_str (NMCmdLine *cmd)
+{
+	char *str;
+
+	g_ptr_array_add (cmd->array, NULL);
+	str = g_strjoinv (" ", (gchar **) cmd->array->pdata);
+	g_ptr_array_remove_index (cmd->array, cmd->array->len - 1);
+
+	return str;
+}
+
+static void
+nm_cmd_line_add_string (NMCmdLine *cmd, const char *str)
+{
+	g_ptr_array_add (cmd->array, g_string_chunk_insert (cmd->chunk, str));
+}
+
+/*******************************************/
+
+static inline const char *
+nm_find_dnsmasq (void)
+{
+	static const char *dnsmasq_binary_paths[] = {
+		"/usr/local/sbin/dnsmasq",
+		"/usr/sbin/dnsmasq",
+		"/sbin/dnsmasq",
+		NULL
+	};
+
+	const char **dnsmasq_binary = dnsmasq_binary_paths;
+
+	while (*dnsmasq_binary != NULL) {
+		if (g_file_test (*dnsmasq_binary, G_FILE_TEST_EXISTS))
+			break;
+		dnsmasq_binary++;
+	}
+
+	return *dnsmasq_binary;
+}
+
+static void
+dm_exit_code (guint dm_exit_status)
+{
+	char *msg = "Unknown error";
+
+	switch (dm_exit_status) {
+	case 1:
+		msg = "Configuration problem";
+		break;
+	case 2:
+		msg = "Network access problem (address in use; permissions; etc)";
+		break;
+	case 3:
+		msg = "Filesystem problem (missing file/directory; permissions; etc)";
+		break;
+	case 4:
+		msg = "Memory allocation failure";
+		break;
+	case 5: 
+		msg = "Other problem";
+		break;
+	default:
+		if (dm_exit_status >= 11)
+			msg = "Lease-script 'init' process failure";
+		break;
+	}
+
+	nm_log_warn (LOGD_SHARING, "dnsmasq exited with error: %s (%d)", msg, dm_exit_status);
+}
+
+static void
+dm_watch_cb (GPid pid, gint status, gpointer user_data)
+{
+	NMDnsMasqManager *manager = NM_DNSMASQ_MANAGER (user_data);
+	NMDnsMasqManagerPrivate *priv = NM_DNSMASQ_MANAGER_GET_PRIVATE (manager);
+	guint err;
+
+	if (WIFEXITED (status)) {
+		err = WEXITSTATUS (status);
+		if (err != 0)
+			dm_exit_code (err);
+	} else if (WIFSTOPPED (status)) {
+		nm_log_warn (LOGD_SHARING, "dnsmasq stopped unexpectedly with signal %d", WSTOPSIG (status));
+	} else if (WIFSIGNALED (status)) {
+		nm_log_warn (LOGD_SHARING, "dnsmasq died with signal %d", WTERMSIG (status));
+	} else {
+		nm_log_warn (LOGD_SHARING, "dnsmasq died from an unknown cause");
+	}
+  
+	priv->pid = 0;
+
+	g_signal_emit (manager, signals[STATE_CHANGED], 0, NM_DNSMASQ_STATUS_DEAD);
+}
+
+static NMCmdLine *
+create_dm_cmd_line (const char *iface,
+                    NMIP4Config *ip4_config,
+                    const char *pidfile,
+                    GError **error)
+{
+	const char *dm_binary;
+	NMCmdLine *cmd;
+	GString *s;
+	NMIP4Address *tmp;
+	struct in_addr addr;
+	char buf[INET_ADDRSTRLEN + 15];
+	char localaddr[INET_ADDRSTRLEN + 1];
+	int i;
+
+	dm_binary = nm_find_dnsmasq ();
+	if (!dm_binary) {
+		g_set_error (error, NM_DNSMASQ_MANAGER_ERROR, NM_DNSMASQ_MANAGER_ERROR_NOT_FOUND,
+		             "Could not find dnsmasq binary.");
+		return NULL;
+	}
+
+	/* Find the IP4 address to use */
+	tmp = nm_ip4_config_get_address (ip4_config, 0);
+
+	/* Create dnsmasq command line */
+	cmd = nm_cmd_line_new ();
+	nm_cmd_line_add_string (cmd, dm_binary);
+
+	if (getenv ("NM_DNSMASQ_DEBUG")) {
+		nm_cmd_line_add_string (cmd, "--log-dhcp");
+		nm_cmd_line_add_string (cmd, "--log-queries");
+	}
+
+	/* dnsmasq may read from it's default config file location, which if that
+	 * location is a valid config file, it will combine with the options here
+	 * and cause undesirable side-effects.  Like sending bogus IP addresses
+	 * as the gateway or whatever.  So give dnsmasq a bogus config file
+	 * location to avoid screwing up the configuration we're passing to it.
+	 */
+	memset (buf, 0, sizeof (buf));
+	strcpy (buf, "/tmp/");
+	for (i = 5; i < 15; i++)
+		buf[i] = (char) (g_random_int_range ((guint32) 'a', (guint32) 'z') & 0xFF);
+	strcat (buf, ".conf");
+
+	nm_cmd_line_add_string (cmd, "--conf-file");
+	nm_cmd_line_add_string (cmd, buf);
+
+	nm_cmd_line_add_string (cmd, "--no-hosts");
+	nm_cmd_line_add_string (cmd, "--keep-in-foreground");
+	nm_cmd_line_add_string (cmd, "--bind-interfaces");
+	nm_cmd_line_add_string (cmd, "--except-interface=lo");
+	nm_cmd_line_add_string (cmd, "--clear-on-reload");
+
+	/* Use strict order since in the case of VPN connections, the VPN's
+	 * nameservers will be first in resolv.conf, and those need to be tried
+	 * first by dnsmasq to successfully resolve names from the VPN.
+	 */
+	nm_cmd_line_add_string (cmd, "--strict-order");
+
+	s = g_string_new ("--listen-address=");
+	addr.s_addr = nm_ip4_address_get_address (tmp);
+	if (!inet_ntop (AF_INET, &addr, &localaddr[0], INET_ADDRSTRLEN)) {
+		nm_log_warn (LOGD_SHARING, "error converting IP4 address 0x%X",
+		             ntohl (addr.s_addr));
+		goto error;
+	}
+	g_string_append (s, localaddr);
+	nm_cmd_line_add_string (cmd, s->str);
+	g_string_free (s, TRUE);
+
+	s = g_string_new ("--dhcp-range=");
+
+	/* Add start of address range */
+	addr.s_addr = nm_ip4_address_get_address (tmp) + htonl (9);
+	if (!inet_ntop (AF_INET, &addr, &buf[0], INET_ADDRSTRLEN)) {
+		nm_log_warn (LOGD_SHARING, "error converting IP4 address 0x%X",
+		             ntohl (addr.s_addr));
+		goto error;
+	}
+	g_string_append (s, buf);
+
+	g_string_append_c (s, ',');
+
+	/* Add end of address range */
+	addr.s_addr = nm_ip4_address_get_address (tmp) + htonl (99);
+	if (!inet_ntop (AF_INET, &addr, &buf[0], INET_ADDRSTRLEN)) {
+		nm_log_warn (LOGD_SHARING, "error converting IP4 address 0x%X",
+		             ntohl (addr.s_addr));
+		goto error;
+	}
+	g_string_append (s, buf);
+
+	g_string_append (s, ",60m");
+	nm_cmd_line_add_string (cmd, s->str);
+	g_string_free (s, TRUE);
+
+	s = g_string_new ("--dhcp-option=option:router,");
+	g_string_append (s, localaddr);
+	nm_cmd_line_add_string (cmd, s->str);
+	g_string_free (s, TRUE);
+
+	nm_cmd_line_add_string (cmd, "--dhcp-lease-max=50");
+
+	s = g_string_new ("--pid-file=");
+	g_string_append (s, pidfile);
+	nm_cmd_line_add_string (cmd, s->str);
+	g_string_free (s, TRUE);
+
+	return cmd;
+
+error:
+	nm_cmd_line_destroy (cmd);
+	return NULL;
+}
+
+static void
+dm_child_setup (gpointer user_data G_GNUC_UNUSED)
+{
+	/* We are in the child process at this point */
+	pid_t pid = getpid ();
+	setpgid (pid, pid);
+}
+
+static void
+kill_existing_for_iface (const char *iface, const char *pidfile)
+{
+	char *contents = NULL;
+	glong pid;
+	char *proc_path = NULL;
+	char *cmdline_contents = NULL;
+
+	if (!g_file_get_contents (pidfile, &contents, NULL, NULL))
+		goto out;
+
+	pid = strtol (contents, NULL, 10);
+	if (pid < 1 || pid > INT_MAX)
+		goto out;
+
+	proc_path = g_strdup_printf ("/proc/%ld/cmdline", pid);
+	if (!g_file_get_contents (proc_path, &cmdline_contents, NULL, NULL))
+		goto out;
+
+	if (strstr (cmdline_contents, "bin/dnsmasq")) {
+		if (kill (pid, 0)) {
+			nm_log_dbg (LOGD_SHARING, "Killing stale dnsmasq process %ld", pid);
+			kill (pid, SIGKILL);
+		}
+		unlink (pidfile);
+	}
+
+out:
+	g_free (cmdline_contents);
+	g_free (proc_path);
+	g_free (contents);
+}
+
+gboolean
+nm_dnsmasq_manager_start (NMDnsMasqManager *manager,
+                          NMIP4Config *ip4_config,
+                          GError **error)
+{
+	NMDnsMasqManagerPrivate *priv;
+	NMCmdLine *dm_cmd;
+	char *cmd_str;
+
+	g_return_val_if_fail (NM_IS_DNSMASQ_MANAGER (manager), FALSE);
+	if (error)
+		g_return_val_if_fail (*error == NULL, FALSE);
+
+	priv = NM_DNSMASQ_MANAGER_GET_PRIVATE (manager);
+
+	kill_existing_for_iface (priv->iface, priv->pidfile);
+
+	dm_cmd = create_dm_cmd_line (priv->iface, ip4_config, priv->pidfile, error);
+	if (!dm_cmd)
+		return FALSE;
+
+	g_ptr_array_add (dm_cmd->array, NULL);
+
+	nm_log_info (LOGD_SHARING, "Starting dnsmasq...");
+
+	cmd_str = nm_cmd_line_to_str (dm_cmd);
+	nm_log_dbg (LOGD_SHARING, "Command line: %s", cmd_str);
+	g_free (cmd_str);
+
+	priv->pid = 0;
+	if (!g_spawn_async (NULL, (char **) dm_cmd->array->pdata, NULL,
+					G_SPAWN_DO_NOT_REAP_CHILD,
+					dm_child_setup,
+					NULL, &priv->pid, error)) {
+		goto out;
+	}
+
+	nm_log_dbg (LOGD_SHARING, "dnsmasq started with pid %d", priv->pid);
+
+	priv->dm_watch_id = g_child_watch_add (priv->pid, (GChildWatchFunc) dm_watch_cb, manager);
+
+ out:
+	if (dm_cmd)
+		nm_cmd_line_destroy (dm_cmd);
+
+	return priv->pid > 0;
+}
+
+static gboolean
+ensure_killed (gpointer data)
+{
+	int pid = GPOINTER_TO_INT (data);
+
+	if (kill (pid, 0) == 0)
+		kill (pid, SIGKILL);
+
+	/* ensure the child is reaped */
+	nm_log_dbg (LOGD_SHARING, "waiting for dnsmasq pid %d to exit", pid);
+	waitpid (pid, NULL, 0);
+	nm_log_dbg (LOGD_SHARING, "dnsmasq pid %d cleaned up", pid);
+
+	return FALSE;
+}
+
+void
+nm_dnsmasq_manager_stop (NMDnsMasqManager *manager)
+{
+	NMDnsMasqManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_DNSMASQ_MANAGER (manager));
+
+	priv = NM_DNSMASQ_MANAGER_GET_PRIVATE (manager);
+
+	if (priv->dm_watch_id) {
+		g_source_remove (priv->dm_watch_id);
+		priv->dm_watch_id = 0;
+	}
+
+	if (priv->pid) {
+		if (kill (priv->pid, SIGTERM) == 0)
+			g_timeout_add_seconds (2, ensure_killed, GINT_TO_POINTER (priv->pid));
+		else {
+			kill (priv->pid, SIGKILL);
+
+			/* ensure the child is reaped */
+			nm_log_dbg (LOGD_SHARING, "waiting for dnsmasq pid %d to exit", priv->pid);
+			waitpid (priv->pid, NULL, 0);
+			nm_log_dbg (LOGD_SHARING, "dnsmasq pid %d cleaned up", priv->pid);
+		}
+
+		priv->pid = 0;
+	}
+
+	unlink (priv->pidfile);
+}