diff options
author | Dan Williams <dcbw@redhat.com> | 2010-09-10 18:02:40 -0500 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2010-09-22 16:24:18 -0500 |
commit | a7fe393d94e12ae071bbe55d242b61d0b97d469e (patch) | |
tree | d0039c97fdd7f494e0b5047c85177422fa9e2bda /src | |
parent | aeb04f54dc21d60eb0c0e9f7d653c7c780349808 (diff) |
dns: first cut of a dnsmasq local caching plugin
Diffstat (limited to 'src')
-rw-r--r-- | src/dns-manager/Makefile.am | 11 | ||||
-rw-r--r-- | src/dns-manager/nm-dns-dnsmasq.c | 391 | ||||
-rw-r--r-- | src/dns-manager/nm-dns-dnsmasq.h | 47 | ||||
-rw-r--r-- | src/dns-manager/nm-dns-manager.c | 18 | ||||
-rw-r--r-- | src/dns-manager/nm-dns-plugin.c | 311 | ||||
-rw-r--r-- | src/dns-manager/nm-dns-plugin.h | 107 |
6 files changed, 871 insertions, 14 deletions
diff --git a/src/dns-manager/Makefile.am b/src/dns-manager/Makefile.am index d1ea5f87e9..2a56fb8602 100644 --- a/src/dns-manager/Makefile.am +++ b/src/dns-manager/Makefile.am @@ -6,13 +6,18 @@ INCLUDES = \ noinst_LTLIBRARIES = libdns-manager.la -libdns_manager_la_SOURCES = nm-dns-manager.h nm-dns-manager.c +libdns_manager_la_SOURCES = \ + nm-dns-manager.h \ + nm-dns-manager.c \ + nm-dns-plugin.h \ + nm-dns-plugin.c \ + nm-dns-dnsmasq.h \ + nm-dns-dnsmasq.c libdns_manager_la_CPPFLAGS = \ $(DBUS_CFLAGS) \ $(GLIB_CFLAGS) \ - -DNM_PKGDATADIR=\"$(pkgdatadir)\" \ - -DNM_LOCALSTATEDIR=\"$(localstatedir)\" + -DLOCALSTATEDIR=\"$(localstatedir)\" libdns_manager_la_LIBADD = \ $(top_builddir)/src/logging/libnm-logging.la \ diff --git a/src/dns-manager/nm-dns-dnsmasq.c b/src/dns-manager/nm-dns-dnsmasq.c new file mode 100644 index 0000000000..5fd6fe6570 --- /dev/null +++ b/src/dns-manager/nm-dns-dnsmasq.c @@ -0,0 +1,391 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Dan Williams <dcbw@redhat.com> + * + * 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, 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. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <arpa/inet.h> +#include <sys/stat.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include "nm-dns-dnsmasq.h" +#include "nm-logging.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" + +G_DEFINE_TYPE (NMDnsDnsmasq, nm_dns_dnsmasq, NM_TYPE_DNS_PLUGIN) + +#define NM_DNS_DNSMASQ_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasqPrivate)) + +#define PIDFILE LOCALSTATEDIR "/run/nm-dns-dnsmasq.pid" +#define CONFFILE LOCALSTATEDIR "/run/nm-dns-dnsmasq.conf" + +typedef struct { + int pid; +} NMDnsDnsmasqPrivate; + +/*******************************************/ + +#if 0 +static NMCmdLine * +create_dm_cmd_line (const char *iface, + const char *pidfile, + GError **error) +{ + const char *dm_binary; + GString *conf; + 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) { + nm_log_warn (LOGD_DNS, "could not find dnsmasq binary."); + return NULL; + } + + /* 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-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); + return cmd; + +error: + nm_cmd_line_destroy (cmd); + return NULL; +} +#endif + +static inline const char * +find_dnsmasq (void) +{ + static const char *paths[] = { + "/usr/local/sbin/dnsmasq", + "/usr/sbin/dnsmasq", + "/sbin/dnsmasq", + NULL + }; + const char **binary = paths; + + while (*binary != NULL) { + if (g_file_test (*binary, G_FILE_TEST_EXISTS)) + return *binary; + binary++; + } + return NULL; +} + +static gboolean +add_ip4_config (GString *str, NMIP4Config *ip4, gboolean split) +{ + char buf[INET_ADDRSTRLEN + 1]; + struct in_addr addr; + int n, i; + + /* FIXME: it appears that dnsmasq can only handle one nameserver + * per domain (at the manpage seems to indicate that) so only use + * the first nameserver here. + */ + addr.s_addr = nm_ip4_config_get_nameserver (ip4, 0); + memset (&buf[0], 0, sizeof (buf)); + if (!inet_ntop (AF_INET, &addr, buf, sizeof (buf))) + return FALSE; + + /* searches are preferred over domains */ + n = nm_ip4_config_get_num_searches (ip4); + for (i = 0; i < n; i++) { + g_string_append_printf (str, "server=%s%s%s%s\n", + split ? "/" : "", + split ? nm_ip4_config_get_search (ip4, i) : "", + split ? "/" : "", + buf); + } + + if (n == 0) { + /* If not searches, use any domains */ + n = nm_ip4_config_get_num_domains (ip4); + for (i = 0; i < n; i++) { + g_string_append_printf (str, "server=%s%s%s%s\n", + split ? "/" : "", + split ? nm_ip4_config_get_domain (ip4, i) : "", + split ? "/" : "", + buf); + } + } + + return TRUE; +} + +static gboolean +add_ip6_config (GString *str, NMIP6Config *ip6, gboolean split) +{ + char buf[INET6_ADDRSTRLEN + 1]; + const struct in6_addr *addr; + int n, i; + + /* FIXME: it appears that dnsmasq can only handle one nameserver + * per domain (at the manpage seems to indicate that) so only use + * the first nameserver here. + */ + addr = nm_ip6_config_get_nameserver (ip6, 0); + memset (&buf[0], 0, sizeof (buf)); + + /* inet_ntop is probably supposed to do this for us, but it doesn't */ + if (IN6_IS_ADDR_V4MAPPED (addr)) { + if (!inet_ntop (AF_INET, &(addr->s6_addr32[3]), buf, sizeof (buf))) + return FALSE; + } else { + if (!inet_ntop (AF_INET6, addr, buf, sizeof (buf))) + return FALSE; + } + + /* searches are preferred over domains */ + n = nm_ip6_config_get_num_searches (ip6); + for (i = 0; i < n; i++) { + g_string_append_printf (str, "server=%s%s%s%s\n", + split ? "/" : "", + split ? nm_ip6_config_get_search (ip6, i) : "", + split ? "/" : "", + buf); + } + + if (n == 0) { + /* If not searches, use any domains */ + n = nm_ip6_config_get_num_domains (ip6); + for (i = 0; i < n; i++) { + g_string_append_printf (str, "server=%s%s%s%s\n", + split ? "/" : "", + split ? nm_ip6_config_get_domain (ip6, i) : "", + split ? "/" : "", + buf); + } + } + + return TRUE; +} + +static gboolean +update (NMDnsPlugin *plugin, + const GSList *vpn_configs, + const GSList *dev_configs, + const GSList *other_configs, + const char *hostname) +{ + NMDnsDnsmasq *self = NM_DNS_DNSMASQ (plugin); + NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE (self); + GString *conf; + GSList *iter; + const char *argv[10]; + GError *error = NULL; + int ignored; + + /* Build up the new dnsmasq config file */ + conf = g_string_sized_new (150); + + /* Use split DNS for VPN configs */ + for (iter = (GSList *) vpn_configs; iter; iter = g_slist_next (iter)) { + if (NM_IS_IP4_CONFIG (iter->data)) + add_ip4_config (conf, NM_IP4_CONFIG (iter->data), TRUE); + else if (NM_IS_IP6_CONFIG (iter->data)) + add_ip6_config (conf, NM_IP6_CONFIG (iter->data), TRUE); + } + + /* Now add interface configs without split DNS */ + for (iter = (GSList *) dev_configs; iter;iter = g_slist_next (iter)) { + if (NM_IS_IP4_CONFIG (iter->data)) + add_ip4_config (conf, NM_IP4_CONFIG (iter->data), FALSE); + else if (NM_IS_IP6_CONFIG (iter->data)) + add_ip6_config (conf, NM_IP6_CONFIG (iter->data), FALSE); + } + + /* And any other random configs */ + for (iter = (GSList *) other_configs; iter;iter = g_slist_next (iter)) { + if (NM_IS_IP4_CONFIG (iter->data)) + add_ip4_config (conf, NM_IP4_CONFIG (iter->data), FALSE); + else if (NM_IS_IP6_CONFIG (iter->data)) + add_ip6_config (conf, NM_IP6_CONFIG (iter->data), FALSE); + } + + /* Write out the config file */ + if (!g_file_set_contents (CONFFILE, conf->str, -1, &error)) { + nm_log_warn (LOGD_DNS, "Failed to write dnsmasq config file %s: (%d) %s", + CONFFILE, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + goto out; + } + ignored = chmod (CONFFILE, 0600); + + nm_log_dbg (LOGD_DNS, "dnsmasq local caching DNS configuration:"); + nm_log_dbg (LOGD_DNS, "%s", conf->str); + + argv[0] = find_dnsmasq (); + argv[1] = "--no-resolv"; /* Use only commandline */ + argv[2] = "--keep-in-foreground"; + argv[3] = "--pid-file=" PIDFILE; + argv[4] = "--listen-address=127.0.0.1"; /* Should work for both 4 and 6 */ + argv[5] = "--conf-file=" CONFFILE; + argv[6] = NULL; + + /* And finally spawn dnsmasq */ + priv->pid = nm_dns_plugin_child_spawn (NM_DNS_PLUGIN (self), argv, PIDFILE, "bin/dnsmasq"); + +out: + g_string_free (conf, TRUE); + return priv->pid ? TRUE : FALSE; +} + +/****************************************************************/ + +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) { + nm_log_warn (LOGD_DNS, "dnsmasq exited with error: %s (%d)", + dm_exit_code_to_msg (err), + err); + } else + failed = FALSE; + } else if (WIFSTOPPED (status)) { + nm_log_warn (LOGD_DNS, "dnsmasq stopped unexpectedly with signal %d", WSTOPSIG (status)); + } else if (WIFSIGNALED (status)) { + nm_log_warn (LOGD_DNS, "dnsmasq died with signal %d", WTERMSIG (status)); + } else { + nm_log_warn (LOGD_DNS, "dnsmasq died from an unknown cause"); + } + + if (failed) + g_signal_emit_by_name (self, NM_DNS_PLUGIN_FAILED); + + priv->pid = 0; + unlink (CONFFILE); +} + +/****************************************************************/ + +static gboolean +init (NMDnsPlugin *plugin) +{ + return TRUE; +} + +static gboolean +is_exclusive (NMDnsPlugin *plugin) +{ + return TRUE; +} + +/****************************************************************/ + +NMDnsDnsmasq * +nm_dns_dnsmasq_new (void) +{ + return (NMDnsDnsmasq *) g_object_new (NM_TYPE_DNS_DNSMASQ, NULL); +} + +static void +nm_dns_dnsmasq_init (NMDnsDnsmasq *self) +{ +} + +static void +nm_dns_dnsmasq_class_init (NMDnsDnsmasqClass *dns_class) +{ + NMDnsPluginClass *plugin_class = NM_DNS_PLUGIN_CLASS (dns_class); + + g_type_class_add_private (dns_class, sizeof (NMDnsDnsmasqPrivate)); + + plugin_class->init = init; + plugin_class->child_quit = child_quit; + plugin_class->is_exclusive = is_exclusive; + plugin_class->update = update; +} + diff --git a/src/dns-manager/nm-dns-dnsmasq.h b/src/dns-manager/nm-dns-dnsmasq.h new file mode 100644 index 0000000000..6491b271ae --- /dev/null +++ b/src/dns-manager/nm-dns-dnsmasq.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* 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, 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) 2010 Red Hat, Inc. + */ + +#ifndef NM_DNS_DNSMASQ_H +#define NM_DNS_DNSMASQ_H + +#include <glib.h> +#include <glib-object.h> + +#include "nm-dns-plugin.h" + +#define NM_TYPE_DNS_DNSMASQ (nm_dns_dnsmasq_get_type ()) +#define NM_DNS_DNSMASQ(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasq)) +#define NM_DNS_DNSMASQ_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasqClass)) +#define NM_IS_DNS_DNSMASQ(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DNS_DNSMASQ)) +#define NM_IS_DNS_DNSMASQ_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), NM_TYPE_DNS_DNSMASQ)) +#define NM_DNS_DNSMASQ_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasqClass)) + +typedef struct { + NMDnsPlugin parent; +} NMDnsDnsmasq; + +typedef struct { + NMDnsPluginClass parent; +} NMDnsDnsmasqClass; + +GType nm_dns_dnsmasq_get_type (void); + +NMDnsDnsmasq *nm_dns_dnsmasq_new (void); + +#endif /* NM_DNS_DNSMASQ_H */ + diff --git a/src/dns-manager/nm-dns-manager.c b/src/dns-manager/nm-dns-manager.c index c3a0a4ff45..615532133c 100644 --- a/src/dns-manager/nm-dns-manager.c +++ b/src/dns-manager/nm-dns-manager.c @@ -43,6 +43,8 @@ #include "nm-system.h" #include "NetworkManagerUtils.h" +#include "nm-dns-dnsmasq.h" + #ifdef HAVE_SELINUX #include <selinux/selinux.h> #endif @@ -829,12 +831,6 @@ nm_dns_manager_set_hostname (NMDnsManager *mgr, } static GObject * -nm_dns_dnsmasq_new (void) -{ - return NULL; -} - -static GObject * nm_dns_bind_new (void) { return NULL; @@ -850,18 +846,18 @@ static void load_plugins (NMDnsManager *self, const char **plugins) { NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self); - GObject *plugin; + NMDnsPlugin *plugin; const char **iter; if (plugins && *plugins) { /* Create each configured plugin */ for (iter = plugins; iter && *iter; iter++) { if (!strcasecmp (*iter, "dnsmasq")) - plugin = nm_dns_dnsmasq_new (); + plugin = NM_DNS_PLUGIN (nm_dns_dnsmasq_new ()); else if (!strcasecmp (*iter, "bind")) - plugin = nm_dns_bind_new (); + plugin = NM_DNS_PLUGIN (nm_dns_bind_new ()); else if (!strcasecmp (*iter, "chromium")) - plugin = nm_dns_chromium_new (); + plugin = NM_DNS_PLUGIN (nm_dns_chromium_new ()); else { nm_log_warn (LOGD_DNS, "Unknown DNS plugin '%s'", *iter); } @@ -873,7 +869,7 @@ load_plugins (NMDnsManager *self, const char **plugins) /* Create default plugins */ /* Chromium support */ - plugin = nm_dns_chromium_new (); + plugin = NM_DNS_PLUGIN (nm_dns_chromium_new ()); g_assert (plugin); priv->plugins = g_slist_append (priv->plugins, plugin); } diff --git a/src/dns-manager/nm-dns-plugin.c b/src/dns-manager/nm-dns-plugin.c new file mode 100644 index 0000000000..bfa80bd87f --- /dev/null +++ b/src/dns-manager/nm-dns-plugin.c @@ -0,0 +1,311 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* 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, 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) 2010 Red Hat, Inc. + * + */ + +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <glib.h> + +#include "nm-dns-plugin.h" +#include "nm-logging.h" + +typedef struct { + gboolean disposed; + + GPid pid; + guint32 watch_id; + char *progname; + char *pidfile; +} NMDnsPluginPrivate; + +#define NM_DNS_PLUGIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DNS_PLUGIN, NMDnsPluginPrivate)) + +G_DEFINE_TYPE_EXTENDED (NMDnsPlugin, nm_dns_plugin, G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT, {}) + +enum { + FAILED, + CHILD_QUIT, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0 }; + +/********************************************/ + +gboolean +nm_dns_plugin_update (NMDnsPlugin *self, + const GSList *vpn_configs, + const GSList *dev_configs, + const GSList *other_configs, + 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, + vpn_configs, + dev_configs, + other_configs, + hostname); +} + +static gboolean +is_exclusive (NMDnsPlugin *self) +{ + return FALSE; +} + +gboolean +nm_dns_plugin_is_exclusive (NMDnsPlugin *self) +{ + return NM_DNS_PLUGIN_GET_CLASS (self)->is_exclusive (self); +} + +/********************************************/ + +static void +kill_existing (const char *progname, const char *pidfile, const char *kill_match) +{ + char *contents = NULL; + glong pid; + char *proc_path = NULL; + char *cmdline_contents = NULL; + + if (!g_file_get_contents (pidfile, &contents, NULL, NULL)) + return; + + 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, kill_match)) { + if (kill (pid, 0)) { + nm_log_dbg (LOGD_DNS, "Killing stale %s child process %ld", progname, pid); + kill (pid, SIGKILL); + } + unlink (pidfile); + } + +out: + g_free (cmdline_contents); + g_free (proc_path); + g_free (contents); +} + +static void +watch_cb (GPid pid, gint status, gpointer user_data) +{ + NMDnsPlugin *self = NM_DNS_PLUGIN (user_data); + NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self); + + priv->pid = 0; + g_free (priv->progname); + priv->progname = NULL; + + g_signal_emit (self, signals[CHILD_QUIT], 0, status); +} + +static void +child_setup (gpointer user_data G_GNUC_UNUSED) +{ + /* We are in the child process at this point */ + pid_t pid = getpid (); + setpgid (pid, pid); +} + +GPid +nm_dns_plugin_child_spawn (NMDnsPlugin *self, + const char **argv, + const char *pidfile, + const char *kill_match) +{ + NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self); + GError *error = NULL; + char *cmdline; + + g_return_val_if_fail (argv != NULL, 0); + g_return_val_if_fail (argv[0] != NULL, 0); + + g_warn_if_fail (priv->progname == NULL); + g_free (priv->progname); + priv->progname = g_path_get_basename (argv[0]); + + if (pidfile) { + g_return_val_if_fail (kill_match != NULL, 0); + kill_existing (priv->progname, pidfile, kill_match); + + g_free (priv->pidfile); + priv->pidfile = g_strdup (pidfile); + } + + nm_log_info (LOGD_DNS, "Starting %s...", priv->progname); + cmdline = g_strjoinv (" ", (char **) argv); + nm_log_dbg (LOGD_DNS, "Command line: %s", cmdline); + g_free (cmdline); + + priv->pid = 0; + if (g_spawn_async (NULL, (char **) argv, NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + child_setup, + NULL, &priv->pid, + &error)) { + nm_log_dbg (LOGD_DNS, "%s started with pid %d", priv->progname, priv->pid); + priv->watch_id = g_child_watch_add (priv->pid, (GChildWatchFunc) watch_cb, self); + } else { + nm_log_warn (LOGD_DNS, "Failed to spawn %s: (%d) %s", + priv->progname, error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + } + + return priv->pid; +} + +typedef struct { + int pid; + char *progname; +} KillInfo; + +static gboolean +ensure_killed (gpointer data) +{ + KillInfo *info = data; + + if (kill (info->pid, 0) == 0) + kill (info->pid, SIGKILL); + + /* ensure the child is reaped */ + nm_log_dbg (LOGD_DNS, "waiting for %s pid %d to exit", info->progname, info->pid); + waitpid (info->pid, NULL, 0); + nm_log_dbg (LOGD_DNS, "dnsmasq pid %d cleaned up", info->progname, info->pid); + + g_free (info->progname); + g_free (info); + return FALSE; +} + +gboolean nm_dns_plugin_child_kill (NMDnsPlugin *self) +{ + NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self); + + if (priv->watch_id) { + g_source_remove (priv->watch_id); + priv->watch_id = 0; + } + + if (priv->pid) { + KillInfo *info; + + if (kill (priv->pid, SIGTERM) == 0) { + info = g_malloc0 (sizeof (KillInfo)); + info->pid = priv->pid; + info->progname = g_strdup (priv->progname); + g_timeout_add_seconds (2, ensure_killed, info); + } else { + kill (priv->pid, SIGKILL); + + /* ensure the child is reaped */ + nm_log_dbg (LOGD_DNS, "waiting for %s pid %d to exit", priv->progname, priv->pid); + waitpid (priv->pid, NULL, 0); + nm_log_dbg (LOGD_DNS, "%s pid %d cleaned up", priv->progname, priv->pid); + } + priv->pid = 0; + g_free (priv->progname); + priv->progname = NULL; + } + + if (priv->pidfile) { + unlink (priv->pidfile); + g_free (priv->pidfile); + priv->pidfile = NULL; + } + + return TRUE; +} + +/********************************************/ + +static void +nm_dns_plugin_init (NMDnsPlugin *self) +{ +} + +static void +dispose (GObject *object) +{ + NMDnsPlugin *self = NM_DNS_PLUGIN (object); + NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self); + + if (!priv->disposed) { + priv->disposed = TRUE; + + nm_dns_plugin_child_kill (self); + } + + G_OBJECT_CLASS (nm_dns_plugin_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + NMDnsPlugin *self = NM_DNS_PLUGIN (object); + NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self); + + g_free (priv->progname); + g_free (priv->pidfile); + + G_OBJECT_CLASS (nm_dns_plugin_parent_class)->finalize (object); +} + +static void +nm_dns_plugin_class_init (NMDnsPluginClass *plugin_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (plugin_class); + + g_type_class_add_private (plugin_class, sizeof (NMDnsPluginPrivate)); + + /* virtual methods */ + object_class->dispose = dispose; + object_class->finalize = finalize; + plugin_class->is_exclusive = is_exclusive; + + /* signals */ + signals[FAILED] = + g_signal_new (NM_DNS_PLUGIN_FAILED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDnsPluginClass, failed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[CHILD_QUIT] = + g_signal_new (NM_DNS_PLUGIN_CHILD_QUIT, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDnsPluginClass, child_quit), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); +} + diff --git a/src/dns-manager/nm-dns-plugin.h b/src/dns-manager/nm-dns-plugin.h new file mode 100644 index 0000000000..cb8fabe1ff --- /dev/null +++ b/src/dns-manager/nm-dns-plugin.h @@ -0,0 +1,107 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* 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, 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) 2010 Red Hat, Inc. + */ + +#ifndef NM_DNS_PLUGIN_H +#define NM_DNS_PLUGIN_H + +#include <glib.h> +#include <glib-object.h> + +#define NM_TYPE_DNS_PLUGIN (nm_dns_plugin_get_type ()) +#define NM_DNS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DNS_PLUGIN, NMDnsPlugin)) +#define NM_DNS_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DNS_PLUGIN, NMDnsPluginClass)) +#define NM_IS_DNS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DNS_PLUGIN)) +#define NM_IS_DNS_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), NM_TYPE_DNS_PLUGIN)) +#define NM_DNS_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DNS_PLUGIN, NMDnsPluginClass)) + +#define NM_DNS_PLUGIN_FAILED "failed" +#define NM_DNS_PLUGIN_CHILD_QUIT "child-quit" + +typedef struct { + GObject parent; +} NMDnsPlugin; + +typedef struct { + GObjectClass parent; + + /* Methods */ + gboolean (*init) (NMDnsPlugin *self); + + /* Called when DNS information is changed. 'vpn_configs' is a list of + * NMIP4Config or NMIP6Config objects from VPN connections, while + * 'dev_configs' is a list of NMPI4Config or NMIP6Config objects from + * active devices. 'other_configs' represent other IP configuration that + * may be in-use. Configs of the same IP version are sorted in priority + * order. + */ + gboolean (*update) (NMDnsPlugin *self, + const GSList *vpn_configs, + const GSList *dev_configs, + const GSList *other_configs, + const char *hostname); + + /* Subclasses should override and return TRUE if they start a local + * caching nameserver that listens on localhost and would block any + * other local caching nameserver from operating. + */ + gboolean (*is_exclusive) (NMDnsPlugin *self); + + /* Signals */ + + /* Emitted by the plugin and consumed by NMDnsManager when + * some error happens with the nameserver subprocess. Causes NM to fall + * back to writing out a non-local-caching resolv.conf until the next + * DNS update. + */ + void (*failed) (NMDnsPlugin *self); + + /* Emitted by the plugin base class when the nameserver subprocess + * quits. This signal is consumed by the plugin subclasses and not + * by NMDnsManager. If the subclass decides the exit status (as returned + * by waitpid(2)) is fatal it should then emit the 'failed' signal. + */ + void (*child_quit) (NMDnsPlugin *self, gint status); +} NMDnsPluginClass; + +GType nm_dns_plugin_get_type (void); + +gboolean nm_dns_plugin_is_exclusive (NMDnsPlugin *self); + +gboolean nm_dns_plugin_update (NMDnsPlugin *self, + const GSList *vpn_configs, + const GSList *dev_configs, + const GSList *other_configs, + const char *hostname); + +/* For subclasses/plugins */ + +/* Spawn a child process and watch for it to quit. 'argv' is the NULL-terminated + * argument vector to spawn the child with, where argv[0] is the full path to + * the child's executable. If 'pidfile' is given the process owning the PID + * contained in 'pidfile' will be killed if its command line matches 'kill_match' + * and the pidfile will be deleted. + */ +GPid nm_dns_plugin_child_spawn (NMDnsPlugin *self, + const char **argv, + const char *pidfile, + const char *kill_match); + +gboolean nm_dns_plugin_child_kill (NMDnsPlugin *self); + +#endif /* NM_DNS_PLUGIN_H */ + |